|
@@ -72,6 +72,70 @@ public:
|
|
static_cast<void>(unlink(LOAD_MARKER_FILE));
|
|
static_cast<void>(unlink(LOAD_MARKER_FILE));
|
|
static_cast<void>(unlink(UNLOAD_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) {
|
|
TEST_F(CtrlDhcpv4SrvTest, commands) {
|
|
@@ -193,7 +257,14 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|
|
|
|
|
// Checks if the server is able to parse control socket configuration and
|
|
// Checks if the server is able to parse control socket configuration and
|
|
// configures the command socket properly.
|
|
// 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";
|
|
string socket_path = string(TEST_DATA_DIR) + "/kea4.sock";
|
|
|
|
|
|
@@ -211,35 +282,60 @@ TEST_F(CtrlDhcpv4SrvTest, commandSocketBasic) {
|
|
" \"control-socket\": {"
|
|
" \"control-socket\": {"
|
|
" \"socket-type\": \"unix\","
|
|
" \"socket-type\": \"unix\","
|
|
" \"socket-name\": \"" + socket_path + "\""
|
|
" \"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
|
|
} // End of anonymous namespace
|