Skip to content

Commit

Permalink
feat: Add test for self-restart functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
lchico committed Jan 27, 2025
1 parent e3a107d commit 5b8b913
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 18 deletions.
12 changes: 7 additions & 5 deletions src/agent/restart_handler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ else()
endif()

add_library(RestartHandler ${SOURCES})
target_include_directories(RestartHandler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(RestartHandler
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(RestartHandler PUBLIC Boost::asio CommandEntry PRIVATE Logger)

include(../../cmake/ConfigureTarget.cmake)
configure_target(RestartHandler)

# if(BUILD_TESTS)
# enable_testing()
# add_subdirectory(tests)
# endif()
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
3 changes: 3 additions & 0 deletions src/agent/restart_handler/include/restart_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace restart_handler
public:
/// @brief Command-line arguments for the service.
static std::vector<char*> cmd_line;
/// @brief Timeout for the restart operation until force a hard restart (in seconds).
static const int timeout = 30;

explicit RestartHandler();

/// @brief Stores the command-line arguments passed to the agent.
Expand Down
12 changes: 4 additions & 8 deletions src/agent/restart_handler/src/restart_handler_unix.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "restart_handler_unix.hpp"
#include <fstream>
#include <restart_handler_unix.hpp>
#include <logger.hpp>

#include <chrono>
Expand Down Expand Up @@ -31,21 +30,18 @@ namespace restart_handler
}
}

void StopAgent()
void StopAgent(const pid_t pid, const int timeout)
{
const int timeout = 30;
time_t start_time = time(nullptr); // Record the start time to track the timeout duration

pid_t pid = getppid();

// Shutdown Gracefully
kill(pid, SIGTERM);

while (true)
{
if (kill(pid, 0) != 0)
{
LogInfo("Agent gracefully stopped.");
LogInfo("Agent stopped.");
break;
}

Expand All @@ -70,7 +66,7 @@ namespace restart_handler
else if (pid == 0)
{
// Child process
StopAgent();
StopAgent(getppid(), RestartHandler::timeout);

LogInfo("Starting wazuh agent in a new process.");

Expand Down
13 changes: 8 additions & 5 deletions src/agent/restart_handler/src/restart_handler_unix.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once
#include <boost/asio/awaitable.hpp>
#include <command_entry.hpp>
#include <restart_handler.hpp>
#include <vector>

Expand All @@ -13,11 +12,15 @@ namespace restart_handler
/// @return true if systemctl is available and running as a systemd service, otherwise returns false.
bool UsingSystemctl();

/// @brief Stops the agent by terminating the child process.
/// @brief Stops the agent by terminating its process.
///
/// This function sends a SIGTERM signal to the agent process to stop it. If the agent process
/// does not stop within a specified timeout period (30 seconds), it forces termination with a SIGKILL signal.
void StopAgent();
/// This function sends a SIGTERM signal to the agent process to request its termination. If the agent
/// process does not stop within the specified timeout period (30 seconds), a SIGKILL signal is sent to force
/// termination.
///
/// @param pid The process ID of the agent to be stopped.
/// @param timeout The maximum time (in seconds) to wait for the agent to stop before forcing termination.
void StopAgent(const pid_t pid, const int timeout);

/// @brief Restarts the module by forking a new process.
///
Expand Down
9 changes: 9 additions & 0 deletions src/agent/restart_handler/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
find_package(GTest CONFIG REQUIRED)

if(UNIX)
add_executable(restart_handler_test restart_handler_tests.cpp)
configure_target(restart_handler_test)
target_include_directories(restart_handler_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src)
target_link_libraries(restart_handler_test PRIVATE RestartHandler GTest::gtest GTest::gmock GTest::gmock_main)
add_test(NAME RestartHandler COMMAND restart_handler_test)
endif()
165 changes: 165 additions & 0 deletions src/agent/restart_handler/tests/restart_handler_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <restart_handler_unix.hpp>

#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>

#include <chrono>
#include <csignal>
#include <cstdlib>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

using namespace testing;

namespace
{
// NOLINTBEGIN(cppcoreguidelines-avoid-reference-signal-handler-definitions)
void SigchldHandler(int status)
{
while (waitpid(-1, &status, WNOHANG) > 0)
{
// The child process has terminated, no action needed here
}
}

// NOLINTEND(cppcoreguidelines-avoid-reference-signal-handler-definitions)

void SigtermHandler([[maybe_unused]] int signum)
{
exit(0);
}
} // namespace

TEST(TestUsingSystemctl, ReturnsTrueWhenSystemctlIsAvailable)
{
setenv("INVOCATION_ID", "testing", 1);
if ( 0 == std::system("which systemctl > /dev/null 2>&1") )
{
EXPECT_TRUE(restart_handler::UsingSystemctl());
}
else
{
EXPECT_FALSE(restart_handler::UsingSystemctl());
}
}

TEST(TestUsingSystemctl, ReturnsFalseWhenSystemctlIsNotAvailable)
{
unsetenv("INVOCATION_ID");
EXPECT_FALSE(restart_handler::UsingSystemctl());
}

TEST(StopAgentTest, GratefullyStop)
{
testing::internal::CaptureStdout();
signal(SIGCHLD, SigchldHandler);

pid_t pid = fork();

if (pid < 0)
{
FAIL() << "Fork failed!";
}
if (pid == 0)
{
// Child process (Simulate agent running)
const int timeout = 60;
std::this_thread::sleep_for(std::chrono::seconds(timeout));
}

// Parent process
std::this_thread::sleep_for(std::chrono::seconds(1));

const int timeout = 10;
restart_handler::StopAgent(pid, timeout);

std::string stdout_logs = testing::internal::GetCapturedStdout();
EXPECT_EQ(stdout_logs.find("Timeout reached! Forcing agent process termination."), std::string::npos);
EXPECT_NE(stdout_logs.find("Agent stopped"), std::string::npos);
}

TEST(StopAgentTest, ForceStop)
{
testing::internal::CaptureStdout();
signal(SIGCHLD, SigchldHandler);
signal(SIGTERM, SIG_IGN); // Ignore SIGTERM

pid_t pid = fork();

if (pid < 0)
{
FAIL() << "Fork failed!";
}
if (pid == 0)
{
// Child process (Simulate agent running)
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
std::this_thread::sleep_for(std::chrono::seconds(60));
}

// Parent process
std::this_thread::sleep_for(std::chrono::seconds(1));

// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
restart_handler::StopAgent(pid, 2);

std::string stdout_logs = testing::internal::GetCapturedStdout();
EXPECT_NE(stdout_logs.find("Timeout reached! Forcing agent process termination."), std::string::npos);
EXPECT_NE(stdout_logs.find("Agent stopped"), std::string::npos);
}

TEST(RestartWithForkTest, ShouldRestartUsingFork)
{

testing::internal::CaptureStdout();
pid_t pid = fork();

if (pid < 0)
{
FAIL() << "Fork failed!";
}
if (pid == 0)
{
// Child process (Simulate agent running)
setsid();
signal(SIGTERM, SigtermHandler);

boost::asio::io_context io_context;

boost::asio::co_spawn(
io_context,
[]() -> boost::asio::awaitable<void>
{
const char* args[] = {"/usr/bin/sleep", "3"};
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
restart_handler::RestartHandler::SetCommandLineArguments(2, const_cast<char**>(args));

const auto result = co_await restart_handler::RestartWithFork();
}(),
boost::asio::detached);

io_context.run();
}

// Parent process
int status {};
waitpid(pid, &status, 0);

std::string stdout_logs = testing::internal::GetCapturedStdout();
EXPECT_EQ(stdout_logs.find("Agent stopped"), std::string::npos);
EXPECT_EQ(stdout_logs.find("Starting wazuh agent in a new process"), std::string::npos);
}

int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

0 comments on commit 5b8b913

Please sign in to comment.