From: Tomek Mrugalski Date: Mon, 8 Jun 2015 17:46:26 +0000 (+0200) Subject: [3880] Unit-test for UNIX control socket written. X-Git-Tag: trac3830_base~5^2~16 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fd66d0643b8b2b94164dbb189abe6e91af8a9aaa;p=thirdparty%2Fkea.git [3880] Unit-test for UNIX control socket written. --- diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index 86b393ee16..ed539b20c7 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -72,6 +72,70 @@ public: static_cast(unlink(LOAD_MARKER_FILE)); static_cast(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 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 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 diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index aeb5b1c952..ac38df5fdd 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -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. diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index 6e8fab6190..27a764a39f 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -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. ///