feat: add mysqlx runtime config store and load commands

mysqlx-plugin-impl
Rene Cannao 1 month ago
parent 782e019a8c
commit 0b11bce37e

@ -9,7 +9,7 @@ PLUGIN_DIR := $(PROXYSQL_PATH)/plugins/mysqlx
ODIR := $(PLUGIN_DIR)/obj
PLUGIN_SO := $(PLUGIN_DIR)/ProxySQL_MySQLX_Plugin.so
IDIRS := -I$(PROXYSQL_IDIR) -I$(PLUGIN_DIR)/include
IDIRS := -I$(PROXYSQL_IDIR) -I$(PLUGIN_DIR)/include -I$(SQLITE3_IDIR)
OPTZ ?= -O2 -ggdb
CXXFLAGS := $(STDCPP) -fPIC $(OPTZ) $(WGCOV) $(WASAN)
@ -17,7 +17,8 @@ CXXFLAGS := $(STDCPP) -fPIC $(OPTZ) $(WGCOV) $(WASAN)
.DEFAULT_GOAL := all
SRCS := $(PLUGIN_DIR)/src/mysqlx_plugin.cpp \
$(PLUGIN_DIR)/src/mysqlx_admin_schema.cpp
$(PLUGIN_DIR)/src/mysqlx_admin_schema.cpp \
$(PLUGIN_DIR)/src/mysqlx_config_store.cpp
HEADERS := $(wildcard $(PLUGIN_DIR)/include/*.h) \
$(PROXYSQL_PATH)/include/ProxySQL_Plugin.h
OBJS := $(patsubst $(PLUGIN_DIR)/src/%.cpp,$(ODIR)/%.o,$(SRCS))

@ -0,0 +1,74 @@
#ifndef PROXYSQL_MYSQLX_CONFIG_STORE_H
#define PROXYSQL_MYSQLX_CONFIG_STORE_H
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
class SQLite3DB;
enum class MysqlxBackendAuthMode : uint8_t {
mapped = 0,
service_account = 1,
pass_through = 2
};
MysqlxBackendAuthMode mysqlx_backend_auth_mode_from_string(const std::string& value);
struct MysqlxResolvedIdentity {
std::string username {};
int default_hostgroup { 0 };
int max_connections { 0 };
bool x_enabled { false };
bool require_tls { false };
std::string allowed_auth_methods {};
std::string default_route {};
std::string policy_profile {};
MysqlxBackendAuthMode backend_auth_mode { MysqlxBackendAuthMode::mapped };
std::string backend_username {};
std::string backend_password {};
std::string attributes {};
};
struct MysqlxRoute {
std::string name {};
std::string bind {};
int destination_hostgroup { 0 };
int fallback_hostgroup { -1 };
std::string strategy { "first_available" };
bool active { true };
std::string attributes {};
};
struct MysqlxBackendEndpoint {
std::string hostname {};
int mysql_port { 0 };
int mysqlx_port { 33060 };
bool use_ssl { false };
std::string attributes {};
};
class MysqlxConfigStore {
public:
MysqlxConfigStore() = default;
MysqlxConfigStore(const MysqlxConfigStore&) = delete;
MysqlxConfigStore& operator=(const MysqlxConfigStore&) = delete;
~MysqlxConfigStore() = default;
bool load_from_runtime(SQLite3DB& db, std::string& err);
std::optional<MysqlxResolvedIdentity> resolve_identity(const std::string& username) const;
MysqlxBackendEndpoint pick_endpoint(const std::string& route_name) const;
uint64_t topology_generation() const;
void bump_topology_generation();
private:
std::unordered_map<std::string, MysqlxResolvedIdentity> identities_ {};
std::unordered_map<std::string, MysqlxRoute> routes_ {};
std::unordered_map<int, std::vector<MysqlxBackendEndpoint>> hostgroup_endpoints_ {};
uint64_t topology_generation_ { 0 };
};
#endif /* PROXYSQL_MYSQLX_CONFIG_STORE_H */

@ -3,11 +3,10 @@
#include "ProxySQL_Plugin.h"
#include "mysqlx_admin_schema.h"
#include "mysqlx_config_store.h"
#include <memory>
class MysqlxConfigStore;
struct MysqlxPluginContext {
ProxySQL_PluginServices* services { nullptr };
std::unique_ptr<MysqlxConfigStore> config_store {};

@ -1,8 +1,18 @@
#include "mysqlx_admin_schema.h"
#include "sqlite3db.h"
#include <string>
namespace {
const char kMysqlxUsersTable[] = "mysqlx_users";
const char kRuntimeMysqlxUsersTable[] = "runtime_mysqlx_users";
const char kMysqlxRoutesTable[] = "mysqlx_routes";
const char kRuntimeMysqlxRoutesTable[] = "runtime_mysqlx_routes";
const char kMysqlxBackendEndpointsTable[] = "mysqlx_backend_endpoints";
const char kRuntimeMysqlxBackendEndpointsTable[] = "runtime_mysqlx_backend_endpoints";
const char kMysqlxUsersTableDef[] =
"CREATE TABLE mysqlx_users ("
" username VARCHAR NOT NULL PRIMARY KEY,"
@ -18,25 +28,194 @@ const char kMysqlxUsersTableDef[] =
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
} // namespace
const char kRuntimeMysqlxUsersTableDef[] =
"CREATE TABLE runtime_mysqlx_users ("
" username VARCHAR NOT NULL PRIMARY KEY,"
" active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,"
" require_tls INT CHECK (require_tls IN (0,1)) NOT NULL DEFAULT 0,"
" allowed_auth_methods VARCHAR NOT NULL DEFAULT '',"
" default_route VARCHAR,"
" policy_profile VARCHAR,"
" backend_auth_mode VARCHAR NOT NULL DEFAULT 'mapped',"
" backend_username VARCHAR,"
" backend_password VARCHAR,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) {
if (services.register_table == nullptr) {
const char kMysqlxRoutesTableDef[] =
"CREATE TABLE mysqlx_routes ("
" name VARCHAR NOT NULL PRIMARY KEY,"
" bind VARCHAR NOT NULL,"
" destination_hostgroup INT NOT NULL,"
" fallback_hostgroup INT,"
" strategy VARCHAR NOT NULL DEFAULT 'first_available',"
" active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
const char kRuntimeMysqlxRoutesTableDef[] =
"CREATE TABLE runtime_mysqlx_routes ("
" name VARCHAR NOT NULL PRIMARY KEY,"
" bind VARCHAR NOT NULL,"
" destination_hostgroup INT NOT NULL,"
" fallback_hostgroup INT,"
" strategy VARCHAR NOT NULL DEFAULT 'first_available',"
" active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
const char kMysqlxBackendEndpointsTableDef[] =
"CREATE TABLE mysqlx_backend_endpoints ("
" hostname VARCHAR NOT NULL,"
" mysql_port INT NOT NULL,"
" mysqlx_port INT NOT NULL DEFAULT 33060,"
" use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT '',"
" PRIMARY KEY (hostname, mysql_port)"
" )";
const char kRuntimeMysqlxBackendEndpointsTableDef[] =
"CREATE TABLE runtime_mysqlx_backend_endpoints ("
" hostname VARCHAR NOT NULL,"
" mysql_port INT NOT NULL,"
" mysqlx_port INT NOT NULL DEFAULT 33060,"
" use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT '',"
" PRIMARY KEY (hostname, mysql_port)"
" )";
ProxySQL_PluginCommandResult command_failure(const char* message) {
return {1, 0, message != nullptr ? message : "mysqlx admin command failed"};
}
bool copy_table(SQLite3DB& db, const char* source_table, const char* runtime_table) {
if (!db.execute("BEGIN")) {
return false;
}
std::string delete_sql = "DELETE FROM ";
delete_sql += runtime_table;
if (!db.execute(delete_sql.c_str())) {
db.execute("ROLLBACK");
return false;
}
std::string insert_sql = "INSERT INTO ";
insert_sql += runtime_table;
insert_sql += " SELECT * FROM ";
insert_sql += source_table;
if (!db.execute(insert_sql.c_str())) {
db.execute("ROLLBACK");
return false;
}
if (!db.execute("COMMIT")) {
db.execute("ROLLBACK");
return false;
}
return true;
}
ProxySQL_PluginCommandResult load_users_to_runtime(const ProxySQL_PluginCommandContext& ctx, const char*) {
if (ctx.admindb == nullptr) {
return command_failure("mysqlx users load requires admin db");
}
if (!copy_table(*ctx.admindb, kMysqlxUsersTable, kRuntimeMysqlxUsersTable)) {
return command_failure("failed to copy mysqlx users to runtime");
}
ProxySQL_PluginCommandResult result {0, 0, ""};
result.rows_affected = ctx.admindb->return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users");
result.message = "mysqlx users loaded to runtime";
return result;
}
ProxySQL_PluginCommandResult load_routes_to_runtime(const ProxySQL_PluginCommandContext& ctx, const char*) {
if (ctx.admindb == nullptr) {
return command_failure("mysqlx routes load requires admin db");
}
if (!copy_table(*ctx.admindb, kMysqlxRoutesTable, kRuntimeMysqlxRoutesTable)) {
return command_failure("failed to copy mysqlx routes to runtime");
}
ProxySQL_PluginCommandResult result {0, 0, ""};
result.rows_affected = ctx.admindb->return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_routes");
result.message = "mysqlx routes loaded to runtime";
return result;
}
ProxySQL_PluginCommandResult load_backend_endpoints_to_runtime(const ProxySQL_PluginCommandContext& ctx, const char*) {
if (ctx.admindb == nullptr) {
return command_failure("mysqlx backend endpoints load requires admin db");
}
if (!copy_table(*ctx.admindb, kMysqlxBackendEndpointsTable, kRuntimeMysqlxBackendEndpointsTable)) {
return command_failure("failed to copy mysqlx backend endpoints to runtime");
}
ProxySQL_PluginCommandResult result {0, 0, ""};
result.rows_affected = ctx.admindb->return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_backend_endpoints");
result.message = "mysqlx backend endpoints loaded to runtime";
return result;
}
void register_table_pair(
ProxySQL_PluginServices& services,
const char* table_name,
const char* table_def
) {
ProxySQL_PluginTableDef admin_def {
ProxySQL_PluginDBKind::admin_db,
kMysqlxUsersTable,
kMysqlxUsersTableDef
table_name,
table_def
};
ProxySQL_PluginTableDef config_def {
ProxySQL_PluginDBKind::config_db,
kMysqlxUsersTable,
kMysqlxUsersTableDef
table_name,
table_def
};
services.register_table(admin_def);
services.register_table(config_def);
}
void register_runtime_table(
ProxySQL_PluginServices& services,
const char* table_name,
const char* table_def
) {
ProxySQL_PluginTableDef runtime_def {
ProxySQL_PluginDBKind::admin_db,
table_name,
table_def
};
services.register_table(runtime_def);
}
} // namespace
bool mysqlx_register_admin_schema(ProxySQL_PluginServices& services) {
if (services.register_table == nullptr || services.register_command == nullptr) {
return false;
}
register_table_pair(services, kMysqlxUsersTable, kMysqlxUsersTableDef);
register_runtime_table(services, kRuntimeMysqlxUsersTable, kRuntimeMysqlxUsersTableDef);
register_table_pair(services, kMysqlxRoutesTable, kMysqlxRoutesTableDef);
register_runtime_table(services, kRuntimeMysqlxRoutesTable, kRuntimeMysqlxRoutesTableDef);
register_table_pair(services, kMysqlxBackendEndpointsTable, kMysqlxBackendEndpointsTableDef);
register_runtime_table(services, kRuntimeMysqlxBackendEndpointsTable, kRuntimeMysqlxBackendEndpointsTableDef);
services.register_command("PLUGIN MYSQLX LOAD USERS TO RUNTIME", &load_users_to_runtime);
services.register_command("PLUGIN MYSQLX LOAD ROUTES TO RUNTIME", &load_routes_to_runtime);
services.register_command("PLUGIN MYSQLX LOAD BACKEND ENDPOINTS TO RUNTIME", &load_backend_endpoints_to_runtime);
return true;
}

@ -0,0 +1,276 @@
#include "mysqlx_config_store.h"
#include "sqlite3db.h"
#include <cstdlib>
#include <memory>
#include <strings.h>
#include <unordered_map>
namespace {
struct MysqlxEndpointOverride {
int mysqlx_port { 33060 };
bool use_ssl { false };
std::string attributes {};
};
std::string nullable_string(const char* value) {
return value != nullptr ? value : "";
}
int nullable_int(const char* value, int default_value = 0) {
return value != nullptr ? std::atoi(value) : default_value;
}
bool nullable_bool(const char* value, bool default_value = false) {
return value != nullptr ? std::atoi(value) != 0 : default_value;
}
std::string endpoint_key(const std::string& hostname, int mysql_port) {
return hostname + ":" + std::to_string(mysql_port);
}
bool fetch_result(SQLite3DB& db, const char* sql, std::unique_ptr<SQLite3_result>& result, std::string& err) {
char* error = nullptr;
result.reset(db.execute_statement(sql, &error));
if (error != nullptr) {
err = error;
free(error);
return false;
}
if (!result) {
err = "sqlite query returned no result";
return false;
}
return true;
}
void load_canonical_users(
SQLite3_result& rows,
std::unordered_map<std::string, MysqlxResolvedIdentity>& identities
) {
for (auto* row : rows.rows) {
if (row == nullptr) {
continue;
}
MysqlxResolvedIdentity identity {};
identity.username = nullable_string(row->fields[0]);
identity.default_hostgroup = nullable_int(row->fields[1]);
identity.max_connections = nullable_int(row->fields[2]);
identities[identity.username] = std::move(identity);
}
}
void merge_mysqlx_users(
SQLite3_result& rows,
std::unordered_map<std::string, MysqlxResolvedIdentity>& identities
) {
for (auto* row : rows.rows) {
if (row == nullptr || row->fields[0] == nullptr) {
continue;
}
const std::string username = row->fields[0];
auto it = identities.find(username);
if (it == identities.end()) {
continue;
}
MysqlxResolvedIdentity& identity = it->second;
identity.x_enabled = nullable_bool(row->fields[1]);
identity.require_tls = nullable_bool(row->fields[2]);
identity.allowed_auth_methods = nullable_string(row->fields[3]);
identity.default_route = nullable_string(row->fields[4]);
identity.policy_profile = nullable_string(row->fields[5]);
identity.backend_auth_mode = mysqlx_backend_auth_mode_from_string(nullable_string(row->fields[6]));
identity.backend_username = nullable_string(row->fields[7]);
identity.backend_password = nullable_string(row->fields[8]);
identity.attributes = nullable_string(row->fields[9]);
}
}
void load_routes(
SQLite3_result& rows,
std::unordered_map<std::string, MysqlxRoute>& routes
) {
for (auto* row : rows.rows) {
if (row == nullptr || row->fields[0] == nullptr) {
continue;
}
MysqlxRoute route {};
route.name = nullable_string(row->fields[0]);
route.bind = nullable_string(row->fields[1]);
route.destination_hostgroup = nullable_int(row->fields[2]);
route.fallback_hostgroup = nullable_int(row->fields[3], -1);
route.strategy = nullable_string(row->fields[4]);
route.active = nullable_bool(row->fields[5], true);
route.attributes = nullable_string(row->fields[6]);
routes[route.name] = std::move(route);
}
}
void load_endpoint_overrides(
SQLite3_result& rows,
std::unordered_map<std::string, MysqlxEndpointOverride>& overrides
) {
for (auto* row : rows.rows) {
if (row == nullptr || row->fields[0] == nullptr) {
continue;
}
MysqlxEndpointOverride override {};
const std::string hostname = nullable_string(row->fields[0]);
const int mysql_port = nullable_int(row->fields[1]);
override.mysqlx_port = nullable_int(row->fields[2], 33060);
override.use_ssl = nullable_bool(row->fields[3]);
override.attributes = nullable_string(row->fields[4]);
overrides[endpoint_key(hostname, mysql_port)] = std::move(override);
}
}
void load_backend_servers(
SQLite3_result& rows,
const std::unordered_map<std::string, MysqlxEndpointOverride>& overrides,
std::unordered_map<int, std::vector<MysqlxBackendEndpoint>>& hostgroup_endpoints
) {
for (auto* row : rows.rows) {
if (row == nullptr || row->fields[1] == nullptr) {
continue;
}
MysqlxBackendEndpoint endpoint {};
const int hostgroup_id = nullable_int(row->fields[0]);
endpoint.hostname = nullable_string(row->fields[1]);
endpoint.mysql_port = nullable_int(row->fields[2]);
endpoint.use_ssl = nullable_bool(row->fields[3]);
const auto it = overrides.find(endpoint_key(endpoint.hostname, endpoint.mysql_port));
if (it != overrides.end()) {
endpoint.mysqlx_port = it->second.mysqlx_port;
endpoint.use_ssl = it->second.use_ssl;
endpoint.attributes = it->second.attributes;
}
hostgroup_endpoints[hostgroup_id].push_back(std::move(endpoint));
}
}
} // namespace
MysqlxBackendAuthMode mysqlx_backend_auth_mode_from_string(const std::string& value) {
if (strcasecmp(value.c_str(), "pass_through") == 0) {
return MysqlxBackendAuthMode::pass_through;
}
if (strcasecmp(value.c_str(), "service_account") == 0) {
return MysqlxBackendAuthMode::service_account;
}
return MysqlxBackendAuthMode::mapped;
}
bool MysqlxConfigStore::load_from_runtime(SQLite3DB& db, std::string& err) {
err.clear();
std::unordered_map<std::string, MysqlxResolvedIdentity> new_identities {};
std::unordered_map<std::string, MysqlxRoute> new_routes {};
std::unordered_map<int, std::vector<MysqlxBackendEndpoint>> new_hostgroup_endpoints {};
std::unordered_map<std::string, MysqlxEndpointOverride> endpoint_overrides {};
std::unique_ptr<SQLite3_result> result {};
if (!fetch_result(
db,
"SELECT username, default_hostgroup, max_connections "
"FROM runtime_mysql_users WHERE active=1 AND frontend=1",
result,
err)) {
return false;
}
load_canonical_users(*result, new_identities);
if (!fetch_result(
db,
"SELECT username, active, require_tls, allowed_auth_methods, default_route, policy_profile, "
"backend_auth_mode, backend_username, backend_password, attributes "
"FROM runtime_mysqlx_users",
result,
err)) {
return false;
}
merge_mysqlx_users(*result, new_identities);
if (!fetch_result(
db,
"SELECT name, bind, destination_hostgroup, fallback_hostgroup, strategy, active, attributes "
"FROM runtime_mysqlx_routes WHERE active=1",
result,
err)) {
return false;
}
load_routes(*result, new_routes);
if (!fetch_result(
db,
"SELECT hostname, mysql_port, mysqlx_port, use_ssl, attributes "
"FROM runtime_mysqlx_backend_endpoints",
result,
err)) {
return false;
}
load_endpoint_overrides(*result, endpoint_overrides);
if (!fetch_result(
db,
"SELECT hostgroup_id, hostname, port, use_ssl "
"FROM runtime_mysql_servers WHERE UPPER(status)='ONLINE' "
"ORDER BY hostgroup_id, weight DESC, hostname, port",
result,
err)) {
return false;
}
load_backend_servers(*result, endpoint_overrides, new_hostgroup_endpoints);
identities_.swap(new_identities);
routes_.swap(new_routes);
hostgroup_endpoints_.swap(new_hostgroup_endpoints);
return true;
}
std::optional<MysqlxResolvedIdentity> MysqlxConfigStore::resolve_identity(const std::string& username) const {
const auto it = identities_.find(username);
if (it == identities_.end()) {
return std::nullopt;
}
return it->second;
}
MysqlxBackendEndpoint MysqlxConfigStore::pick_endpoint(const std::string& route_name) const {
const auto route_it = routes_.find(route_name);
if (route_it == routes_.end()) {
return {};
}
const MysqlxRoute& route = route_it->second;
const auto primary_it = hostgroup_endpoints_.find(route.destination_hostgroup);
if (primary_it != hostgroup_endpoints_.end() && !primary_it->second.empty()) {
return primary_it->second.front();
}
if (route.fallback_hostgroup >= 0) {
const auto fallback_it = hostgroup_endpoints_.find(route.fallback_hostgroup);
if (fallback_it != hostgroup_endpoints_.end() && !fallback_it->second.empty()) {
return fallback_it->second.front();
}
}
return {};
}
uint64_t MysqlxConfigStore::topology_generation() const {
return topology_generation_;
}
void MysqlxConfigStore::bump_topology_generation() {
++topology_generation_;
}

@ -1,23 +1,16 @@
#include "mysqlx_plugin.h"
class MysqlxConfigStore {
public:
MysqlxConfigStore() = default;
MysqlxConfigStore(const MysqlxConfigStore&) = delete;
MysqlxConfigStore& operator=(const MysqlxConfigStore&) = delete;
~MysqlxConfigStore() = default;
};
namespace {
bool mysqlx_init(ProxySQL_PluginServices* services) {
if (services == nullptr) {
return false;
}
MysqlxPluginContext& ctx = mysqlx_context();
ctx.services = services;
ctx.config_store = std::make_unique<MysqlxConfigStore>();
ctx.started = false;
if (services == nullptr) {
return false;
}
return mysqlx_register_admin_schema(*services);
}

@ -78,7 +78,7 @@ SQLite3DB* proxysql_plugin_get_statsdb() {
}
int main() {
plan(18);
plan(20);
ok(test_init_minimal() == 0, "minimal test globals initialize");

@ -362,8 +362,8 @@ test_mysqlx_plugin_load-t: ../test_mysqlx_plugin_load-t.cpp $(ODIR)/tap.o $(ODIR
$(IDIRS) $(LDIRS) $(OPT) $(LIBPROXYSQLAR_FULL) $(STATIC_LIBS) \
$(MYLIBS) -ldl $(ALLOW_MULTI_DEF) -o $@
mysqlx_config_store_unit-t: mysqlx_config_store_unit-t.cpp $(TEST_HELPERS_OBJ) $(LIBPROXYSQLAR)
$(CXX) $< $(TEST_HELPERS_OBJ) $(IDIRS) $(LDIRS) $(OPT) \
mysqlx_config_store_unit-t: mysqlx_config_store_unit-t.cpp $(PROXYSQL_PATH)/plugins/mysqlx/src/mysqlx_config_store.cpp $(TEST_HELPERS_OBJ) $(LIBPROXYSQLAR)
$(CXX) $< $(PROXYSQL_PATH)/plugins/mysqlx/src/mysqlx_config_store.cpp $(TEST_HELPERS_OBJ) $(IDIRS) $(LDIRS) $(OPT) \
-I$(PROXYSQL_PATH)/plugins/mysqlx/include \
$(LIBPROXYSQLAR_FULL) $(STATIC_LIBS) $(MYLIBS) \
$(ALLOW_MULTI_DEF) -o $@

@ -1,8 +1,61 @@
#include "mysqlx_config_store.h"
#include "ProxySQL_Admin_Tables_Definitions.h"
#include "sqlite3db.h"
#include "tap.h"
#include <memory>
#include <string>
namespace {
const char kRuntimeMysqlxUsersDdl[] =
"CREATE TABLE runtime_mysqlx_users ("
" username VARCHAR NOT NULL PRIMARY KEY,"
" active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,"
" require_tls INT CHECK (require_tls IN (0,1)) NOT NULL DEFAULT 0,"
" allowed_auth_methods VARCHAR NOT NULL DEFAULT '',"
" default_route VARCHAR,"
" policy_profile VARCHAR,"
" backend_auth_mode VARCHAR NOT NULL DEFAULT 'mapped',"
" backend_username VARCHAR,"
" backend_password VARCHAR,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
const char kRuntimeMysqlxRoutesDdl[] =
"CREATE TABLE runtime_mysqlx_routes ("
" name VARCHAR NOT NULL PRIMARY KEY,"
" bind VARCHAR NOT NULL,"
" destination_hostgroup INT NOT NULL,"
" fallback_hostgroup INT,"
" strategy VARCHAR NOT NULL DEFAULT 'first_available',"
" active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT ''"
" )";
const char kRuntimeMysqlxEndpointsDdl[] =
"CREATE TABLE runtime_mysqlx_backend_endpoints ("
" hostname VARCHAR NOT NULL,"
" mysql_port INT NOT NULL,"
" mysqlx_port INT NOT NULL DEFAULT 33060,"
" use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,"
" attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',"
" comment VARCHAR NOT NULL DEFAULT '',"
" PRIMARY KEY (hostname, mysql_port)"
" )";
std::unique_ptr<SQLite3DB> create_test_db() {
auto db = std::make_unique<SQLite3DB>();
db->open((char*)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX);
return db;
}
} // namespace
int main() {
plan(5);
plan(16);
MysqlxResolvedIdentity identity {};
identity.username = "canonical_user";
@ -21,5 +74,63 @@ int main() {
MysqlxBackendAuthMode::pass_through,
"backend auth mode parser accepts pass_through");
auto db = create_test_db();
ok(db->execute(ADMIN_SQLITE_RUNTIME_MYSQL_USERS),
"runtime mysql users table is created");
ok(db->execute(ADMIN_SQLITE_TABLE_RUNTIME_MYSQL_SERVERS),
"runtime mysql servers table is created");
ok(db->execute(kRuntimeMysqlxUsersDdl) &&
db->execute(kRuntimeMysqlxRoutesDdl) &&
db->execute(kRuntimeMysqlxEndpointsDdl),
"runtime mysqlx tables are created");
ok(db->execute("INSERT INTO runtime_mysql_users (username, password, active, use_ssl, default_hostgroup, "
"default_schema, schema_locked, transaction_persistent, fast_forward, backend, frontend, "
"max_connections, attributes, comment) VALUES "
"('alice', 'pw', 1, 0, 10, NULL, 0, 1, 0, 0, 1, 25, '', 'canonical')"),
"canonical frontend mysql user is inserted");
ok(db->execute("INSERT INTO runtime_mysqlx_users (username, active, require_tls, allowed_auth_methods, "
"default_route, policy_profile, backend_auth_mode, backend_username, backend_password, attributes, comment) VALUES "
"('alice', 1, 1, 'PLAIN', 'rw', 'policy-a', 'service_account', 'svc_user', 'svc_pass', '', 'override')"),
"mysqlx override row is inserted");
ok(db->execute("INSERT INTO runtime_mysqlx_routes (name, bind, destination_hostgroup, fallback_hostgroup, strategy, active, attributes, comment) VALUES "
"('rw', '127.0.0.1:6603', 42, 43, 'first_available', 1, '', 'route')") &&
db->execute("INSERT INTO runtime_mysql_servers (hostgroup_id, hostname, port, gtid_port, status, weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment) VALUES "
"(42, 'db1.internal', 3306, 0, 'ONLINE', 100, 0, 1000, 0, 0, 0, 'server')") &&
db->execute("INSERT INTO runtime_mysqlx_backend_endpoints (hostname, mysql_port, mysqlx_port, use_ssl, attributes, comment) VALUES "
"('db1.internal', 3306, 33100, 1, '', 'endpoint')"),
"route, backend server, and mysqlx endpoint are inserted");
MysqlxConfigStore store {};
std::string err {};
ok(store.load_from_runtime(*db, err) && err.empty(),
"config store loads runtime mysql and mysqlx state");
const auto resolved = store.resolve_identity("alice");
ok(resolved.has_value() &&
resolved->default_hostgroup == 10 &&
resolved->max_connections == 25 &&
resolved->x_enabled &&
resolved->require_tls &&
resolved->default_route == "rw" &&
resolved->policy_profile == "policy-a" &&
resolved->backend_auth_mode == MysqlxBackendAuthMode::service_account &&
resolved->backend_username == "svc_user" &&
resolved->backend_password == "svc_pass",
"config store merges canonical mysql user and mysqlx override state");
const MysqlxBackendEndpoint endpoint = store.pick_endpoint("rw");
ok(endpoint.hostname == "db1.internal" &&
endpoint.mysql_port == 3306 &&
endpoint.mysqlx_port == 33100 &&
endpoint.use_ssl,
"config store picks backend endpoint from route hostgroup and mysqlx overrides");
ok(store.topology_generation() == 0,
"topology generation starts at zero");
store.bump_topology_generation();
ok(store.topology_generation() == 1,
"topology generation increments on demand");
return exit_status();
}

Loading…
Cancel
Save