You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/test/tap/test_helpers/fake_plugin.cpp

281 lines
9.0 KiB

#include "ProxySQL_Plugin.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
// Two builds of this source produce two distinct .so files used together
// in multi-plugin tests:
// * libproxysql_fake_plugin.so — plugin name "fake_plugin", env vars PROXYSQL_FAKE_PLUGIN_*
// * libproxysql_fake_plugin2.so — plugin name "fake_plugin2", env vars PROXYSQL_FAKE_PLUGIN2_*
//
// FAKE_PLUGIN_NAME / FAKE_PLUGIN_ENV_PREFIX are -D'd by the Makefile.
#ifndef FAKE_PLUGIN_NAME
#define FAKE_PLUGIN_NAME "fake_plugin"
#endif
#ifndef FAKE_PLUGIN_ENV_PREFIX
#define FAKE_PLUGIN_ENV_PREFIX "PROXYSQL_FAKE_PLUGIN_"
#endif
namespace {
ProxySQL_PluginServices* fake_services = nullptr;
ProxySQL_PluginCommandResult fake_command(const ProxySQL_PluginCommandContext&, const char*) {
return {0, 1, "fake command executed"};
}
#ifdef PROXYSQL40
ProxySQL_PluginQueryHookResult fake_query_hook(const ProxySQL_PluginQueryHookPayload& payload) {
// Echo the SQL back through the message field so tests can verify the
// payload was wired through. DENY-vs-ALLOW is selected by env var so
// a test can exercise both paths without rebuilding the plugin.
std::string msg(payload.query_text, payload.query_len);
const char* deny_env = std::getenv("PROXYSQL_FAKE_PLUGIN_HOOK_DENY");
if (deny_env == nullptr) {
deny_env = std::getenv("PROXYSQL_FAKE_PLUGIN2_HOOK_DENY");
}
if (deny_env != nullptr && *deny_env != '\0') {
return {ProxySQL_PluginQueryHookAction::deny, std::string("denied: ") + msg};
}
return {ProxySQL_PluginQueryHookAction::allow, msg};
}
#endif /* PROXYSQL40 */
const char* env(const char* suffix) {
static char name[128];
std::snprintf(name, sizeof(name), "%s%s", FAKE_PLUGIN_ENV_PREFIX, suffix);
return std::getenv(name);
}
void fake_log_event(const char *event) {
const char *log_path = env("LOG");
if (log_path == nullptr || *log_path == '\0') {
return;
}
FILE *log_file = std::fopen(log_path, "a");
if (log_file == nullptr) {
return;
}
std::fprintf(log_file, "%s:%s\n", FAKE_PLUGIN_NAME, event);
std::fclose(log_file);
}
#ifdef PROXYSQL40
// Phase-B callback (Step 2 chassis ABI extension). Only wired into the
// descriptor when PROXYSQL_FAKE_PLUGIN_ENABLE_PHASE_B (or the plugin2
// variant) is set. Toggles via env vars:
// _PHASE_B_FAIL -> return false
// _PHASE_B_REGISTER_TABLE -> register a per-plugin admin table (Phase B)
// _PHASE_B_TOUCH_HANDLES -> try to call DB handle getters; if they come
// back null we log "phase_b_handles_null",
// otherwise "phase_b_handles_live" (the
// lifecycle contract says they must be null).
bool fake_register_schemas(ProxySQL_PluginServices *services) {
fake_services = services;
// _PHASE_B_PARTIAL_THEN_FAIL: register a table first, then return
// false. Tests that the loader rolls back the registration so a
// retry doesn't trip on the leftover.
if (env("PHASE_B_PARTIAL_THEN_FAIL") != nullptr &&
services != nullptr &&
services->register_table != nullptr) {
const ProxySQL_PluginTableDef table {
ProxySQL_PluginDBKind::admin_db,
FAKE_PLUGIN_NAME "_partial_table",
"CREATE TABLE " FAKE_PLUGIN_NAME "_partial_table (id INTEGER)"
};
services->register_table(table);
fake_log_event("phase_b_partial_then_fail");
return false;
}
if (env("PHASE_B_FAIL") != nullptr) {
fake_log_event("phase_b_fail");
return false;
}
if (env("PHASE_B_TOUCH_HANDLES") != nullptr &&
services != nullptr) {
// Contract: during Phase B the getters are non-null stub
// functions that return nullptr. A nullptr function pointer
// would mean plugins can't even call them unconditionally —
// that breaks the contract just as much as returning a live
// handle does. Distinguish the three outcomes so the test can
// assert the exact one we advertise.
if (services->get_admindb == nullptr ||
services->get_configdb == nullptr ||
services->get_statsdb == nullptr) {
fake_log_event("phase_b_getter_null");
} else {
SQLite3DB* a = services->get_admindb();
SQLite3DB* c = services->get_configdb();
SQLite3DB* s = services->get_statsdb();
if (a == nullptr && c == nullptr && s == nullptr) {
fake_log_event("phase_b_handles_null");
} else {
fake_log_event("phase_b_handles_live");
}
}
}
if (env("PHASE_B_REGISTER_TABLE") != nullptr &&
services != nullptr &&
services->register_table != nullptr) {
const ProxySQL_PluginTableDef table {
ProxySQL_PluginDBKind::admin_db,
FAKE_PLUGIN_NAME "_phase_b_table",
"CREATE TABLE " FAKE_PLUGIN_NAME "_phase_b_table (id INTEGER)"
};
services->register_table(table);
}
fake_log_event("phase_b");
return true;
}
#endif /* PROXYSQL40 */
bool fake_init(ProxySQL_PluginServices *services) {
fake_services = services;
if (env("INIT_FAIL") != nullptr) {
fake_log_event("init_fail");
return false;
}
if (env("REGISTER_INVALID_TABLE") != nullptr &&
services != nullptr &&
services->register_table != nullptr) {
const ProxySQL_PluginTableDef invalid_table {
static_cast<ProxySQL_PluginDBKind>(255),
"fake_invalid_table",
"CREATE TABLE fake_invalid_table (id INTEGER)"
};
services->register_table(invalid_table);
}
if (env("REGISTER_COMMAND") != nullptr &&
services != nullptr &&
services->register_command != nullptr) {
const char* sql = env("REGISTER_COMMAND_SQL");
services->register_command(sql != nullptr ? sql : "PLUGIN FAKE NOOP", &fake_command);
}
#ifdef PROXYSQL40
if (env("REGISTER_COMMAND_ALIAS") != nullptr &&
services != nullptr &&
services->register_command_alias != nullptr) {
const char* canonical = env("REGISTER_COMMAND_ALIAS_CANONICAL");
const char* alias = env("REGISTER_COMMAND_ALIAS_SQL");
if (canonical != nullptr && alias != nullptr) {
services->register_command_alias(canonical, alias);
}
}
#endif /* PROXYSQL40 */
if (env("REGISTER_TABLE") != nullptr &&
services != nullptr &&
services->register_table != nullptr) {
const ProxySQL_PluginTableDef table {
ProxySQL_PluginDBKind::admin_db,
FAKE_PLUGIN_NAME "_table",
"CREATE TABLE " FAKE_PLUGIN_NAME "_table (id INTEGER)"
};
services->register_table(table);
}
#ifdef PROXYSQL40
if (env("REGISTER_QUERY_HOOK") != nullptr &&
services != nullptr &&
services->register_query_hook != nullptr) {
const char* proto_env = env("REGISTER_QUERY_HOOK_PROTO");
ProxySQL_PluginProtocol proto = ProxySQL_PluginProtocol::mysql;
if (proto_env != nullptr && std::strcmp(proto_env, "pgsql") == 0) {
proto = ProxySQL_PluginProtocol::pgsql;
}
services->register_query_hook(proto, &fake_query_hook);
}
#endif /* PROXYSQL40 */
fake_log_event("init");
return true;
}
bool fake_start() {
if (env("START_FAIL") != nullptr) {
fake_log_event("start_fail");
return false;
}
if (fake_services == nullptr ||
fake_services->get_admindb == nullptr ||
fake_services->get_configdb == nullptr ||
fake_services->get_statsdb == nullptr ||
fake_services->get_admindb() == nullptr ||
fake_services->get_configdb() == nullptr ||
fake_services->get_statsdb() == nullptr) {
return false;
}
fake_log_event("start");
return true;
}
bool fake_stop() {
if (env("STOP_FAIL") != nullptr) {
fake_log_event("stop_fail");
return false;
}
fake_log_event("stop");
return true;
}
const char *fake_status_json() {
return "{\"name\":\"" FAKE_PLUGIN_NAME "\",\"state\":\"running\"}";
}
// Pre-Step-2.2 descriptor layout (six fields). Used when the plugin is
// NOT opting into Phase B -- register_schemas is implicitly null because
// the field is absent. Leaves us testing that plugins built against the
// older descriptor still work under the new loader. abi_version stays
// at 1 regardless of the compile-time PROXYSQL40 flag so this descriptor
// represents the "legacy plugin" shape the v4 loader has to accept.
const ProxySQL_PluginDescriptor fake_descriptor = {
FAKE_PLUGIN_NAME,
1,
&fake_init,
&fake_start,
&fake_stop,
&fake_status_json,
};
#ifdef PROXYSQL40
// Phase-B-aware descriptor: same as above but wires the register_schemas
// entry. Selected at plugin-discovery time when the env toggle is set.
// abi_version 2 tells the loader this descriptor has the seventh field.
const ProxySQL_PluginDescriptor fake_descriptor_with_phase_b = {
FAKE_PLUGIN_NAME,
2,
&fake_init,
&fake_start,
&fake_stop,
&fake_status_json,
&fake_register_schemas,
};
#endif /* PROXYSQL40 */
// Descriptor with a bogus ABI version -- used by lifecycle tests to
// verify the loader's version check rejects unknown ABIs rather than
// reading past the end of the plugin's struct.
const ProxySQL_PluginDescriptor fake_descriptor_bogus_abi = {
FAKE_PLUGIN_NAME,
99,
&fake_init,
&fake_start,
&fake_stop,
&fake_status_json,
};
} // namespace
extern "C" const ProxySQL_PluginDescriptor *proxysql_plugin_descriptor_v1() {
if (env("FORCE_BOGUS_ABI") != nullptr) {
return &fake_descriptor_bogus_abi;
}
#ifdef PROXYSQL40
if (env("ENABLE_PHASE_B") != nullptr) {
return &fake_descriptor_with_phase_b;
}
#endif /* PROXYSQL40 */
return &fake_descriptor;
}