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 25, 2025
1 parent a5f847f commit 739b694
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 19 deletions.
13 changes: 8 additions & 5 deletions src/agent/restart/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ else()
endif()

add_library(Restart ${SOURCES})
target_include_directories(Restart PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(Restart
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

target_link_libraries(Restart PUBLIC ModuleCommand Boost::asio nlohmann_json::nlohmann_json PRIVATE Logger)

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

# if(BUILD_TESTS)
# enable_testing()
# add_subdirectory(tests)
# endif()
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
22 changes: 10 additions & 12 deletions src/agent/restart/src/restart_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,35 +47,32 @@ namespace restart
}
}

void StopAgent()
void StopAgent(pid_t ppid, const int timeout)
{
const int timeout = 30; // Timeout duration (in seconds) for killing the agent child process
time_t start_time = time(nullptr); // Record the start time to track the timeout duration

pid_t pid = getppid();

// Shutdown Gracefully
kill(pid, SIGTERM);
kill(ppid, SIGTERM);

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

if (difftime(time(nullptr), start_time) > timeout)
{
LogError("Timeout reached! Forcing agent process termination.");
kill(pid, SIGKILL);
kill(ppid, SIGKILL);
}

std::this_thread::sleep_for(std::chrono::seconds(1));
}
}

boost::asio::awaitable<module_command::CommandExecutionResult> RestartWithFork()
boost::asio::awaitable<module_command::CommandExecutionResult> RestartWithFork(std::vector<char*> args)
{
pid_t pid = fork();

Expand All @@ -86,9 +83,9 @@ namespace restart
else if (pid == 0)
{
// Child process
StopAgent();
const int timeout = 30;
StopAgent(getppid(), timeout);

std::vector<char*> args = GetCommandLineArgs();
LogInfo("Starting wazuh agent in a new process.");

if (execve(args[0], args.data(), nullptr) == -1)
Expand All @@ -110,7 +107,8 @@ namespace restart
}
else
{
return RestartWithFork();
std::vector<char*> args = GetCommandLineArgs();
return RestartWithFork(std::move(args));
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/agent/restart/src/restart_unix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ namespace restart
/// does not stop within a specified timeout period (30 seconds), it forces termination with a SIGKILL signal.
///
/// @param pid The process ID of the agent to stop.
void StopAgent();
void StopAgent(pid_t ppid, const int timeout);

/// @brief Restarts the module by forking a new process.
///
/// This function restarts the module by creating a new child process.
///
/// @return A boost::asio::awaitable containing the result of the command execution.
boost::asio::awaitable<module_command::CommandExecutionResult> RestartWithFork();
boost::asio::awaitable<module_command::CommandExecutionResult> RestartWithFork(std::vector<char*> args);

/// @brief Restarts the module using systemd service management.
///
Expand Down
11 changes: 11 additions & 0 deletions src/agent/restart/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
find_package(GTest CONFIG REQUIRED)


if(UNIX)
add_executable(restart_test unix_restart_tests.cpp)
endif()

configure_target(restart_test)
target_include_directories(restart_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src)
target_link_libraries(restart_test PRIVATE Restart GTest::gtest GTest::gmock GTest::gmock_main)
add_test(NAME Restart COMMAND restart_test)
152 changes: 152 additions & 0 deletions src/agent/restart/tests/unix_restart_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <restart_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 <spdlog/spdlog.h>
#include <string>
#include <thread>
#include <vector>

using namespace testing;

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

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

TEST(TestGetCommandLineArgs, ReturnsArgumentsWhenFileIsOpen)
{
std::vector<char*> args = restart::GetCommandLineArgs();

EXPECT_GT(args.size(), 0);
}

TEST(TestUsingSystemctl, ReturnsTrueWhenSystemctlIsAvailable)
{
setenv("INVOCATION_ID", "testing", 1);
EXPECT_TRUE(restart::UsingSystemctl());
}

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

TEST(StopAgentTest, GratefullyStop)
{
signal(SIGCHLD, SigchldHandler); // Set up the signal handler for SIGCHLD

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::StopAgent(pid, timeout);

ASSERT_TRUE(kill(pid, 0));
}

TEST(StopAgentTest, ForceStop)
{
signal(SIGCHLD, SigchldHandler); // Set up the signal handler for SIGCHLD
signal(SIGTERM, SIG_IGN); // Ignore SIGTERM

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 = 2;
restart::StopAgent(pid, timeout);

ASSERT_TRUE(kill(pid, 0));
}

// TEST(RestartWithForkTest, ShouldRestartUsingFork)
// {
// signal(SIGCHLD, SigchldHandler); // Set up the signal handler for SIGCHLD

// pid_t pid = fork();

// if (pid < 0)
// {
// FAIL() << "Fork failed!";
// }
// if (pid == 0)
// {
// // Child process (Simulate agent running)
// boost::asio::io_context io_context;

// boost::asio::co_spawn(
// io_context,
// []() -> boost::asio::awaitable<void>
// {
// std::vector<char*> args;
// std::string executable = "sleep 20";
// args.push_back(executable.data());
// args.push_back(nullptr); // Terminate with nullptr
// const auto result = co_await restart::RestartWithFork(std::move(args));

// }(),
// boost::asio::detached);
// const int timeout = 30;
// std::this_thread::sleep_for(std::chrono::seconds(timeout));

// }

// // Parent process
// std::this_thread::sleep_for(std::chrono::seconds(1));
// int status{};
// waitpid(pid, &status, 0);
// }

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

0 comments on commit 739b694

Please sign in to comment.