
Since the module command interface was expanded to include a JSON output parameter, there is no longer a need for an output DCB. As the JSON can be printed by both maxadmin and the REST API, this allows the removal of explicit output formatting in module commands.
615 lines
18 KiB
C++
615 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2020-01-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
/**
|
|
* @file load_utils.c Utility functions for loading of modules
|
|
*/
|
|
|
|
#include "maxscale/modules.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include <maxscale/modinfo.h>
|
|
#include <maxscale/log_manager.h>
|
|
#include <maxscale/version.h>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/json_api.h>
|
|
#include <maxscale/modulecmd.h>
|
|
|
|
#include "maxscale/modules.h"
|
|
#include "maxscale/config.h"
|
|
|
|
typedef struct loaded_module
|
|
{
|
|
char *module; /**< The name of the module */
|
|
char *type; /**< The module type */
|
|
char *version; /**< Module version */
|
|
void *handle; /**< The handle returned by dlopen */
|
|
void *modobj; /**< The module "object" this is the set of entry points */
|
|
MXS_MODULE *info; /**< The module information */
|
|
struct loaded_module *next; /**< Next module in the linked list */
|
|
} LOADED_MODULE;
|
|
|
|
static LOADED_MODULE *registered = NULL;
|
|
|
|
static LOADED_MODULE *find_module(const char *module);
|
|
static LOADED_MODULE* register_module(const char *module,
|
|
const char *type,
|
|
void *dlhandle,
|
|
MXS_MODULE *mod_info);
|
|
static void unregister_module(const char *module);
|
|
|
|
static bool check_module(const MXS_MODULE *mod_info, const char *type, const char *module)
|
|
{
|
|
bool success = true;
|
|
|
|
if (strcmp(type, MODULE_PROTOCOL) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_PROTOCOL)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the protocol API.", module);
|
|
success = false;
|
|
}
|
|
if (strcmp(type, MODULE_AUTHENTICATOR) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_AUTHENTICATOR)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the authenticator API.", module);
|
|
success = false;
|
|
}
|
|
if (strcmp(type, MODULE_ROUTER) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_ROUTER)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the router API.", module);
|
|
success = false;
|
|
}
|
|
if (strcmp(type, MODULE_MONITOR) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_MONITOR)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the monitor API.", module);
|
|
success = false;
|
|
}
|
|
if (strcmp(type, MODULE_FILTER) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_FILTER)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the filter API.", module);
|
|
success = false;
|
|
}
|
|
if (strcmp(type, MODULE_QUERY_CLASSIFIER) == 0
|
|
&& mod_info->modapi != MXS_MODULE_API_QUERY_CLASSIFIER)
|
|
{
|
|
MXS_ERROR("Module '%s' does not implement the query classifier API.", module);
|
|
success = false;
|
|
}
|
|
if (mod_info->version == NULL)
|
|
{
|
|
MXS_ERROR("Module '%s' does not define a version string", module);
|
|
success = false;
|
|
}
|
|
|
|
if (mod_info->module_object == NULL)
|
|
{
|
|
MXS_ERROR("Module '%s' does not define a module object", module);
|
|
success = false;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void *load_module(const char *module, const char *type)
|
|
{
|
|
ss_dassert(module && type);
|
|
LOADED_MODULE *mod;
|
|
|
|
if ((mod = find_module(module)) == NULL)
|
|
{
|
|
/** The module is not already loaded, search for the shared object */
|
|
char fname[MAXPATHLEN + 1];
|
|
snprintf(fname, MAXPATHLEN + 1, "%s/lib%s.so", get_libdir(), module);
|
|
|
|
if (access(fname, F_OK) == -1)
|
|
{
|
|
MXS_ERROR("Unable to find library for "
|
|
"module: %s. Module dir: %s",
|
|
module, get_libdir());
|
|
return NULL;
|
|
}
|
|
|
|
void *dlhandle = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
|
|
|
|
if (dlhandle == NULL)
|
|
{
|
|
MXS_ERROR("Unable to load library for module: "
|
|
"%s\n\n\t\t %s."
|
|
"\n\n",
|
|
module, dlerror());
|
|
return NULL;
|
|
}
|
|
|
|
void *sym = dlsym(dlhandle, MXS_MODULE_SYMBOL_NAME);
|
|
|
|
if (sym == NULL)
|
|
{
|
|
MXS_ERROR("Expected entry point interface missing "
|
|
"from module: %s\n\t\t\t %s.",
|
|
module, dlerror());
|
|
dlclose(dlhandle);
|
|
return NULL;
|
|
}
|
|
|
|
void *(*entry_point)() = (void *(*)())sym;
|
|
MXS_MODULE *mod_info = (MXS_MODULE*)entry_point();
|
|
|
|
if (!check_module(mod_info, type, module) ||
|
|
(mod = register_module(module, type, dlhandle, mod_info)) == NULL)
|
|
{
|
|
dlclose(dlhandle);
|
|
return NULL;
|
|
}
|
|
|
|
MXS_NOTICE("Loaded module %s: %s from %s", module, mod_info->version, fname);
|
|
}
|
|
|
|
return mod->modobj;
|
|
}
|
|
|
|
void unload_module(const char *module)
|
|
{
|
|
LOADED_MODULE *mod = find_module(module);
|
|
|
|
if (mod)
|
|
{
|
|
void *handle = mod->handle;
|
|
unregister_module(module);
|
|
dlclose(handle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find a module that has been previously loaded and return the handle for that
|
|
* library
|
|
*
|
|
* @param module The name of the module
|
|
* @return The module handle or NULL if it was not found
|
|
*/
|
|
static LOADED_MODULE *
|
|
find_module(const char *module)
|
|
{
|
|
LOADED_MODULE *mod = registered;
|
|
|
|
if (module)
|
|
{
|
|
while (mod)
|
|
{
|
|
if (strcmp(mod->module, module) == 0)
|
|
{
|
|
return mod;
|
|
}
|
|
else
|
|
{
|
|
mod = mod->next;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Register a newly loaded module. The registration allows for single copies
|
|
* to be loaded and cached entry point information to be return.
|
|
*
|
|
* @param module The name of the module loaded
|
|
* @param type The type of the module loaded
|
|
* @param dlhandle The handle returned by dlopen
|
|
* @param version The version string returned by the module
|
|
* @param modobj The module object
|
|
* @param mod_info The module information
|
|
* @return The new registered module or NULL on memory allocation failure
|
|
*/
|
|
static LOADED_MODULE* register_module(const char *module,
|
|
const char *type,
|
|
void *dlhandle,
|
|
MXS_MODULE *mod_info)
|
|
{
|
|
module = MXS_STRDUP(module);
|
|
type = MXS_STRDUP(type);
|
|
char *version = MXS_STRDUP(mod_info->version);
|
|
|
|
LOADED_MODULE *mod = (LOADED_MODULE *)MXS_MALLOC(sizeof(LOADED_MODULE));
|
|
|
|
if (!module || !type || !version || !mod)
|
|
{
|
|
MXS_FREE((void*)module);
|
|
MXS_FREE((void*)type);
|
|
MXS_FREE(version);
|
|
MXS_FREE(mod);
|
|
return NULL;
|
|
}
|
|
|
|
mod->module = (char*)module;
|
|
mod->type = (char*)type;
|
|
mod->handle = dlhandle;
|
|
mod->version = version;
|
|
mod->modobj = mod_info->module_object;
|
|
mod->next = registered;
|
|
mod->info = mod_info;
|
|
registered = mod;
|
|
return mod;
|
|
}
|
|
|
|
/**
|
|
* Unregister a module
|
|
*
|
|
* @param module The name of the module to remove
|
|
*/
|
|
static void
|
|
unregister_module(const char *module)
|
|
{
|
|
LOADED_MODULE *mod = find_module(module);
|
|
LOADED_MODULE *ptr;
|
|
|
|
if (!mod)
|
|
{
|
|
return; // Module not found
|
|
}
|
|
if (registered == mod)
|
|
{
|
|
registered = mod->next;
|
|
}
|
|
else
|
|
{
|
|
ptr = registered;
|
|
while (ptr && ptr->next != mod)
|
|
{
|
|
ptr = ptr->next;
|
|
}
|
|
|
|
/*<
|
|
* Remove the module to be be freed from the list.
|
|
*/
|
|
if (ptr && (ptr->next == mod))
|
|
{
|
|
ptr->next = ptr->next->next;
|
|
}
|
|
}
|
|
|
|
/*<
|
|
* The module is now not in the linked list and all
|
|
* memory related to it can be freed
|
|
*/
|
|
dlclose(mod->handle);
|
|
MXS_FREE(mod->module);
|
|
MXS_FREE(mod->type);
|
|
MXS_FREE(mod->version);
|
|
MXS_FREE(mod);
|
|
}
|
|
|
|
void unload_all_modules()
|
|
{
|
|
while (registered)
|
|
{
|
|
unregister_module(registered->module);
|
|
}
|
|
}
|
|
|
|
void printModules()
|
|
{
|
|
LOADED_MODULE *ptr = registered;
|
|
|
|
printf("%-15s | %-11s | Version\n", "Module Name", "Module Type");
|
|
printf("-----------------------------------------------------\n");
|
|
while (ptr)
|
|
{
|
|
printf("%-15s | %-11s | %s\n", ptr->module, ptr->type, ptr->version);
|
|
ptr = ptr->next;
|
|
}
|
|
}
|
|
|
|
void dprintAllModules(DCB *dcb)
|
|
{
|
|
LOADED_MODULE *ptr = registered;
|
|
|
|
dcb_printf(dcb, "Modules.\n");
|
|
dcb_printf(dcb, "----------------+-----------------+---------+-------+-------------------------\n");
|
|
dcb_printf(dcb, "%-15s | %-15s | Version | API | Status\n", "Module Name", "Module Type");
|
|
dcb_printf(dcb, "----------------+-----------------+---------+-------+-------------------------\n");
|
|
while (ptr)
|
|
{
|
|
dcb_printf(dcb, "%-15s | %-15s | %-7s ", ptr->module, ptr->type, ptr->version);
|
|
if (ptr->info)
|
|
dcb_printf(dcb, "| %d.%d.%d | %s",
|
|
ptr->info->api_version.major,
|
|
ptr->info->api_version.minor,
|
|
ptr->info->api_version.patch,
|
|
ptr->info->status == MXS_MODULE_IN_DEVELOPMENT
|
|
? "In Development"
|
|
: (ptr->info->status == MXS_MODULE_ALPHA_RELEASE
|
|
? "Alpha"
|
|
: (ptr->info->status == MXS_MODULE_BETA_RELEASE
|
|
? "Beta"
|
|
: (ptr->info->status == MXS_MODULE_GA
|
|
? "GA"
|
|
: (ptr->info->status == MXS_MODULE_EXPERIMENTAL
|
|
? "Experimental" : "Unknown")))));
|
|
dcb_printf(dcb, "\n");
|
|
ptr = ptr->next;
|
|
}
|
|
dcb_printf(dcb, "----------------+-----------------+---------+-------+-------------------------\n\n");
|
|
}
|
|
|
|
struct cb_param
|
|
{
|
|
json_t* commands;
|
|
const char* domain;
|
|
const char* host;
|
|
};
|
|
|
|
bool modulecmd_cb(const MODULECMD *cmd, void *data)
|
|
{
|
|
cb_param* d = static_cast<cb_param*>(data);
|
|
|
|
json_t* obj = json_object();
|
|
json_object_set_new(obj, CN_ID, json_string(cmd->identifier));
|
|
json_object_set_new(obj, CN_TYPE, json_string(CN_MODULE_COMMAND));
|
|
|
|
json_t* attr = json_object();
|
|
const char* method = MODULECMD_MODIFIES_DATA(cmd) ? "POST" : "GET";
|
|
json_object_set_new(attr, CN_METHOD, json_string(method));
|
|
json_object_set_new(attr, CN_ARG_MIN, json_integer(cmd->arg_count_min));
|
|
json_object_set_new(attr, CN_ARG_MAX, json_integer(cmd->arg_count_max));
|
|
json_object_set_new(attr, CN_DESCRIPTION, json_string(cmd->description));
|
|
|
|
json_t* param = json_array();
|
|
|
|
for (int i = 0; i < cmd->arg_count_max; i++)
|
|
{
|
|
json_t* p = json_object();
|
|
json_object_set_new(p, CN_DESCRIPTION, json_string(cmd->arg_types[i].description));
|
|
json_object_set_new(p, CN_TYPE, json_string(modulecmd_argtype_to_str(&cmd->arg_types[i])));
|
|
json_object_set_new(p, CN_REQUIRED, json_boolean(MODULECMD_ARG_IS_REQUIRED(&cmd->arg_types[i])));
|
|
json_array_append_new(param, p);
|
|
}
|
|
|
|
std::string s = d->domain;
|
|
s += "/";
|
|
s += cmd->identifier;
|
|
ss_dassert(strcmp(d->domain, cmd->domain) == 0);
|
|
|
|
json_object_set_new(obj, CN_LINKS, mxs_json_self_link(d->host, CN_MODULES, s.c_str()));
|
|
json_object_set_new(attr, CN_PARAMETERS, param);
|
|
json_object_set_new(obj, CN_ATTRIBUTES, attr);
|
|
|
|
json_array_append_new(d->commands, obj);
|
|
|
|
return true;
|
|
}
|
|
|
|
static json_t* module_json_data(const LOADED_MODULE *mod, const char* host)
|
|
{
|
|
json_t* obj = json_object();
|
|
|
|
json_object_set_new(obj, CN_ID, json_string(mod->module));
|
|
json_object_set_new(obj, CN_TYPE, json_string(CN_MODULE));
|
|
|
|
json_t* attr = json_object();
|
|
json_object_set_new(attr, "module_type", json_string(mod->type));
|
|
json_object_set_new(attr, "version", json_string(mod->info->version));
|
|
json_object_set_new(attr, CN_DESCRIPTION, json_string(mod->info->description));
|
|
json_object_set_new(attr, "api", json_string(mxs_module_api_to_string(mod->info->modapi)));
|
|
json_object_set_new(attr, "maturity", json_string(mxs_module_status_to_string(mod->info->status)));
|
|
|
|
json_t* commands = json_array();
|
|
cb_param p = {commands, mod->module, host};
|
|
modulecmd_foreach(mod->module, NULL, modulecmd_cb, &p);
|
|
|
|
json_t* params = json_array();
|
|
|
|
for (int i = 0; mod->info->parameters[i].name; i++)
|
|
{
|
|
json_t* p = json_object();
|
|
|
|
json_object_set_new(p, CN_NAME, json_string(mod->info->parameters[i].name));
|
|
json_object_set_new(p, CN_TYPE, json_string(mxs_module_param_type_to_string(mod->info->parameters[i].type)));
|
|
|
|
if (mod->info->parameters[i].default_value)
|
|
{
|
|
json_object_set(p, "default_value", json_string(mod->info->parameters[i].default_value));
|
|
}
|
|
|
|
if (mod->info->parameters[i].type == MXS_MODULE_PARAM_ENUM &&
|
|
mod->info->parameters[i].accepted_values)
|
|
{
|
|
json_t* arr = json_array();
|
|
|
|
for (int x = 0; mod->info->parameters[i].accepted_values[x].name; x++)
|
|
{
|
|
json_array_append_new(arr, json_string(mod->info->parameters[i].accepted_values[x].name));
|
|
}
|
|
|
|
json_object_set_new(p, "enum_values", arr);
|
|
}
|
|
|
|
json_array_append_new(params, p);
|
|
}
|
|
|
|
json_object_set_new(attr, "commands", commands);
|
|
json_object_set_new(attr, CN_PARAMETERS, params);
|
|
json_object_set_new(obj, CN_ATTRIBUTES, attr);
|
|
json_object_set_new(obj, CN_LINKS, mxs_json_self_link(host, CN_MODULES, mod->module));
|
|
|
|
return obj;
|
|
}
|
|
|
|
json_t* module_to_json(const MXS_MODULE* module, const char* host)
|
|
{
|
|
json_t* data = NULL;
|
|
|
|
for (LOADED_MODULE *ptr = registered; ptr; ptr = ptr->next)
|
|
{
|
|
if (ptr->info == module)
|
|
{
|
|
data = module_json_data(ptr, host);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This should always be non-NULL
|
|
ss_dassert(data);
|
|
|
|
return mxs_json_resource(host, MXS_JSON_API_MODULES, data);
|
|
}
|
|
|
|
json_t* module_list_to_json(const char* host)
|
|
{
|
|
json_t* arr = json_array();
|
|
|
|
for (LOADED_MODULE *ptr = registered; ptr; ptr = ptr->next)
|
|
{
|
|
json_array_append_new(arr, module_json_data(ptr, host));
|
|
}
|
|
|
|
return mxs_json_resource(host, MXS_JSON_API_MODULES, arr);
|
|
}
|
|
|
|
/**
|
|
* Provide a row to the result set that defines the set of modules
|
|
*
|
|
* @param set The result set
|
|
* @param data The index of the row to send
|
|
* @return The next row or NULL
|
|
*/
|
|
static RESULT_ROW *
|
|
moduleRowCallback(RESULTSET *set, void *data)
|
|
{
|
|
int *rowno = (int *)data;
|
|
int i = 0;;
|
|
char *stat, buf[20];
|
|
RESULT_ROW *row;
|
|
LOADED_MODULE *ptr;
|
|
|
|
ptr = registered;
|
|
while (i < *rowno && ptr)
|
|
{
|
|
i++;
|
|
ptr = ptr->next;
|
|
}
|
|
if (ptr == NULL)
|
|
{
|
|
MXS_FREE(data);
|
|
return NULL;
|
|
}
|
|
(*rowno)++;
|
|
row = resultset_make_row(set);
|
|
resultset_row_set(row, 0, ptr->module);
|
|
resultset_row_set(row, 1, ptr->type);
|
|
resultset_row_set(row, 2, ptr->version);
|
|
snprintf(buf, 19, "%d.%d.%d", ptr->info->api_version.major,
|
|
ptr->info->api_version.minor,
|
|
ptr->info->api_version.patch);
|
|
buf[19] = '\0';
|
|
resultset_row_set(row, 3, buf);
|
|
resultset_row_set(row, 4, ptr->info->status == MXS_MODULE_IN_DEVELOPMENT
|
|
? "In Development"
|
|
: (ptr->info->status == MXS_MODULE_ALPHA_RELEASE
|
|
? "Alpha"
|
|
: (ptr->info->status == MXS_MODULE_BETA_RELEASE
|
|
? "Beta"
|
|
: (ptr->info->status == MXS_MODULE_GA
|
|
? "GA"
|
|
: (ptr->info->status == MXS_MODULE_EXPERIMENTAL
|
|
? "Experimental" : "Unknown")))));
|
|
return row;
|
|
}
|
|
|
|
RESULTSET *moduleGetList()
|
|
{
|
|
RESULTSET *set;
|
|
int *data;
|
|
|
|
if ((data = (int *)MXS_MALLOC(sizeof(int))) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
*data = 0;
|
|
if ((set = resultset_create(moduleRowCallback, data)) == NULL)
|
|
{
|
|
MXS_FREE(data);
|
|
return NULL;
|
|
}
|
|
resultset_add_column(set, "Module Name", 18, COL_TYPE_VARCHAR);
|
|
resultset_add_column(set, "Module Type", 12, COL_TYPE_VARCHAR);
|
|
resultset_add_column(set, "Version", 10, COL_TYPE_VARCHAR);
|
|
resultset_add_column(set, "API Version", 8, COL_TYPE_VARCHAR);
|
|
resultset_add_column(set, "Status", 15, COL_TYPE_VARCHAR);
|
|
|
|
return set;
|
|
}
|
|
|
|
const MXS_MODULE *get_module(const char *name, const char *type)
|
|
{
|
|
LOADED_MODULE *mod = find_module(name);
|
|
|
|
if (mod == NULL && type && load_module(name, type))
|
|
{
|
|
mod = find_module(name);
|
|
}
|
|
|
|
return mod ? mod->info : NULL;
|
|
}
|
|
|
|
MXS_MODULE_ITERATOR mxs_module_iterator_get(const char* type)
|
|
{
|
|
LOADED_MODULE* module = registered;
|
|
|
|
while (module && type && (strcmp(module->type, type) != 0))
|
|
{
|
|
module = module->next;
|
|
}
|
|
|
|
MXS_MODULE_ITERATOR iterator;
|
|
iterator.type = type;
|
|
iterator.position = module;
|
|
|
|
return iterator;
|
|
}
|
|
|
|
bool mxs_module_iterator_has_next(const MXS_MODULE_ITERATOR* iterator)
|
|
{
|
|
return iterator->position != NULL;
|
|
}
|
|
|
|
MXS_MODULE* mxs_module_iterator_get_next(MXS_MODULE_ITERATOR* iterator)
|
|
{
|
|
MXS_MODULE* module = NULL;
|
|
LOADED_MODULE* loaded_module = (LOADED_MODULE*)iterator->position;
|
|
|
|
if (loaded_module)
|
|
{
|
|
module = loaded_module->info;
|
|
|
|
do
|
|
{
|
|
loaded_module = loaded_module->next;
|
|
}
|
|
while (loaded_module && iterator->type && (strcmp(loaded_module->type, iterator->type) != 0));
|
|
|
|
iterator->position = loaded_module;
|
|
}
|
|
|
|
return module;
|
|
}
|