Implement Noise Injection framework for TAP tests

- Add use_noise flag to CommandLine
- Implement spawn_noise and stop_noise_tools in utils
- Integrate cleanup in exit_status
- Add initial noise tools in test/tap/noise/
- Add verification test test_noise_injection-t.cpp
pull/5408/head
Rene Cannao 3 months ago
parent 932e074649
commit 3018a3e0e8

@ -0,0 +1,12 @@
#!/bin/bash
HOST=$1
PORT=$2
USER=$3
PASS=$4
INTERVAL=${5:-1.0}
while true; do
mysql -h $HOST -P $PORT -u $USER -p$PASS -e "SELECT 1;" > /dev/null 2>&1
sleep $INTERVAL
done

@ -0,0 +1,14 @@
#!/bin/bash
HOST=$1
PORT=$2
USER=$3
PASS=$4
INTERVAL=${5:-0.5}
export PGPASSWORD=$PASS
while true; do
psql -h $HOST -p $PORT -U $USER -c "SELECT 1;" > /dev/null 2>&1
sleep $INTERVAL
done

@ -0,0 +1,44 @@
#!/usr/bin/env python3
import mysql.connector
import time
import argparse
import sys
import signal
def main():
parser = argparse.ArgumentParser(description="Generate noise by polling stats tables")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=6032)
parser.add_argument("--user", default="admin")
parser.add_argument("--password", default="admin")
parser.add_argument("--interval", type=float, default=0.5)
args = parser.parse_args()
def signal_handler(sig, frame):
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
try:
conn = mysql.connector.connect(
host=args.host,
port=args.port,
user=args.user,
password=args.password,
autocommit=True
)
cursor = conn.cursor()
while True:
cursor.execute("SELECT * FROM stats_mysql_query_digest")
cursor.fetchall()
cursor.execute("SELECT * FROM stats_mysql_connection_pool")
cursor.fetchall()
time.sleep(args.interval)
except Exception as e:
print(f"Error in noise poller: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

@ -376,5 +376,12 @@ int CommandLine::getEnv() {
}
}
value = getenv("TAP_USE_NOISE");
if (value) {
if (strcmp(value, "1") == 0 || strcasecmp(value, "true") == 0) {
this->use_noise = true;
}
}
return 0;
}

@ -12,6 +12,7 @@ class CommandLine {
bool checksum = true;
bool no_write = false;
bool use_noise = false;
int silent = false;
// unpriviliged test connection

@ -350,10 +350,14 @@ todo_end()
*g_test.todo = '\0';
}
extern "C" void stop_noise_tools();
int exit_status()
{
char buff[60];
stop_noise_tools();
/*
If there were no plan, we write one last instead.
*/

@ -8,6 +8,7 @@
#include <string>
#include <sstream>
#include <random>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utility>
@ -2435,3 +2436,63 @@ int run_q(MYSQL *mysql, const char *q) {
MYSQL_QUERY_T(mysql,q);
return 0;
}
static std::vector<pid_t> background_noise_pids;
static bool atexit_noise_registered = false;
extern "C" void stop_noise_tools() {
for (pid_t pid : background_noise_pids) {
kill(pid, SIGTERM);
// Small wait and reap
usleep(100000);
int status;
if (waitpid(pid, &status, WNOHANG) == 0) {
kill(pid, SIGKILL);
waitpid(pid, &status, 0);
}
}
background_noise_pids.clear();
}
void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std::vector<std::string>& args) {
if (!cl.use_noise) {
return;
}
if (!atexit_noise_registered) {
atexit(stop_noise_tools);
atexit_noise_registered = true;
}
pid_t pid = fork();
if (pid == 0) {
// Child
setpgid(0, 0);
int fd = open("/dev/null", O_RDWR);
if (fd != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > 2) {
close(fd);
}
}
std::vector<char*> c_args;
c_args.push_back(const_cast<char*>(tool_path.c_str()));
for (const auto& arg : args) {
c_args.push_back(const_cast<char*>(arg.c_str()));
}
c_args.push_back(nullptr);
execvp(tool_path.c_str(), c_args.data());
// If we are here, exec failed
fprintf(stderr, "Failed to exec noise tool: %s\n", tool_path.c_str());
_exit(1);
} else if (pid > 0) {
background_noise_pids.push_back(pid);
diag("Spawned background noise tool '%s' with PID %d", tool_path.c_str(), pid);
} else {
fprintf(stderr, "Failed to fork for noise tool: %s\n", tool_path.c_str());
}
}

@ -1063,4 +1063,18 @@ bool get_env_bool(const char* envname, bool envdefault);
MYSQL* init_mysql_conn(char* host, int port, char* user, char* pass, bool ssl=false, bool cmp=false);
int run_q(MYSQL *mysql, const char *q);
/**
* @brief Spawns a background noise tool if noise is enabled in CommandLine.
* @param cl The CommandLine object containing configuration.
* @param tool_path Path to the executable tool.
* @param args Vector of arguments to pass to the tool.
*/
void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std::vector<std::string>& args);
/**
* @brief Stops all background noise tools spawned by spawn_noise.
* @details This is intended to be called at the end of a TAP test.
*/
extern "C" void stop_noise_tools();
#endif // #define UTILS_H

@ -0,0 +1,68 @@
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <sys/types.h>
#include <signal.h>
#include "tap.h"
#include "command_line.h"
#include "utils.h"
int main(int argc, char** argv) {
CommandLine cl;
if (cl.getEnv()) {
diag("Failed to get environment variables");
return 1;
}
// Force noise enabled for this test if environment variable is not set
// but CommandLine::getEnv() already read it.
// To properly test, we should run this with TAP_USE_NOISE=1
if (!cl.use_noise) {
skip_all("TAP_USE_NOISE is not enabled. Skip noise injection test.");
}
plan(3);
// Use a simple script that just sleeps or a standard one
// We'll use our new stats poller but with a long interval or just 'sleep 100'
spawn_noise(cl, "/bin/sleep", {"100"});
// We can't easily get the PID from here as it's hidden in utils.cpp
// but we can check if a sleep 100 process exists.
// However, multiple might exist.
// Better way: use a specific noise tool that writes its PID to a file
std::string pid_file = "/tmp/proxysql_noise_test.pid";
std::string cmd = "echo $$ > " + pid_file + " && exec sleep 100";
spawn_noise(cl, "/bin/bash", {"-c", cmd});
sleep(1); // Give it time to start
ok(access(pid_file.c_str(), F_OK) == 0, "Noise process started and created PID file");
// Read PID from file
FILE* f = fopen(pid_file.c_str(), "r");
pid_t pid = 0;
if (f) {
if (fscanf(f, "%d", &pid) != 1) pid = 0;
fclose(f);
}
diag("Noise process PID: %d", pid);
// Verify it is alive
ok(pid > 0 && kill(pid, 0) == 0, "Noise process is alive");
// We can manually call stop_noise_tools() to verify it works
stop_noise_tools();
sleep(1); // Give it time to be killed
ok(pid > 0 && kill(pid, 0) != 0, "Noise process was killed");
if (access(pid_file.c_str(), F_OK) == 0) {
unlink(pid_file.c_str());
}
return exit_status();
}
Loading…
Cancel
Save