mirror of https://github.com/sysown/proxysql
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.
281 lines
9.0 KiB
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;
|
|
}
|