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) {
// 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";
" \"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