diff --git a/CMakeLists.txt b/CMakeLists.txt index 69f9539ec..590c8f07d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,7 +219,7 @@ if(WITH_MAXSCALE_CNF AND (NOT TARGET_COMPONENT OR "core" STREQUAL "${TARGET_COMP endif() install_file(${CMAKE_SOURCE_DIR}/COPYRIGHT core) -install_file(${CMAKE_SOURCE_DIR}/README core) +install_file(${CMAKE_SOURCE_DIR}/README.md core) install_file(${CMAKE_SOURCE_DIR}/LICENSE.TXT core) install_file(${CMAKE_SOURCE_DIR}/LICENSE-THIRDPARTY.TXT core) install_file(etc/lsyncd_example.conf core) diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index 0b2fe2a43..5e7b41084 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -32,6 +32,7 @@ - [Routing Hints](Reference/Hint-Syntax.md) - [MaxBinlogCheck](Reference/MaxBinlogCheck.md) - [MaxScale REST API](REST-API/API.md) + - [Module Commands](Reference/Module-Commands.md) ## Tutorials diff --git a/Documentation/Filters/Database-Firewall-Filter.md b/Documentation/Filters/Database-Firewall-Filter.md index 4301ffacd..615109fe2 100644 --- a/Documentation/Filters/Database-Firewall-Filter.md +++ b/Documentation/Filters/Database-Firewall-Filter.md @@ -161,12 +161,29 @@ The `users` directive defines the users to which the rule should be applied. The first keyword is `users`, which identifies this line as a user definition line. -The second component is a list of user names and network addresses in the format *`user`*`@`*`0.0.0.0`*. The first part is the user name and the second part is the network address. You can use the `%` character as the wildcard to enable user name matching from any address or network matching for all users. After the list of users and networks the keyword match is expected. +The second component is a list of user names and network addresses in the format *`user`*`@`*`0.0.0.0`*. The first part is the user name and the second part is the network address. You can use the `%` character as the wildcard to enable user name matching from any address or network matching for all users. After the list of users and networks the keyword match is expected. After this either the keyword `any` `all` or `strict_all` is expected. This defined how the rules are matched. If `any` is used when the first rule is matched the query is considered blocked and the rest of the rules are skipped. If instead the `all` keyword is used all rules must match for the query to be blocked. The `strict_all` is the same as `all` but it checks the rules from left to right in the order they were listed. If one of these does not match, the rest of the rules are not checked. This could be useful in situations where you would for example combine `limit_queries` and `regex` rules. By using `strict_all` you can have the `regex` rule first and the `limit_queries` rule second. This way the rule only matches if the `regex` rule matches enough times for the `limit_queries` rule to match. After the matching part comes the rules keyword after which a list of rule names is expected. This allows reusing of the rules and enables varying levels of query restriction. +## Module commands + +Read [Module Commands](../Reference/Module-Commands.md) documentation for details about module commands. + +The dbfwfilter supports the following module commands. + +### `dbfwfilter::rules/reload [FILE]` + +Load a new rule file or reload the current rules. New rules are only taken into +use if they are successfully loaded and in cases where loading of the rules +fail, the old rules remain in use. The _FILE_ argument is an optional path to a +rule file and if it is not defined, the current rule file is used. + +### `dbfwfilter::rules` + +Shows the current statistics of the rules. + ## Use Cases ### Use Case 1 - Prevent rapid execution of specific queries diff --git a/Documentation/Reference/Module-Commands.md b/Documentation/Reference/Module-Commands.md new file mode 100644 index 000000000..2e4f177e7 --- /dev/null +++ b/Documentation/Reference/Module-Commands.md @@ -0,0 +1,65 @@ +# Module commands + +Introduced in MaxScale 2.1, the module commands are special, module-specific +commands. They allow the modules to expand beyond the capabilities of the +module API. Currently, only MaxAdmin implements an interface to the module +commands. + +All registered module commands can be shown with `maxadmin list functions` and +they can be executed with `maxadmin call function ARGS...` where +__ is the domain where the module registered the function and __ +is the name of the function. _ARGS_ is a function specific list of arguments. + +## Developer reference + +The module command API is defined in the _modulecmd.h_ header. It consists of +various functions to register and call module commands. Read the function +documentation in the header for more details. + +The following example registers the module command _my_command_ in the _my_module_ domain. + +``` +#include + +bool my_simple_cmd(const MODULECMD_ARG *argv) +{ + printf("%d arguments given\n", argv->argc); +} + +int main(int argc, char **argv) +{ + modulecmd_arg_type_t my_args[] = + { + {MODULECMD_ARG_BOOLEAN, "This is a boolean parameter"}, + {MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, "This is an optional string parameter"} + }; + + // Register the command + modulecmd_register_command("my_module", "my_command", my_simple_cmd, 2, my_args); + + // Find the registered command + const MODULECMD *cmd = modulecmd_find_command("my_module", "my_command"); + + // Parse the arguments for the command + const void *arglist[] = {"true", "optional string"}; + MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, arglist, 2); + + // Call the module command + modulecmd_call_command(cmd, arg); + + // Free the parsed arguments + modulecmd_arg_free(arg); + return 0; +} +``` + +The array of _modulecmd_arg_type_t_ type is used to tell what kinds of arguments +the command expects. The first argument is a SERVER which will be replaced with a +pointer to a server. The second argument is an optional string argument. + +Arguments are passed to the parsing function as an array of void pointers. They +are interpreted as the types the command expects. + +When the module command is executed, the _argv_ parameter for the +_my_simple_cmd_ contains the parsed arguments received from the caller of the +command. diff --git a/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md index e50c25a00..188c9457d 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md @@ -27,6 +27,7 @@ To cater for this situation there is now a `set server stale` command. [Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/browse/MXS-976?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.0.2) * [MXS-1018](https://jira.mariadb.org/browse/MXS-1018): Internal connections don't use TLS +* [MXS-1008](https://jira.mariadb.org/browse/MXS-1008): MySQL Monitor with scripts leaks memory * [MXS-976](https://jira.mariadb.org/browse/MXS-976): Crash in libqc_sqlite * [MXS-975](https://jira.mariadb.org/browse/MXS-975): TCP backlog is capped at 1280 * [MXS-970](https://jira.mariadb.org/browse/MXS-970): A fatal problem with maxscale automatically shut down @@ -35,6 +36,7 @@ To cater for this situation there is now a `set server stale` command. * [MXS-965](https://jira.mariadb.org/browse/MXS-965): galeramon erlaubt keine TLS verschlüsselte Verbindung * [MXS-960](https://jira.mariadb.org/browse/MXS-960): MaxScale Binlog Server does not allow comma to be in password * [MXS-957](https://jira.mariadb.org/browse/MXS-957): Temporary table creation from another temporary table isn't detected +* [MXS-956](https://jira.mariadb.org/browse/MXS-956): Removing DCB 0x7fbf94016760 but was in state DCB_STATE_DISCONNECTED which is not legal for a call to dcb_close * [MXS-955](https://jira.mariadb.org/browse/MXS-955): MaxScale 2.0.1 doesn't recognize user and passwd options in .maxadmin file * [MXS-953](https://jira.mariadb.org/browse/MXS-953): Charset error when server configued in utf8mb4 * [MXS-942](https://jira.mariadb.org/browse/MXS-942): describe table query not routed to shard that contains the schema diff --git a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md index 907b549bf..6d77070ed 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md @@ -104,6 +104,22 @@ following new commands were added to maxadmin, see output of `maxadmin help With these new features, you can start MaxScale without the servers and define them later. +# Module commands + +## Module commands + +Introduced in MaxScale 2.1, the module commands are special, module-specific +commands. They allow the modules to expand beyound the capabilities of the +module API. Currently, only MaxAdmin implements an interface to the module +commands. + +All registered module commands can be shown with `maxadmin list functions` and +they can be executed with `maxadmin call function ARGS...` where +__ is the domain where the module registered the function and __ +is the name of the function. _ARGS_ is a function specific list of arguments. + +Read [Module Commands](../Reference/Module-Commands.md) documentation for more details. + ### Amazon RDS Aurora monitor The new [Aurora Monitor](../Monitors/Aurora-Monitor.md) module allows monitoring diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 0e7ae0f51..000000000 --- a/client/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# binaries generated here -maxadmin diff --git a/include/maxscale/buffer.h b/include/maxscale/buffer.h index 3e9995faf..d303ce5d9 100644 --- a/include/maxscale/buffer.h +++ b/include/maxscale/buffer.h @@ -151,7 +151,7 @@ typedef struct gwbuf * Macros to access the data in the buffers */ /*< First valid, unconsumed byte in the buffer */ -#define GWBUF_DATA(b) ((b)->start) +#define GWBUF_DATA(b) ((uint8_t*)(b)->start) /*< Number of bytes in the individual buffer */ #define GWBUF_LENGTH(b) ((char *)(b)->end - (char *)(b)->start) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h new file mode 100644 index 000000000..06ae263a8 --- /dev/null +++ b/include/maxscale/config_runtime.h @@ -0,0 +1,159 @@ +#pragma once +/* + * 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/bsl. + * + * Change Date: 2019-07-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 config_runtime.h - Functions for runtime configuration modifications + */ + +#include + +#include +#include +#include +#include + +/** + * @brief Create a new server + * + * This function creates a new, persistent server by first allocating a new + * server and then storing the resulting configuration file on disk. This + * function should be used only from administrative interface modules and internal + * modules should use server_alloc() instead. + * + * @param name Server name + * @param address Network address + * @param port Network port + * @param protocol Protocol module name + * @param authenticator Authenticator module name + * @param options Options for the authenticator module + * @return True on success, false if an error occurred + */ +bool runtime_create_server(const char *name, const char *address, + const char *port, const char *protocol, + const char *authenticator, const char *options); + +/** + * @brief Destroy a server + * + * This removes any created server configuration files and marks the server removed + * If the server is not in use. + * + * @param server Server to destroy + * @return True if server was destroyed + */ +bool runtime_destroy_server(SERVER *server); + +/** + * @brief Link a server to an object + * + * This function links the server to another object. The target can be either + * a monitor or a service. + * + * @param server Server to link + * @param target The monitor or service where the server is added + * @return True if the object was found and the server was linked to it, false + * if no object matching @c target was found + */ +bool runtime_link_server(SERVER *server, const char *target); + +/** + * @brief Unlink a server from an object + * + * This function unlinks the server from another object. The target can be either + * a monitor or a service. + * + * @param server Server to unlink + * @param target The monitor or service from which the server is removed + * @return True if the object was found and the server was unlinked from it, false + * if no object matching @c target was found + */ +bool runtime_unlink_server(SERVER *server, const char *target); + +/** + * @brief Alter server parameters + * + * @param server Server to alter + * @param key Key to modify + * @param value New value + * @return True if @c key was one of the supported parameters + */ +bool runtime_alter_server(SERVER *server, char *key, char *value); + +/** + * @brief Enable SSL for a server + * + * The @c key , @c cert and @c ca parameters are required. @c version and @c depth + * are optional. + * + * @note SSL cannot be disabled at runtime. + * + * @param server Server to configure + * @param key Path to SSL private key + * @param cert Path to SSL public certificate + * @param ca Path to certificate authority + * @param version Required SSL Version + * @param depth Certificate verification depth + * @return True if SSL was successfully enabled + */ +bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, + const char *ca, const char *version, const char *depth); + +/** + * @brief Alter monitor parameters + * + * @param monitor Monitor to alter + * @param key Key to modify + * @param value New value + * @return True if @c key was one of the supported parameters + */ +bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value); + +/** + * @brief Create a new listener for a service + * + * This function adds a new listener to a service and starts it. + * + * @param service Service where the listener is added + * @param name Name of the listener + * @param addr Listening address, NULL for default of 0.0.0.0 + * @param port Listening port, NULL for default of 3306 + * @param proto Listener protocol, NULL for default of "MySQLClient" + * @param auth Listener authenticator, NULL for protocol default authenticator + * @param auth_opt Options for the authenticator, NULL for no options + * @param ssl_key SSL key, NULL for no key + * @param ssl_cert SSL cert, NULL for no cert + * @param ssl_ca SSL CA cert, NULL for no CA cert + * @param ssl_version SSL version, NULL for default of "MAX" + * @param ssl_depth SSL cert verification depth, NULL for default + * + * @return True if the listener was successfully created and started + */ +bool runtime_create_listener(SERVICE *service, const char *name, const char *addr, + const char *port, const char *proto, const char *auth, + const char *auth_opt, const char *ssl_key, + const char *ssl_cert, const char *ssl_ca, + const char *ssl_version, const char *ssl_depth); + +/** + * @brief Destroy a listener + * + * This disables the listener by removing it from the polling system. It also + * removes any generated configurations for this listener. + * + * @param service Service where the listener exists + * @param name Name of the listener + * + * @return True if the listener was successfully destroyed + */ +bool runtime_destroy_listener(SERVICE *service, const char *name); diff --git a/include/maxscale/gw_ssl.h b/include/maxscale/gw_ssl.h index d15733e79..db6be67c1 100644 --- a/include/maxscale/gw_ssl.h +++ b/include/maxscale/gw_ssl.h @@ -71,6 +71,7 @@ typedef struct ssl_listener char *ssl_key; /*< SSL private key */ char *ssl_ca_cert; /*< SSL CA certificate */ bool ssl_init_done; /*< If SSL has already been initialized for this service */ + struct ssl_listener *next; /*< Next SSL configuration, currently used to store obsolete configurations */ } SSL_LISTENER; int ssl_authenticate_client(struct dcb *dcb, bool is_capable); diff --git a/include/maxscale/listener.h b/include/maxscale/listener.h index 0df0f5d8d..f40a7b310 100644 --- a/include/maxscale/listener.h +++ b/include/maxscale/listener.h @@ -49,6 +49,7 @@ typedef struct servlistener unsigned short port; /**< Port to listen on */ char *address; /**< Address to listen with */ char *authenticator; /**< Name of authenticator */ + char *auth_options; /**< Authenticator options */ void *auth_instance; /**< Authenticator instance created in GWAUTHENTICATOR::initialize() */ SSL_LISTENER *ssl; /**< Structure of SSL data or NULL */ struct dcb *listener; /**< The DCB for the listener */ @@ -59,9 +60,21 @@ typedef struct servlistener struct servlistener *next; /**< Next service protocol */ } SERV_LISTENER; -SERV_LISTENER *listener_alloc(struct service* service, char *name, char *protocol, - char *address, unsigned short port, char *authenticator, - char* options, SSL_LISTENER *ssl); +/** + * @brief Serialize a listener to a file + * + * This converts @c listener into an INI format file. This allows created listeners + * to be persisted to disk. This will replace any existing files with the same + * name. + * + * @param listener Listener to serialize + * @return True if the serialization of the listener was successful, false if it fails + */ +bool listener_serialize(const SERV_LISTENER *listener); + +SERV_LISTENER* listener_alloc(struct service* service, const char* name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char* auth_options, SSL_LISTENER *ssl); void listener_free(SERV_LISTENER* listener); int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version); void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert); diff --git a/include/maxscale/modinfo.h b/include/maxscale/modinfo.h index 09e413bd0..151f460b3 100644 --- a/include/maxscale/modinfo.h +++ b/include/maxscale/modinfo.h @@ -81,10 +81,10 @@ typedef struct */ typedef struct { - MODULE_API modapi; + MODULE_API modapi; MODULE_STATUS status; MODULE_VERSION api_version; - char *description; + const char *description; } MODULE_INFO; MXS_END_DECLS diff --git a/include/maxscale/monitor.h b/include/maxscale/monitor.h index 46a9df814..5c458ce46 100644 --- a/include/maxscale/monitor.h +++ b/include/maxscale/monitor.h @@ -203,7 +203,7 @@ struct monitor extern MONITOR *monitor_alloc(char *, char *); extern void monitor_free(MONITOR *); -extern MONITOR *monitor_find(char *); +extern MONITOR *monitor_find(const char *); extern void monitorAddServer(MONITOR *mon, SERVER *server); extern void monitorRemoveServer(MONITOR *mon, SERVER *server); extern void monitorAddUser(MONITOR *, char *, char *); diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 49c175615..01fdd1615 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -289,30 +289,46 @@ typedef struct #define MYSQL_REPLY_OK 0x00 #define MYSQL_REPLY_AUTHSWITCHREQUEST 0xfe -/* - * Let's try this with proper enums instead of numbers -#define MYSQL_GET_COMMAND(payload) (payload[4]) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) -#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) -#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) -#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) -#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02) -#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11) -#define MYSQL_GET_NATTR(payload) ((int)payload[4]) -*/ -#define MYSQL_GET_COMMAND(payload) ((mysql_server_cmd_t)((payload)[4])) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) +static inline mysql_server_cmd_t MYSQL_GET_COMMAND(const uint8_t* header) +{ + return (mysql_server_cmd_t)header[4]; +} + +static inline uint8_t MYSQL_GET_PACKET_NO(const uint8_t* header) +{ + return header[3]; +} + +static inline uint32_t MYSQL_GET_PACKET_LEN(const uint8_t* header) +{ + return gw_mysql_get_byte3(header); +} + #define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) #define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9])) #define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) -#define MYSQL_IS_ERROR_PACKET(payload) ((int)MYSQL_GET_COMMAND(payload)==MYSQL_REPLY_ERR) -#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_QUIT) -#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_INIT_DB) -#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_CHANGE_USER) #define MYSQL_GET_NATTR(payload) ((int)payload[4]) +static inline bool MYSQL_IS_ERROR_PACKET(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_REPLY_ERR; +} + +static inline bool MYSQL_IS_COM_QUIT(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_QUIT; +} + +static inline bool MYSQL_IS_COM_INIT_DB(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_INIT_DB; +} + +static inline bool MYSQL_IS_CHANGE_USER(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_CHANGE_USER; +} + /* The following can be compared using memcmp to detect a null password */ extern uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]; @@ -378,7 +394,7 @@ bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer); mxs_auth_state_t gw_send_backend_auth(DCB *dcb); /** Write an OK packet to a DCB */ -int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* message); +int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* message); /** Check for OK packet */ bool mxs_mysql_is_ok_packet(GWBUF *buffer); diff --git a/include/maxscale/server.h b/include/maxscale/server.h index cfde36a10..dbbd9d7e4 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -227,25 +227,20 @@ extern SERVER* server_alloc(const char *name, const char *address, unsigned shor const char *auth_options); /** - * @brief Create a new server + * @brief Find a server that can be reused * - * This function creates a new, persistent server by first allocating a new - * server and then storing the resulting configuration file on disk. This - * function should be used only from administrative interface modules and internal - * modules should use server_alloc() instead. + * A server that has been destroyed will not be deleted but only deactivated. * - * @param name Server name - * @param address Network address - * @param port Network port - * @param protocol Protocol module name - * @param authenticator Authenticator module name - * @param options Options for the authenticator module - * @return True on success, false if an error occurred + * @param name Name of the server + * @param protocol Protocol used by the server + * @param authenticator The authenticator module of the server + * @param auth_options Options for the authenticator + * @return Reusable SERVER or NULL if no servers matching the criteria were + * found + * @see runtime_create_server */ -extern bool server_create(const char *name, const char *address, const char *port, - const char *protocol, const char *authenticator, - const char *options); - +SERVER* server_find_destroyed(const char *name, const char *protocol, + const char *authenticator, const char *auth_options); /** * @brief Serialize a server to a file * @@ -258,16 +253,6 @@ extern bool server_create(const char *name, const char *address, const char *por */ bool server_serialize(const SERVER *server); -/** - * @brief Destroy a server - * - * This removes any created server configuration files and marks the server removed - * If the server is not in use. - * @param server Server to destroy - * @return True if server was destroyed - */ -bool server_destroy(SERVER *server); - extern int server_free(SERVER *); extern SERVER *server_find_by_unique_name(const char *name); extern SERVER *server_find(char *, unsigned short); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 95ac954c6..9beb1b717 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -98,10 +98,10 @@ typedef struct typedef struct server_ref_t { struct server_ref_t *next; /**< Next server reference */ - SERVER* server; /**< The actual server */ - int weight; /**< Weight of this server */ - int connections; /**< Number of connections created through this reference */ - bool active; /**< Whether this reference is valid and in use*/ + SERVER* server; /**< The actual server */ + int weight; /**< Weight of this server */ + int connections; /**< Number of connections created through this reference */ + bool active; /**< Whether this reference is valid and in use*/ } SERVER_REF; /** Macro to check whether a SERVER_REF is active */ @@ -119,8 +119,8 @@ typedef struct server_ref_t #define SERVICE_PARAM_UNINIT -1 /* Refresh rate limits for load users from database */ -#define USERS_REFRESH_TIME 30 /* Allowed time interval (in seconds) after last update*/ -#define USERS_REFRESH_MAX_PER_TIME 4 /* Max number of load calls within the time interval */ +#define USERS_REFRESH_TIME 30 /* Allowed time interval (in seconds) after last update*/ +#define USERS_REFRESH_MAX_PER_TIME 4 /* Max number of load calls within the time interval */ /** Default timeout values used by the connections which fetch user authentication data */ #define DEFAULT_AUTH_CONNECT_TIMEOUT 3 @@ -142,25 +142,24 @@ typedef struct service int max_connections; /**< Maximum client connections */ QUEUE_CONFIG *queued_connections; /**< Queued connections, if set */ SERV_LISTENER *ports; /**< Linked list of ports and protocols - * that this service will listen on. - */ + * that this service will listen on */ char *routerModule; /**< Name of router module to use */ char **routerOptions; /**< Router specific option strings */ struct router_object *router; /**< The router we are using */ void *router_instance; /**< The router instance for this service */ - char *version_string; /** version string for this service listeners */ - SERVER_REF *dbref; /** server references */ - int n_dbref; /** Number of server references */ + char *version_string; /**< version string for this service listeners */ + SERVER_REF *dbref; /**< server references */ + int n_dbref; /**< Number of server references */ SERVICE_USER credentials; /**< The cedentials of the service user */ SPINLOCK spin; /**< The service spinlock */ SERVICE_STATS stats; /**< The service statistics */ int enable_root; /**< Allow root user access */ int localhost_match_wildcard_host; /**< Match localhost against wildcard */ - CONFIG_PARAMETER* svc_config_param;/*< list of config params and values */ - int svc_config_version; /*< Version number of configuration */ - bool svc_do_shutdown; /*< tells the service to exit loops etc. */ - bool users_from_all; /*< Load users from one server or all of them */ - bool strip_db_esc; /*< Remove the '\' characters from database names + CONFIG_PARAMETER* svc_config_param;/**< list of config params and values */ + int svc_config_version; /**< Version number of configuration */ + bool svc_do_shutdown; /**< tells the service to exit loops etc. */ + bool users_from_all; /**< Load users from one server or all of them */ + bool strip_db_esc; /**< Remove the '\' characters from database names * when querying them from the server. MySQL Workbench seems * to escape at least the underscore character. */ SPINLOCK users_table_spin; /**< The spinlock for users data refresh */ @@ -168,11 +167,11 @@ typedef struct service FILTER_DEF **filters; /**< Ordered list of filters */ int n_filters; /**< Number of filters */ long conn_idle_timeout; /**< Session timeout in seconds */ - char *weightby; + char *weightby; /**< Service weighting parameter name */ struct service *next; /**< The next service in the linked list */ - bool retry_start; /*< If starting of the service should be retried later */ - bool log_auth_warnings; /*< Log authentication failures and warnings */ - uint64_t capabilities; /*< The capabilities of the service. */ + bool retry_start; /**< If starting of the service should be retried later */ + bool log_auth_warnings; /**< Log authentication failures and warnings */ + uint64_t capabilities; /**< The capabilities of the service. */ } SERVICE; typedef enum count_spec_t @@ -188,61 +187,76 @@ typedef enum count_spec_t #define SERVICE_STATE_FAILED 3 /**< The service failed to start */ #define SERVICE_STATE_STOPPED 4 /**< The service has been stopped */ -extern SERVICE *service_alloc(const char *, const char *); -extern int service_free(SERVICE *); -extern SERVICE *service_find(char *); -extern int service_isvalid(SERVICE *); -extern int serviceAddProtocol(SERVICE *service, char *name, char *protocol, - char *address, unsigned short port, - char *authenticator, char *options, - SSL_LISTENER *ssl); -extern int serviceHasProtocol(SERVICE *service, const char *protocol, - const char* address, unsigned short port); -extern void serviceAddBackend(SERVICE *, SERVER *); -extern void serviceRemoveBackend(SERVICE *, const SERVER *); -extern bool serviceHasBackend(SERVICE *, SERVER *); -extern void serviceAddRouterOption(SERVICE *, char *); -extern void serviceClearRouterOptions(SERVICE *); -extern int serviceStart(SERVICE *); -extern int serviceStartAll(); -extern void serviceStartProtocol(SERVICE *, char *, int); -extern int serviceStop(SERVICE *); -extern int serviceRestart(SERVICE *); -extern int serviceSetUser(SERVICE *, char *, char *); -extern int serviceGetUser(SERVICE *, char **, char **); -extern bool serviceSetFilters(SERVICE *, char *); -extern int serviceSetSSL(SERVICE *service, char* action); -extern int serviceSetSSLVersion(SERVICE *service, char* version); -extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth); -extern void serviceSetCertificates(SERVICE *service, char* cert, char* key, char* ca_cert); -extern int serviceEnableRootUser(SERVICE *, int ); -extern int serviceSetTimeout(SERVICE *, int ); -extern int serviceSetConnectionLimits(SERVICE *, int, int, int); -extern void serviceSetRetryOnFailure(SERVICE *service, char* value); -extern void serviceWeightBy(SERVICE *, char *); -extern char *serviceGetWeightingParameter(SERVICE *); -extern int serviceEnableLocalhostMatchWildcardHost(SERVICE *, int); -extern int serviceStripDbEsc(SERVICE* service, int action); -extern int serviceAuthAllServers(SERVICE *service, int action); -extern void service_update(SERVICE *, char *, char *, char *); -extern int service_refresh_users(SERVICE *); -extern void printService(SERVICE *); -extern void printAllServices(); -extern void dprintAllServices(DCB *); -extern bool service_set_param_value(SERVICE* service, - CONFIG_PARAMETER* param, - char* valstr, - count_spec_t count_spec, - config_param_type_t type); -extern void dprintService(DCB *, SERVICE *); -extern void dListServices(DCB *); -extern void dListListeners(DCB *); -extern char* service_get_name(SERVICE* svc); -extern void service_shutdown(); -extern int serviceSessionCountAll(); -extern RESULTSET *serviceGetList(); -extern RESULTSET *serviceGetListenerList(); -extern bool service_all_services_have_listeners(); +/** + * Starting and stopping services + */ + +/** + * @brief Stop a service + * + * @param service Service to stop + * @return True if service was stopped + */ +bool serviceStop(SERVICE *service); + +/** + * @brief Restart a stopped service + * + * @param service Service to restart + * @return True if service was restarted + */ +bool serviceStart(SERVICE *service); + +/** + * @brief Start new a listener for a service + * + * @param service Service where the listener is linked + * @param port Listener to start + * @return True if listener was started + */ +bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port); + +/** + * @brief Stop a listener for a service + * + * @param service Service where the listener is linked + * @param name Name of the listener + * @return True if listener was stopped + */ +bool serviceStopListener(SERVICE *service, const char *name); + +/** + * Utility functions + */ +SERVICE* service_find(const char *name); + +// TODO: Change binlogrouter to use the functions in config_runtime.h +void serviceAddBackend(SERVICE *service, SERVER *server); + +int serviceGetUser(SERVICE *service, char **user, char **auth); +int serviceSetUser(SERVICE *service, char *user, char *auth); +bool serviceSetFilters(SERVICE *service, char *filters); +int serviceEnableRootUser(SERVICE *service, int action); +int serviceSetTimeout(SERVICE *service, int val); +int serviceSetConnectionLimits(SERVICE *service, int max, int queued, int timeout); +void serviceSetRetryOnFailure(SERVICE *service, char* value); +void serviceWeightBy(SERVICE *service, char *weightby); +char* serviceGetWeightingParameter(SERVICE *service); +int serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action); +int serviceStripDbEsc(SERVICE* service, int action); +int serviceAuthAllServers(SERVICE *service, int action); +int service_refresh_users(SERVICE *service); + +/** + * Diagnostics + */ +void dprintAllServices(DCB *dcb); +void dprintService(DCB *dcb, SERVICE *service); +void dListServices(DCB *dcb); +void dListListeners(DCB *dcb); +int serviceSessionCountAll(void); +RESULTSET* serviceGetList(void); +RESULTSET* serviceGetListenerList(void); /** * Get the capabilities of the servive. @@ -257,27 +271,4 @@ static inline uint64_t service_get_capabilities(const SERVICE *service) return service->capabilities; } -/** - * Check if a service uses @c servers - * @param server Server that is queried - * @return True if server is used by at least one service - */ -bool service_server_in_use(const SERVER *server); - -/** - * @brief Serialize a service to a file - * - * This partially converts @c service into an INI format file. Only the servers - * of the service are serialized. This allows the service to keep using the servers - * added at runtime even after a restart. - * - * NOTE: This does not persist the complete service configuration and requires - * that an existing service configuration is in the main configuration file. - * Changes to service parameters are not persisted. - * - * @param service Service to serialize - * @return False if the serialization of the service fails, true if it was successful - */ -bool service_serialize_servers(const SERVICE *service); - MXS_END_DECLS diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index e298181a1..c1d336d3e 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -58,6 +58,11 @@ #include #include #include +// assumes it is being compiled agains Connector-C, +// so we need to make certain Connector-C constants visible. +#define MYSQL_COM_QUIT COM_QUIT +#define MYSQL_COM_INIT_DB COM_INIT_DB +#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER #include #include diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 5e9b1eb42..a6f351c0d 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -444,25 +444,28 @@ static void parse_query_string(const char* query, size_t len) } } } - else + else if (!this_thread.info->initializing) // If we are initializing, the query will not be classified. { - if (qc_info_was_tokenized(this_thread.info->status)) + if (this_unit.log_level > QC_LOG_NOTHING) { - // This suggests a callback from the parser into this module is not made. - format = - "Statement was classified only based on keywords, " - "even though the statement was parsed: \"%.*s%s\""; + if (qc_info_was_tokenized(this_thread.info->status)) + { + // This suggests a callback from the parser into this module is not made. + format = + "Statement was classified only based on keywords, " + "even though the statement was parsed: \"%.*s%s\""; - MXS_WARNING(format, l, query, suffix); - } - else if (!qc_info_was_parsed(this_thread.info->status)) - { - // This suggests there are keywords that should be recognized but are not, - // a tentative classification cannot be (or is not) made using the keywords - // seen and/or a callback from the parser into this module is not made. - format = "Statement was parsed, but not classified: \"%.*s%s\""; + MXS_WARNING(format, l, query, suffix); + } + else if (!qc_info_was_parsed(this_thread.info->status)) + { + // This suggests there are keywords that should be recognized but are not, + // a tentative classification cannot be (or is not) made using the keywords + // seen and/or a callback from the parser into this module is not made. + format = "Statement was parsed, but not classified: \"%.*s%s\""; - MXS_ERROR(format, l, query, suffix); + MXS_WARNING(format, l, query, suffix); + } } } diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c b/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c index 2030e0e0c..c9ce17de8 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c @@ -227,9 +227,11 @@ int sqlite3Dequote(char *z){ // TODO: removed. break; }else if ( z[i]=='\\' ){ - z[j++] = '\\'; + // If we want to dequote properly, a few more characters would have to be + // handled explicitly. That would not affect the classification, however, + // so we won't do that. if ( z[i+1]==quote || z[i+1]=='\\' ){ - z[j++] = quote; + z[j++] = z[i+1]; i++; } } else diff --git a/query_classifier/test/.gitignore b/query_classifier/test/.gitignore deleted file mode 100644 index 670ee3e79..000000000 --- a/query_classifier/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# binaries generated here -testmain diff --git a/query_classifier/test/CMakeLists.txt b/query_classifier/test/CMakeLists.txt index ba817cb5e..68de663a7 100644 --- a/query_classifier/test/CMakeLists.txt +++ b/query_classifier/test/CMakeLists.txt @@ -4,7 +4,7 @@ if (BUILD_QC_MYSQLEMBEDDED) foreach(DIR ${MYSQL_INCLUDE_DIR_ALL}) include_directories(${DIR}) endforeach() - include_directories(${MYSQL_EMMBEDDED_INCLUDE_DIR}/..) + include_directories(${MYSQL_EMBEDDED_INCLUDE_DIR}/..) if(${ERRMSG} MATCHES "ERRMSG-NOTFOUND") message(FATAL_ERROR "The errmsg.sys file was not found, please define the path with -DERRMSG=") diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 102b9e42b..1a5d6fd20 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -20,6 +20,9 @@ #include #include #include +#define MYSQL_COM_QUIT COM_QUIT +#define MYSQL_COM_INIT_DB COM_INIT_DB +#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER #include #include #include diff --git a/query_classifier/test/qc_sqlite_unsupported.test b/query_classifier/test/qc_sqlite_unsupported.test index ae2ffd9f1..defc70dd7 100644 --- a/query_classifier/test/qc_sqlite_unsupported.test +++ b/query_classifier/test/qc_sqlite_unsupported.test @@ -34,3 +34,11 @@ set @`TeST`=4; # (Sqlite3 error: SQL logic error or missing database, unrecognized token: "@"): "set @=4" # # sqlite3GetToken needs to be modified to accept a quoted variable name. + +SAVEPOINT sa_savepoint_1 +#warning: [qc_sqlite] Statement was neither parsed nor recognized from keywords +# (Sqlite3 error: SQL logic error or missing database, near "SNAPSHOT": syntax error): "SNAPSHOT s" + +RELEASE SAVEPOINT sa_savepoint_1 +# warning: [qc_sqlite] Statement was neither parsed nor recognized from keywords +# (Sqlite3 error: SQL logic error or missing database, near "RELEASE": syntax error): "RELEASE SNAPSHOT s" \ No newline at end of file diff --git a/rabbitmq_consumer/inih/.gitignore b/rabbitmq_consumer/inih/.gitignore deleted file mode 100644 index 2a5429025..000000000 --- a/rabbitmq_consumer/inih/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -*.a -make.depend diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 5509140f2..000000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.DS_Store diff --git a/server/core/.gitignore b/server/core/.gitignore deleted file mode 100644 index d0c1894ec..000000000 --- a/server/core/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# binaries generated here -maxscale -maxkeys -maxpasswd -*.DS_Store diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index d232fe559..847d2fa42 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c modulecmd.c) +add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c modulecmd.c ) target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++) diff --git a/server/core/buffer.c b/server/core/buffer.c index e91e79b58..ff753c1f7 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -854,9 +854,9 @@ gwbuf_get_property(GWBUF *buf, char *name) GWBUF * gwbuf_make_contiguous(GWBUF *orig) { - GWBUF *newbuf; - char *ptr; - int len; + GWBUF *newbuf; + uint8_t *ptr; + int len; if (orig == NULL) { diff --git a/server/core/config.c b/server/core/config.c index 3e62de294..3c60038ae 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -62,11 +62,12 @@ #include #include #include -#include #include #include #include +#include "maxscale/service.h" + typedef struct duplicate_context { HASHTABLE *hash; @@ -98,7 +99,7 @@ bool config_has_duplicate_sections(const char* config, DUPLICATE_CONTEXT* contex int create_new_service(CONFIG_CONTEXT *obj); int create_new_server(CONFIG_CONTEXT *obj); int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash); -int create_new_listener(CONFIG_CONTEXT *obj, bool startnow); +int create_new_listener(CONFIG_CONTEXT *obj); int create_new_filter(CONFIG_CONTEXT *obj); int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj); @@ -815,7 +816,7 @@ process_config_context(CONFIG_CONTEXT *context) } else if (!strcmp(type, "listener")) { - error_count += create_new_listener(obj, false); + error_count += create_new_listener(obj); } else if (!strcmp(type, "monitor")) { @@ -3094,7 +3095,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* * @param startnow If true, start the listener now * @return Number of errors */ -int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) +int create_new_listener(CONFIG_CONTEXT *obj) { int error_count = 0; char *service_name = config_get_value(obj->parameters, "service"); @@ -3121,12 +3122,8 @@ int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) } else { - serviceAddProtocol(service, obj->object, protocol, socket, 0, - authenticator, authenticator_options, ssl_info); - if (startnow) - { - serviceStartProtocol(service, protocol, 0); - } + serviceCreateListener(service, obj->object, protocol, socket, 0, + authenticator, authenticator_options, ssl_info); } } @@ -3142,12 +3139,8 @@ int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) } else { - serviceAddProtocol(service, obj->object, protocol, address, atoi(port), - authenticator, authenticator_options, ssl_info); - if (startnow) - { - serviceStartProtocol(service, protocol, atoi(port)); - } + serviceCreateListener(service, obj->object, protocol, address, atoi(port), + authenticator, authenticator_options, ssl_info); } } diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c new file mode 100644 index 000000000..7e2619646 --- /dev/null +++ b/server/core/config_runtime.c @@ -0,0 +1,482 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include +#include +#include +#include +#include + +#include "maxscale/service.h" + +static SPINLOCK crt_lock = SPINLOCK_INIT; + +bool runtime_link_server(SERVER *server, const char *target) +{ + spinlock_acquire(&crt_lock); + + bool rval = false; + SERVICE *service = service_find(target); + MONITOR *monitor = service ? NULL : monitor_find(target); + + if (service || monitor) + { + rval = true; + + if (service) + { + serviceAddBackend(service, server); + service_serialize_servers(service); + } + else if (monitor) + { + monitorAddServer(monitor, server); + monitor_serialize_servers(monitor); + } + + const char *type = service ? "service" : "monitor"; + + MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, type, target); + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_unlink_server(SERVER *server, const char *target) +{ + spinlock_acquire(&crt_lock); + + bool rval = false; + SERVICE *service = service_find(target); + MONITOR *monitor = service ? NULL : monitor_find(target); + + if (service || monitor) + { + rval = true; + + if (service) + { + serviceRemoveBackend(service, server); + service_serialize_servers(service); + } + else if (monitor) + { + monitorRemoveServer(monitor, server); + monitor_serialize_servers(monitor); + } + + const char *type = service ? "service" : "monitor"; + MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, type, target); + } + + spinlock_release(&crt_lock); + return rval; +} + + +bool runtime_create_server(const char *name, const char *address, const char *port, + const char *protocol, const char *authenticator, + const char *authenticator_options) +{ + spinlock_acquire(&crt_lock); + bool rval = false; + + if (server_find_by_unique_name(name) == NULL) + { + // TODO: Get default values from the protocol module + if (port == NULL) + { + port = "3306"; + } + if (protocol == NULL) + { + protocol = "MySQLBackend"; + } + if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) + { + MXS_ERROR("No authenticator defined for server '%s' and no default " + "authenticator for protocol '%s'.", name, protocol); + spinlock_release(&crt_lock); + return false; + } + + /** First check if this service has been created before */ + SERVER *server = server_find_destroyed(name, protocol, authenticator, + authenticator_options); + + if (server) + { + /** Found old server, replace network details with new ones and + * reactivate it */ + snprintf(server->name, sizeof(server->name), "%s", address); + server->port = atoi(port); + server->is_active = true; + rval = true; + } + else + { + /** + * server_alloc will add the server to the global list of + * servers so we don't need to manually add it. + */ + server = server_alloc(name, address, atoi(port), protocol, + authenticator, authenticator_options); + } + + if (server && server_serialize(server)) + { + rval = true; + } + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_destroy_server(SERVER *server) +{ + spinlock_acquire(&crt_lock); + bool rval = false; + + if (service_server_in_use(server) || monitor_server_in_use(server)) + { + MXS_ERROR("Cannot destroy server '%s' as it is used by at least one " + "service or monitor", server->unique_name); + } + else + { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), + server->unique_name); + + if (unlink(filename) == -1) + { + if (errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else + { + rval = true; + MXS_WARNING("Server '%s' was not created at runtime. Remove the " + "server manually from the correct configuration file.", + server->unique_name); + } + } + else + { + rval = true; + } + + if (rval) + { + MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name, + server->name, server->port); + server->is_active = false; + } + } + + spinlock_release(&crt_lock); + return rval; +} + +static SSL_LISTENER* create_ssl(const char *name, const char *key, const char *cert, + const char *ca, const char *version, const char *depth) +{ + SSL_LISTENER *rval = NULL; + CONFIG_CONTEXT *obj = config_context_create(name); + + if (obj) + { + if (config_add_param(obj, "ssl", "required") && + config_add_param(obj, "ssl_key", key) && + config_add_param(obj, "ssl_cert", cert) && + config_add_param(obj, "ssl_ca_cert", ca) && + (!version || config_add_param(obj, "ssl_version", version)) && + (!depth || config_add_param(obj, "ssl_cert_verify_depth", depth))) + { + int err = 0; + SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err); + + if (err == 0 && ssl && listener_init_SSL(ssl) == 0) + { + rval = ssl; + } + } + + config_context_free(obj); + } + + return rval; +} + +bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, + const char *ca, const char *version, const char *depth) +{ + bool rval = false; + + if (key && cert && ca) + { + spinlock_acquire(&crt_lock); + SSL_LISTENER *ssl = create_ssl(server->unique_name, key, cert, ca, version, depth); + + if (ssl) + { + /** TODO: Properly discard old SSL configurations.This could cause the + * loss of a pointer if two update operations are done at the same time.*/ + ssl->next = server->server_ssl; + + /** Sync to prevent reads on partially initialized server_ssl */ + atomic_synchronize(); + server->server_ssl = ssl; + + if (server_serialize(server)) + { + rval = true; + } + } + spinlock_release(&crt_lock); + } + + return rval; +} + +bool runtime_alter_server(SERVER *server, char *key, char *value) +{ + spinlock_acquire(&crt_lock); + bool valid = true; + + if (strcmp(key, "address") == 0) + { + server_update_address(server, value); + } + else if (strcmp(key, "port") == 0) + { + server_update_port(server, atoi(value)); + } + else if (strcmp(key, "monuser") == 0) + { + server_update_credentials(server, value, server->monpw); + } + else if (strcmp(key, "monpw") == 0) + { + server_update_credentials(server, server->monuser, value); + } + else + { + valid = false; + } + + spinlock_release(&crt_lock); + return valid; +} + +/** + * @brief Convert a string value to a positive integer + * + * If the value is not a positive integer, an error is printed to @c dcb. + * + * @param value String value + * @return 0 on error, otherwise a positive integer + */ +static long get_positive_int(const char *value) +{ + char *endptr; + long ival = strtol(value, &endptr, 10); + + if (*endptr == '\0' && ival > 0) + { + return ival; + } + + return 0; +} + +bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value) +{ + spinlock_acquire(&crt_lock); + bool valid = false; + + if (strcmp(key, "user") == 0) + { + valid = true; + monitorAddUser(monitor, value, monitor->password); + } + else if (strcmp(key, "password") == 0) + { + valid = true; + monitorAddUser(monitor, monitor->user, value); + } + else if (strcmp(key, "monitor_interval") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetInterval(monitor, ival); + } + } + else if (strcmp(key, "backend_connect_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_write_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_read_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); + } + } + + spinlock_release(&crt_lock); + return valid; +} + +bool runtime_create_listener(SERVICE *service, const char *name, const char *addr, + const char *port, const char *proto, const char *auth, + const char *auth_opt, const char *ssl_key, + const char *ssl_cert, const char *ssl_ca, + const char *ssl_version, const char *ssl_depth) +{ + SSL_LISTENER *ssl = NULL; + bool rval = true; + + if (addr == NULL || strcasecmp(addr, "default") == 0) + { + addr = "0.0.0.0"; + } + if (port == NULL || strcasecmp(port, "default") == 0) + { + port = "3306"; + } + if (proto == NULL || strcasecmp(proto, "default") == 0) + { + proto = "MySQLClient"; + } + + if (auth && strcasecmp(auth, "default") == 0) + { + /** Set auth to NULL so the protocol default authenticator is used */ + auth = NULL; + } + + if (auth_opt && strcasecmp(auth_opt, "default") == 0) + { + /** Don't pass options to the authenticator */ + auth_opt = NULL; + } + + unsigned short u_port = atoi(port); + + if (ssl_key && ssl_cert && ssl_ca) + { + ssl = create_ssl(name, ssl_key, ssl_cert, ssl_ca, ssl_version, ssl_depth); + + if (ssl == NULL) + { + MXS_ERROR("SSL initialization for listener '%s' failed.", name); + rval = false; + } + } + + spinlock_acquire(&crt_lock); + + if (rval) + { + const char *print_addr = addr ? addr : "0.0.0.0"; + SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr, + u_port, auth, auth_opt, ssl); + + if (listener && listener_serialize(listener) && serviceLaunchListener(service, listener)) + { + MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created", + name, print_addr, port, service->name); + } + else + { + MXS_ERROR("Failed to start listener '%s' at %s:%s.", name, print_addr, port); + rval = false; + } + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_destroy_listener(SERVICE *service, const char *name) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), name); + + spinlock_acquire(&crt_lock); + + if (unlink(filename) == -1) + { + if (errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove persisted listener configuration '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else + { + rval = false; + MXS_WARNING("Listener '%s' was not created at runtime. Remove the " + "listener manually from the correct configuration file.", + name); + } + } + else + { + rval = true; + } + + if (rval) + { + rval = serviceStopListener(service, name); + + if (rval) + { + MXS_NOTICE("Destroyed listener '%s' for service '%s'. The listener " + "will be removed after the next restart of MaxScale.", + name, service->name); + } + else + { + MXS_ERROR("Failed to destroy listener '%s' for service '%s'", name, service->name); + } + } + + spinlock_release(&crt_lock); + return rval; +} diff --git a/server/core/dcb.c b/server/core/dcb.c index 35ceee80d..cc76a3a89 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2286,10 +2286,10 @@ dcb_printf(DCB *dcb, const char *fmt, ...) return; } va_start(args, fmt); - vsnprintf(GWBUF_DATA(buf), 10240, fmt, args); + vsnprintf((char*)GWBUF_DATA(buf), 10240, fmt, args); va_end(args); - buf->end = (void *)((char *)GWBUF_DATA(buf) + strlen(GWBUF_DATA(buf))); + buf->end = (void *)((char *)GWBUF_DATA(buf) + strlen((char*)GWBUF_DATA(buf))); dcb->func.write(dcb, buf); } diff --git a/server/core/externcmd.c b/server/core/externcmd.c index 37264e19d..01b7ab482 100644 --- a/server/core/externcmd.c +++ b/server/core/externcmd.c @@ -230,6 +230,7 @@ bool externcmd_substitute_arg(EXTERNCMD* cmd, const char* match, const char* rep } } } + pcre2_code_free(re); } else { diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 9d6a2e28d..7f7a9b10e 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -66,14 +66,14 @@ #include #include #include -#include -#include #include #include #include #include #include +#include "maxscale/service.h" + #define STRING_BUFFER_SIZE 1024 #define PIDFD_CLOSED -1 @@ -1951,7 +1951,7 @@ int main(int argc, char **argv) monitorStartAll(); /** Start the services that were created above */ - n_services = serviceStartAll(); + n_services = service_launch_all(); if (n_services == 0) { diff --git a/server/core/listener.c b/server/core/listener.c index 075ae8eff..1d811290b 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -30,13 +30,16 @@ #include #include #include +#include #include +#include #include #include #include #include #include #include +#include static RSA *rsa_512 = NULL; static RSA *rsa_1024 = NULL; @@ -55,61 +58,76 @@ static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength); * @return New listener object or NULL if unable to allocate */ SERV_LISTENER * -listener_alloc(struct service* service, char* name, char *protocol, char *address, - unsigned short port, char *authenticator, char* auth_options, SSL_LISTENER *ssl) +listener_alloc(struct service* service, const char* name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char* auth_options, SSL_LISTENER *ssl) { + char *my_address = NULL; if (address) { - address = MXS_STRDUP(address); - if (!address) + my_address = MXS_STRDUP(address); + if (!my_address) { return NULL; } } + char *my_auth_options = NULL; + + if (auth_options && (my_auth_options = MXS_STRDUP(auth_options)) == NULL) + { + MXS_FREE(my_address); + return NULL; + } + + char *my_authenticator = NULL; + if (authenticator) { - authenticator = MXS_STRDUP(authenticator); + my_authenticator = MXS_STRDUP(authenticator); } - else if ((authenticator = (char*)get_default_authenticator(protocol)) == NULL || - (authenticator = MXS_STRDUP(authenticator)) == NULL) + else if ((authenticator = get_default_authenticator(protocol)) == NULL || + (my_authenticator = MXS_STRDUP(authenticator)) == NULL) { MXS_ERROR("No authenticator defined for listener '%s' and could not get " "default authenticator for protocol '%s'.", name, protocol); + MXS_FREE(my_address); + return NULL; } void *auth_instance = NULL; - if (!authenticator_init(&auth_instance, authenticator, auth_options)) + if (!authenticator_init(&auth_instance, my_authenticator, auth_options)) { MXS_ERROR("Failed to initialize authenticator module '%s' for " - "listener '%s'.", authenticator, name); - MXS_FREE(address); - MXS_FREE(authenticator); + "listener '%s'.", my_authenticator, name); + MXS_FREE(my_address); + MXS_FREE(my_authenticator); return NULL; } - protocol = MXS_STRDUP(protocol); - name = MXS_STRDUP(name); + char *my_protocol = MXS_STRDUP(protocol); + char *my_name = MXS_STRDUP(name); SERV_LISTENER *proto = (SERV_LISTENER*)MXS_MALLOC(sizeof(SERV_LISTENER)); - if (!protocol || !proto || !name || !authenticator) + if (!my_protocol || !proto || !my_name || !my_authenticator) { - MXS_FREE(authenticator); - MXS_FREE(protocol); - MXS_FREE(address); + MXS_FREE(my_authenticator); + MXS_FREE(my_protocol); + MXS_FREE(my_address); + MXS_FREE(my_name); MXS_FREE(proto); - MXS_FREE(name); return NULL; } - proto->name = name; + proto->name = my_name; proto->listener = NULL; proto->service = service; - proto->protocol = protocol; - proto->address = address; + proto->protocol = my_protocol; + proto->address = my_address; proto->port = port; - proto->authenticator = authenticator; + proto->authenticator = my_authenticator; + proto->auth_options = my_auth_options; proto->ssl = ssl; proto->users = NULL; proto->resources = NULL; @@ -140,6 +158,8 @@ void listener_free(SERV_LISTENER* listener) MXS_FREE(listener->address); MXS_FREE(listener->authenticator); + MXS_FREE(listener->auth_options); + MXS_FREE(listener->name); MXS_FREE(listener->protocol); MXS_FREE(listener); } @@ -370,3 +390,132 @@ tmp_rsa_callback(SSL *s, int is_export, int keylength) } return(rsa_tmp); } + +/** + * Creates a listener configuration at the location pointed by @c filename + * + * @param listener Listener to serialize into a configuration + * @param filename Filename where configuration is written + * @return True on success, false on error + */ +static bool create_listener_config(const SERV_LISTENER *listener, const char *filename) +{ + int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (file == -1) + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to open file '%s' when serializing listener '%s': %d, %s", + filename, listener->name, errno, strerror_r(errno, errbuf, sizeof(errbuf))); + return false; + } + + // TODO: Check for return values on all of the dprintf calls + dprintf(file, "[%s]\n", listener->name); + dprintf(file, "type=listener\n"); + dprintf(file, "protocol=%s\n", listener->protocol); + dprintf(file, "service=%s\n", listener->service->name); + dprintf(file, "address=%s\n", listener->address); + dprintf(file, "port=%u\n", listener->port); + dprintf(file, "authenticator=%s\n", listener->authenticator); + + if (listener->auth_options) + { + dprintf(file, "authenticator_options=%s\n", listener->auth_options); + } + + if (listener->ssl) + { + dprintf(file, "ssl=required\n"); + + if (listener->ssl->ssl_cert) + { + dprintf(file, "ssl_cert=%s\n", listener->ssl->ssl_cert); + } + + if (listener->ssl->ssl_key) + { + dprintf(file, "ssl_key=%s\n", listener->ssl->ssl_key); + } + + if (listener->ssl->ssl_ca_cert) + { + dprintf(file, "ssl_ca_cert=%s\n", listener->ssl->ssl_ca_cert); + } + if (listener->ssl->ssl_cert_verify_depth) + { + dprintf(file, "ssl_cert_verify_depth=%d\n", listener->ssl->ssl_cert_verify_depth); + } + + const char *version = NULL; + + switch (listener->ssl->ssl_method_type) + { + case SERVICE_TLS10: + version = "TLSV10"; + break; + +#ifdef OPENSSL_1_0 + case SERVICE_TLS11: + version = "TLSV11"; + break; + + case SERVICE_TLS12: + version = "TLSV12"; + break; +#endif + case SERVICE_SSL_TLS_MAX: + version = "MAX"; + break; + + default: + break; + } + + if (version) + { + dprintf(file, "ssl_version=%s\n", version); + } + } + + close(file); + + return true; +} + +bool listener_serialize(const SERV_LISTENER *listener) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), + listener->name); + + if (unlink(filename) == -1 && errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove temporary listener configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else if (create_listener_config(listener, filename)) + { + char final_filename[PATH_MAX]; + strcpy(final_filename, filename); + + char *dot = strrchr(final_filename, '.'); + ss_dassert(dot); + *dot = '\0'; + + if (rename(filename, final_filename) == 0) + { + rval = true; + } + else + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to rename temporary listener configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + } + + return rval; +} diff --git a/server/core/maxscale/service.h b/server/core/maxscale/service.h new file mode 100644 index 000000000..aa17a1e22 --- /dev/null +++ b/server/core/maxscale/service.h @@ -0,0 +1,121 @@ +#pragma once +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include + +MXS_BEGIN_DECLS + +/** + * @file service.h - MaxScale internal service functions + */ + +/** + * Service life cycle management + * + * These functions should only be called by the MaxScale core. + */ + +/** + * @brief Allocate a new service + * + * @param name The service name + * @param router The router module this service uses + * + * @return The newly created service or NULL if an error occurred + */ +SERVICE* service_alloc(const char *name, const char *router); + +/** + * @brief Free the specified service + * + * @param service The service to free + */ +void service_free(SERVICE *service); + +/** + * @brief Shut all services down + * + * Stops all services and calls the destroyInstance entry points for all routers + * and filter. This should only be called once by the main shutdown code. + */ +void service_shutdown(void); + +/** + * @brief Launch all services + * + * Initialize and start all services. This should only be called once by the + * main initialization code. + * + * @return Number of successfully started services + */ +int service_launch_all(void); + +/** + * Creating and adding new components to services + */ +SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, + const char *protocol, const char *address, + unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl); +int serviceHasProtocol(SERVICE *service, const char *protocol, + const char* address, unsigned short port); +void serviceRemoveBackend(SERVICE *service, const SERVER *server); +bool serviceHasBackend(SERVICE *service, SERVER *server); + +/** + * @brief Serialize a service to a file + * + * This partially converts @c service into an INI format file. Only the servers + * of the service are serialized. This allows the service to keep using the servers + * added at runtime even after a restart. + * + * NOTE: This does not persist the complete service configuration and requires + * that an existing service configuration is in the main configuration file. + * Changes to service parameters are not persisted. + * + * @param service Service to serialize + * @return False if the serialization of the service fails, true if it was successful + */ +bool service_serialize_servers(const SERVICE *service); + +/** + * Internal utility functions + */ +char* service_get_name(SERVICE* service); +bool service_all_services_have_listeners(void); +int service_isvalid(SERVICE *service); + +/** + * Check if a service uses @c servers + * @param server Server that is queried + * @return True if server is used by at least one service + */ +bool service_server_in_use(const SERVER *server); + +/** + * Alteration of the service configuration + */ +void serviceAddRouterOption(SERVICE *service, char *option); +void serviceClearRouterOptions(SERVICE *service); +void service_update(SERVICE *service, char *router, char *user, char *auth); +bool service_set_param_value(SERVICE* service, CONFIG_PARAMETER* param, char* valstr, + count_spec_t count_spec, config_param_type_t type); + +/** + * Internal debugging diagnostics + */ +void printService(SERVICE *service); +void printAllServices(void); + +MXS_END_DECLS \ No newline at end of file diff --git a/server/core/monitor.c b/server/core/monitor.c index 33a4dd2f0..6a22ee70a 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -372,7 +372,7 @@ void monitorRemoveServer(MONITOR *mon, SERVER *server) if (ptr) { - monitor_server_free(ptr); + monitor_server_free(ptr); } if (old_state == MONITOR_STATE_RUNNING) @@ -515,7 +515,7 @@ monitorList(DCB *dcb) * @return Pointer to the monitor or NULL */ MONITOR * -monitor_find(char *name) +monitor_find(const char *name) { MONITOR *ptr; @@ -845,7 +845,7 @@ mon_get_event_type(MONITOR_SERVERS* node) /* Was running and still is */ if ((!prev_bits || !present_bits || prev_bits == present_bits) && - prev & (SERVER_MASTER | SERVER_SLAVE | SERVER_JOINED | SERVER_NDB)) + prev & (SERVER_MASTER | SERVER_SLAVE | SERVER_JOINED | SERVER_NDB)) { /* We used to know what kind of server it was */ event_type = LOSS_EVENT; @@ -1042,8 +1042,48 @@ monitor_launch_script(MONITOR* mon, MONITOR_SERVERS* ptr, char* script) } else { + ss_dassert(cmd->argv != NULL && cmd->argv[0] != NULL); + // Construct a string with the script + arguments + char *scriptStr = NULL; + int totalStrLen = 0; + bool memError = false; + for (int i = 0; cmd->argv[i]; i++) + { + totalStrLen += strlen(cmd->argv[i]) + 1; // +1 for space and one \0 + } + int spaceRemaining = totalStrLen; + if ((scriptStr = MXS_CALLOC(totalStrLen, sizeof(char))) != NULL) + { + char *currentPos = scriptStr; + // The script name should not begin with a space + int len = snprintf(currentPos, spaceRemaining, "%s", cmd->argv[0]); + currentPos += len; + spaceRemaining -= len; + + for (int i = 1; cmd->argv[i]; i++) + { + if ((cmd->argv[i])[0] == '\0') + { + continue; // Empty argument, print nothing + } + len = snprintf(currentPos, spaceRemaining, " %s", cmd->argv[i]); + currentPos += len; + spaceRemaining -= len; + } + ss_dassert(spaceRemaining > 0); + *currentPos = '\0'; + } + else + { + memError = true; + scriptStr = cmd->argv[0]; // print at least something + } MXS_NOTICE("Executed monitor script '%s' on event '%s'.", - script, mon_get_event_name(ptr)); + scriptStr, mon_get_event_name(ptr)); + if (!memError) + { + MXS_FREE(scriptStr); + } } externcmd_free(cmd); diff --git a/server/core/server.c b/server/core/server.c index 570b47a6e..a06088baf 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -1228,8 +1228,7 @@ bool server_serialize(const SERVER *server) return rval; } -/** Try to find a server with a matching name that has been destroyed */ -static SERVER* find_destroyed_server(const char *name, const char *protocol, +SERVER* server_find_destroyed(const char *name, const char *protocol, const char *authenticator, const char *auth_options) { spinlock_acquire(&server_spin); @@ -1254,106 +1253,3 @@ static SERVER* find_destroyed_server(const char *name, const char *protocol, return server; } - -bool server_create(const char *name, const char *address, const char *port, - const char *protocol, const char *authenticator, - const char *authenticator_options) -{ - bool rval = false; - - if (server_find_by_unique_name(name) == NULL) - { - // TODO: Get default values from the protocol module - if (port == NULL) - { - port = "3306"; - } - if (protocol == NULL) - { - protocol = "MySQLBackend"; - } - if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) - { - MXS_ERROR("No authenticator defined for server '%s' and no default " - "authenticator for protocol '%s'.", name, protocol); - return false; - } - - /** First check if this service has been created before */ - SERVER *server = find_destroyed_server(name, protocol, authenticator, - authenticator_options); - - if (server) - { - /** Found old server, replace network details with new ones and - * reactivate it */ - snprintf(server->name, sizeof(server->name), "%s", address); - server->port = atoi(port); - server->is_active = true; - rval = true; - } - else - { - /** - * server_alloc will add the server to the global list of - * servers so we don't need to manually add it. - */ - server = server_alloc(name, address, atoi(port), protocol, - authenticator, authenticator_options); - } - - if (server && server_serialize(server)) - { - rval = true; - } - } - - return rval; -} - -bool server_destroy(SERVER *server) -{ - bool rval = false; - - if (service_server_in_use(server) || monitor_server_in_use(server)) - { - MXS_ERROR("Cannot destroy server '%s' as it is used by at least one " - "service or monitor", server->unique_name); - } - else - { - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), - server->unique_name); - - if (unlink(filename) == -1) - { - if (errno != ENOENT) - { - char err[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s", - filename, errno, strerror_r(errno, err, sizeof(err))); - } - else - { - rval = true; - MXS_WARNING("Server '%s' was not created at runtime. Remove the " - "server manually from the correct configuration file.", - server->unique_name); - } - } - else - { - rval = true; - } - - if (rval) - { - MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name, - server->name, server->port); - server->is_active = false; - } - } - - return rval; -} diff --git a/server/core/service.c b/server/core/service.c index 9380d7c44..f3eaca089 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -48,7 +48,6 @@ #include #include #include -#include #include #include #include @@ -68,6 +67,8 @@ #include #include +#include "maxscale/service.h" + /** Base value for server weights */ #define SERVICE_BASE_SERVER_WEIGHT 1000 @@ -103,32 +104,21 @@ static void service_internal_restart(void *data); static void service_queue_check(void *data); static void service_calculate_weights(SERVICE *service); -/** - * Allocate a new service for the gateway to support - * - * - * @param servname The service name - * @param router Name of the router module this service uses - * - * @return The newly created service or NULL if an error occurred - */ -SERVICE * -service_alloc(const char *servname, const char *router) +SERVICE* service_alloc(const char *name, const char *router) { - servname = MXS_STRDUP(servname); - router = MXS_STRDUP(router); - + char *my_name = MXS_STRDUP(name); + char *my_router = MXS_STRDUP(router); SERVICE *service = (SERVICE *)MXS_CALLOC(1, sizeof(*service)); - if (!servname || !router || !service) + if (!my_name || !my_router || !service) { - MXS_FREE((void*)servname); - MXS_FREE((void*)router); + MXS_FREE(my_name); + MXS_FREE(my_router); MXS_FREE(service); return NULL; } - if ((service->router = load_module(router, MODULE_ROUTER)) == NULL) + if ((service->router = load_module(my_router, MODULE_ROUTER)) == NULL) { char* home = get_libdir(); char* ldpath = getenv("LD_LIBRARY_PATH"); @@ -138,13 +128,13 @@ service_alloc(const char *servname, const char *router) "following directories :\n\t\t\t " "- %s\n%s%s", MODULE_ROUTER, - router, - router, + my_router, + my_router, home, ldpath ? "\t\t\t - " : "", ldpath ? ldpath : ""); - MXS_FREE((void*)servname); - MXS_FREE((void*)router); + MXS_FREE(my_name); + MXS_FREE(my_router); MXS_FREE(service); return NULL; } @@ -152,8 +142,8 @@ service_alloc(const char *servname, const char *router) service->capabilities = service->router->getCapabilities(); service->client_count = 0; service->n_dbref = 0; - service->name = (char*)servname; - service->routerModule = (char*)router; + service->name = my_name; + service->routerModule = my_router; service->users_from_all = false; service->queued_connections = NULL; service->localhost_match_wildcard_host = SERVICE_PARAM_UNINIT; @@ -478,8 +468,7 @@ static void free_string_array(char** array) * @param service The Service that should be started * @return Returns the number of listeners created */ -int -serviceStart(SERVICE *service) +int serviceInitialize(SERVICE *service) { /** Calculate the server weights */ service_calculate_weights(service); @@ -502,37 +491,36 @@ serviceStart(SERVICE *service) return listeners; } -/** - * Start an individual listener - * - * @param service The service to start the listener for - * @param protocol The name of the protocol - * @param port The port number - */ -void -serviceStartProtocol(SERVICE *service, char *protocol, int port) +bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port) { - SERV_LISTENER *ptr; - - ptr = service->ports; - while (ptr) - { - if (strcmp(ptr->protocol, protocol) == 0 && ptr->port == port) - { - serviceStartPort(service, ptr); - } - ptr = ptr->next; - } + return serviceStartPort(service, port); } +bool serviceStopListener(SERVICE *service, const char *name) +{ + bool rval = false; -/** - * Start all the services - * - * @return Return the number of services started - */ -int -serviceStartAll() + spinlock_acquire(&service->spin); + + for (SERV_LISTENER *port = service->ports; port; port = port->next) + { + if (strcmp(port->name, name) == 0) + { + if (poll_remove_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; + rval = true; + } + break; + } + } + + spinlock_release(&service->spin); + + return rval; +} + +int service_launch_all() { SERVICE *ptr; int n = 0, i; @@ -543,7 +531,7 @@ serviceStartAll() ptr = allServices; while (ptr && !ptr->svc_do_shutdown) { - n += (i = serviceStart(ptr)); + n += (i = serviceInitialize(ptr)); if (i == 0) { @@ -556,16 +544,7 @@ serviceStartAll() return error ? 0 : n; } -/** - * Stop a service - * - * This function stops the listener for the service - * - * @param service The Service that should be stopped - * @return Returns the number of listeners restarted - */ -int -serviceStop(SERVICE *service) +bool serviceStop(SERVICE *service) { SERV_LISTENER *port; int listeners = 0; @@ -585,7 +564,7 @@ serviceStop(SERVICE *service) } service->state = SERVICE_STATE_STOPPED; - return listeners; + return listeners > 0; } /** @@ -596,8 +575,7 @@ serviceStop(SERVICE *service) * @param service The Service that should be restarted * @return Returns the number of listeners restarted */ -int -serviceRestart(SERVICE *service) +bool serviceStart(SERVICE *service) { SERV_LISTENER *port; int listeners = 0; @@ -616,24 +594,16 @@ serviceRestart(SERVICE *service) port = port->next; } service->state = SERVICE_STATE_STARTED; - return listeners; + return listeners > 0; } - -/** - * Deallocate the specified service - * - * @param service The service to deallocate - * @return Returns true if the service was freed - */ -int -service_free(SERVICE *service) +void service_free(SERVICE *service) { SERVICE *ptr; SERVER_REF *srv; if (service->stats.n_current) { - return 0; + return; } /* First of all remove from the linked list */ spinlock_acquire(&service_spin); @@ -674,11 +644,10 @@ service_free(SERVICE *service) serviceClearRouterOptions(service); MXS_FREE(service); - return 1; } /** - * Add a protocol/port pair to the service + * Create a listener for the service * * @param service The service * @param protocol The name of the protocol module @@ -686,12 +655,12 @@ service_free(SERVICE *service) * @param port The port to listen on * @param authenticator Name of the authenticator to be used * @param ssl SSL configuration - * @return TRUE if the protocol/port could be added + * + * @return Created listener or NULL on error */ -int -serviceAddProtocol(SERVICE *service, char *name, char *protocol, char *address, - unsigned short port, char *authenticator, char *options, - SSL_LISTENER *ssl) +SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl) { SERV_LISTENER *proto = listener_alloc(service, name, protocol, address, port, authenticator, options, ssl); @@ -702,10 +671,9 @@ serviceAddProtocol(SERVICE *service, char *name, char *protocol, char *address, proto->next = service->ports; service->ports = proto; spinlock_release(&service->spin); - return 1; } - return 0; + return proto; } /** @@ -1218,7 +1186,7 @@ serviceSetFilters(SERVICE *service, char *filters) * @return The service or NULL if not found */ SERVICE * -service_find(char *servname) +service_find(const char *servname) { SERVICE *service; @@ -1889,7 +1857,7 @@ void service_shutdown() { svc->svc_do_shutdown = true; /* Call destroyInstance hook for routers */ - if (svc->router->destroyInstance) + if (svc->router->destroyInstance && svc->router_instance) { svc->router->destroyInstance(svc->router_instance); } @@ -1898,7 +1866,7 @@ void service_shutdown() FILTER_DEF **filters = svc->filters; for (int i=0; i < svc->n_filters; i++) { - if (filters[i]->obj->destroyInstance) + if (filters[i]->obj->destroyInstance && filters[i]->filter) { /* Call destroyInstance hook for filters */ filters[i]->obj->destroyInstance(filters[i]->filter); diff --git a/server/core/test/.gitignore b/server/core/test/.gitignore deleted file mode 100644 index ebadda641..000000000 --- a/server/core/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -#binaries generated here -testhash diff --git a/server/core/test/testbuffer.c b/server/core/test/testbuffer.c index 119bd464d..ac645f1cd 100644 --- a/server/core/test/testbuffer.c +++ b/server/core/test/testbuffer.c @@ -349,12 +349,12 @@ test1() ss_dfprintf(stderr, "\t..done\nSet a property for the buffer"); gwbuf_add_property(buffer, "name", "value"); ss_info_dassert(0 == strcmp("value", gwbuf_get_property(buffer, "name")), "Should now have correct property"); - strcpy(GWBUF_DATA(buffer), "The quick brown fox jumps over the lazy dog"); + strcpy((char*)GWBUF_DATA(buffer), "The quick brown fox jumps over the lazy dog"); ss_dfprintf(stderr, "\t..done\nLoad some data into the buffer"); ss_info_dassert('q' == GWBUF_DATA_CHAR(buffer, 4), "Fourth character of buffer must be 'q'"); ss_info_dassert(-1 == GWBUF_DATA_CHAR(buffer, 105), "Hundred and fifth character of buffer must return -1"); ss_info_dassert(0 == GWBUF_IS_SQL(buffer), "Must say buffer is not SQL, as it does not have marker"); - strcpy(GWBUF_DATA(buffer), "1234\x03SELECT * FROM sometable"); + strcpy((char*)GWBUF_DATA(buffer), "1234\x03SELECT * FROM sometable"); ss_dfprintf(stderr, "\t..done\nLoad SQL data into the buffer"); ss_info_dassert(1 == GWBUF_IS_SQL(buffer), "Must say buffer is SQL, as it does have marker"); transform = gwbuf_clone_transform(buffer, GWBUF_TYPE_PLAINSQL); diff --git a/server/core/test/testserver.c b/server/core/test/testserver.c index 67991f864..481281834 100644 --- a/server/core/test/testserver.c +++ b/server/core/test/testserver.c @@ -70,7 +70,7 @@ test1() ss_info_dassert(0 == strcmp("value", serverGetParameter(server, "name")), "Parameter should be returned correctly"); ss_dfprintf(stderr, "\t..done\nTesting Unique Name for Server."); - ss_info_dassert(NULL == server_find_by_unique_name("uniquename"), + ss_info_dassert(NULL == server_find_by_unique_name("non-existent"), "Should not find non-existent unique name."); mxs_log_flush_sync(); ss_info_dassert(server == server_find_by_unique_name("uniquename"), "Should find by unique name."); diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index e6aa0bd5b..22eac3876 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -33,7 +33,7 @@ #include #include #include -#include +#include "../maxscale/service.h" #include #include #include "test_utils.h" @@ -69,8 +69,8 @@ test1() ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name"); ss_dfprintf(stderr, "\t..done\nAdding protocol testprotocol."); set_libdir(MXS_STRDUP_A("../../modules/authenticator/MySQLAuth/")); - ss_info_dassert(0 != serviceAddProtocol(service, "TestProtocol", "testprotocol", - "localhost", 9876, "MySQLAuth", NULL, NULL), + ss_info_dassert(serviceCreateListener(service, "TestProtocol", "testprotocol", + "localhost", 9876, "MySQLAuth", NULL, NULL), "Add Protocol should succeed"); ss_info_dassert(0 != serviceHasProtocol(service, "testprotocol", "localhost", 9876), "Service should have new protocol as requested"); diff --git a/server/include/.gitignore b/server/include/.gitignore deleted file mode 100644 index 67020331b..000000000 --- a/server/include/.gitignore +++ /dev/null @@ -1 +0,0 @@ -version.h diff --git a/server/inih/.gitignore b/server/inih/.gitignore deleted file mode 100644 index 2a5429025..000000000 --- a/server/inih/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -*.a -make.depend diff --git a/server/modules/authenticator/MySQLAuth/CMakeLists.txt b/server/modules/authenticator/MySQLAuth/CMakeLists.txt index b27f39766..9bbcd952a 100644 --- a/server/modules/authenticator/MySQLAuth/CMakeLists.txt +++ b/server/modules/authenticator/MySQLAuth/CMakeLists.txt @@ -2,9 +2,3 @@ add_library(MySQLAuth SHARED mysql_auth.c dbusers.c) target_link_libraries(MySQLAuth maxscale-common MySQLCommon) set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0") install_module(MySQLAuth core) - -if (BUILD_TESTS) - add_executable(test_mysql_users test_mysql_users.c) - target_link_libraries(test_mysql_users MySQLAuth MySQLCommon maxscale-common) - add_test(TestMySQLUsers test_mysql_users) -endif() diff --git a/server/modules/authenticator/MySQLAuth/test_mysql_users.c b/server/modules/authenticator/MySQLAuth/test_mysql_users.c deleted file mode 100644 index 1a30b5e03..000000000 --- a/server/modules/authenticator/MySQLAuth/test_mysql_users.c +++ /dev/null @@ -1,546 +0,0 @@ -/* - * 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/bsl. - * - * Change Date: 2019-07-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. - */ - -/** - * - * @verbatim - * Revision History - * - * Date Who Description - * 14/02/2014 Massimiliano Pinto Initial implementation - * 17/02/2014 Massimiliano Pinto Added check ipv4 - * 03/10/2014 Massimiliano Pinto Added check for wildcard hosts - * - * @endverbatim - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include "dbusers.h" -#include -#include -#include -#include -#include - -extern int setipaddress(); - -int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char *password) -{ - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - MYSQL_USER_HOST find_key; - USERS *mysql_users; - char ret_ip[200] = ""; - char *fetch_data; - char *db = ""; - DCB *dcb; - SERVICE *service; - SERV_LISTENER dummy; - unsigned long fix_ipv4; - - dcb = dcb_alloc(DCB_ROLE_INTERNAL, &dummy); - - if (dcb == NULL) - { - fprintf(stderr, "dcb_alloc() failed\n"); - return 1; - } - if ((service = (SERVICE *)MXS_CALLOC(1, sizeof(SERVICE))) == NULL) - { - dcb_close(dcb); - return 1; - } - - if (ipv4 > UINT_MAX) - { - fix_ipv4 = UINT_MAX; - } - else - { - fix_ipv4 = ipv4; - } - - mysql_users = mysql_users_alloc(); - /* prepare the user@host data struct */ - memset(&key, 0, sizeof(key)); - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - memcpy(&(serv_addr).sin_addr.s_addr, &fix_ipv4, sizeof(ipv4)); - - key.user = username; - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); - - fprintf(stderr, "IPv4 passed/fixed [%lu/%lu] is [%s]\n", ipv4, fix_ipv4, ret_ip); - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (!mysql_users_add(mysql_users, &key, password)) - { - fprintf(stderr, "Failed adding %s@%s(%lu)\n", username, ret_ip, fix_ipv4); - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - return 1; - } - - memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&find_key, 0, sizeof(find_key)); - - find_key.user = username; - memcpy(&(serv_addr).sin_addr.s_addr, &ipv4, sizeof(ipv4)); - find_key.resource = db; - - memcpy(&find_key.ipv4, &serv_addr, sizeof(serv_addr)); - - fetch_data = mysql_users_fetch(mysql_users, &find_key); - - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - - if (!fetch_data) - { - return 1; - } - - return 0; -} - -int set_and_get_single_mysql_users(char *username, char *hostname, char *password) -{ - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - USERS *mysql_users; - char ret_ip[200] = ""; - char *fetch_data; - char *db = ""; - - mysql_users = mysql_users_alloc(); - - /* prepare the user@host data struct */ - memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&key, 0, sizeof(key)); - - - if (hostname) - if (!setipaddress(&serv_addr.sin_addr, hostname)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", hostname); - users_free(mysql_users); - return 1; - } - if (username) - { - key.user = username; - } - - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); - - fprintf(stderr, "set/get [%s@%s]: IPV4 %lu is [%u].[%u].[%u].[%u]\n", username, hostname, - (unsigned long) serv_addr.sin_addr.s_addr, serv_addr.sin_addr.s_addr & 0xFF, - (serv_addr.sin_addr.s_addr & 0xFF00), (serv_addr.sin_addr.s_addr & 0xFF0000), - ((serv_addr.sin_addr.s_addr & 0xFF000000) / (256 * 256 * 256))); - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (!mysql_users_add(mysql_users, &key, password)) - { - fprintf(stderr, "mysql_users_add() failed for %s@%s\n", username, hostname); - users_free(mysql_users); - return 1; - } - - memset(&serv_addr, 0, sizeof(serv_addr)); - - if (hostname) - if (!setipaddress(&serv_addr.sin_addr, hostname)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", hostname); - users_free(mysql_users); - return 1; - } - key.user = username; - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - fetch_data = mysql_users_fetch(mysql_users, &key); - - users_free(mysql_users); - - if (!fetch_data) - { - return 1; - } - - return 0; -} - -int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, - char *db, char *db_from) -{ - USERS *mysql_users; - int ret = -1; - struct sockaddr_in client_addr; - DCB *dcb; - SERVICE *service; - MYSQL_session *data; - - if ((service = (SERVICE *)MXS_CALLOC(1, sizeof(SERVICE))) == NULL) - { - return ret; - } - - SERV_LISTENER *port = listener_alloc(service, "testlistener", "MySQLClient", NULL, 4006, "MySQLAuth", NULL, NULL); - - dcb = dcb_alloc(DCB_ROLE_INTERNAL, port); - - if (dcb == NULL) - { - fprintf(stderr, "dcb_alloc() failed\n"); - return ret; - } - - memset(&client_addr, 0, sizeof(client_addr)); - - if (hostname) - { - if (!setipaddress(&client_addr.sin_addr, from)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", from); - MXS_FREE(service); - dcb_close(dcb); - return ret; - } - } - - if ((data = (MYSQL_session *) MXS_CALLOC(1, sizeof(MYSQL_session))) == NULL) - { - MXS_FREE(service); - dcb_close(dcb); - return ret; - } - - - /* client IPv4 in raw data*/ - memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in)); - - dcb->service = service; - - mysql_users = mysql_users_alloc(); - - service->ports = port; - service->ports->users = mysql_users; - - if (db_from != NULL) - { - strcpy(data->db, db_from); - } - else - { - data->db[0] = 0; - } - - /* freed by dcb_close(dcb) */ - dcb->data = data; - - // the routine returns 1 on success - if (anydb != NULL) - { - if (strcmp(anydb, "N") == 0) - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, anydb, db); - } - else if (strcmp(anydb, "Y") == 0) - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "Y", ""); - } - else - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); - } - } - else - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); - } - - if (ret == 0) - { - fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password); - } - else - { - unsigned char db_passwd[100] = ""; - - dcb->remote = MXS_STRDUP_A(from); - - // returns 0 on success - ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); - } - - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - - return ret; -} - -int main() -{ - int ret; - int i = 0; - int k = 0; - time_t t; - - fprintf(stderr, "----------------\n"); - - time(&t); - fprintf(stderr, "%s\n", asctime(localtime(&t))); - fprintf(stderr, ">>> Started MySQL load, set & get users@host\n"); - - - ret = set_and_get_single_mysql_users("pippo", "localhost", "xyz"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("pippo", "127.0.0.2", "xyz"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("pippo", "%", "xyz"); - assert(ret == 1); - ret = set_and_get_single_mysql_users("rootuser", NULL, "wwwww"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("nullpwd", "this_host_does_not_exists", NULL); - assert(ret == 1); - ret = set_and_get_single_mysql_users("myuser", "345.-1.5.40997", "password"); - assert(ret == 1); - ret = set_and_get_single_mysql_users(NULL, NULL, NULL); - assert(ret == 1); - - ret = set_and_get_single_mysql_users_ipv4("negative", -467295, "_ncd"); - assert(ret == 1); - ret = set_and_get_single_mysql_users_ipv4("extra", 0xFFFFFFFFFUL * 100, "JJcd"); - assert(ret == 1); - ret = set_and_get_single_mysql_users_ipv4("aaapo", 0, "JJcd"); - assert(ret == 0); - ret = set_and_get_single_mysql_users_ipv4(NULL, '\0', "JJcd"); - assert(ret == 1); - - - for (i = 256 * 256 * 256; i <= 256 * 256 * 256 + 5; i++) - { - char user[129] = ""; - snprintf(user, 128, "user_%i", k); - ret = set_and_get_single_mysql_users_ipv4(user, i, "JJcd"); - assert(ret == 0); - k++; - } - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "", "127.0.0.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "two", "192.168.2.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.168.4.%", "ffoo", "192.168.2.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.68.0.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "Y", NULL, "cossa"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, N, NULL, ragione\n"); - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "N", NULL, "ragione"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.1", "foo", "192.0.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.1.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "foo", "192.3.2.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", "Y", NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", - "matto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", - "fatto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "matto", - "fatto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "", "fto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", NULL, "grewao"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.254.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.254.%", "foo", "192.254.0.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("riccio", "192.0.0.%", "foo", "192.134.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "12345678901234567890123456789012345678901234", - "192.254.254.245", "Y", NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - fprintf(stderr, "----------------\n"); - fprintf(stderr, "<<< Test completed\n"); - - time(&t); - fprintf(stderr, "%s\n", asctime(localtime(&t))); - - return 0; -} - diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 3977cfbb0..69ea3e697 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cache.c rules.c storage.c) + add_library(cache SHARED cache.cc cachefilter.cc cachemt.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.c deleted file mode 100644 index 302b40a5e..000000000 --- a/server/modules/filter/cache/cache.c +++ /dev/null @@ -1,1141 +0,0 @@ -/* - * 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/bsl. - * - * Change Date: 2019-07-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. - */ - -#define MXS_MODULE_NAME "cache" -#include "cache.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "rules.h" -#include "storage.h" - -static char VERSION_STRING[] = "V1.0.0"; - -static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **); -static void *newSession(FILTER *instance, SESSION *session); -static void closeSession(FILTER *instance, void *sdata); -static void freeSession(FILTER *instance, void *sdata); -static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *downstream); -static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *upstream); -static int routeQuery(FILTER *instance, void *sdata, GWBUF *queue); -static int clientReply(FILTER *instance, void *sdata, GWBUF *queue); -static void diagnostics(FILTER *instance, void *sdata, DCB *dcb); -static uint64_t getCapabilities(void); - -// -// Global symbols of the Module -// - -MODULE_INFO info = -{ - MODULE_API_FILTER, - MODULE_IN_DEVELOPMENT, - FILTER_VERSION, - "A caching filter that is capable of caching and returning cached data." -}; - -char *version() -{ - return VERSION_STRING; -} - -/** - * The module initialization functions, called when the module has - * been loaded. - */ -void ModuleInit() -{ -} - -/** - * The module entry point function, called when the module is loaded. - * - * @return The module object. - */ -FILTER_OBJECT *GetModuleObject() -{ - static FILTER_OBJECT object = - { - createInstance, - newSession, - closeSession, - freeSession, - setDownstream, - setUpstream, - routeQuery, - clientReply, - diagnostics, - getCapabilities, - NULL, // destroyInstance - }; - - return &object; -}; - -// -// Implementation -// - -typedef struct cache_config -{ - uint32_t max_resultset_rows; - uint32_t max_resultset_size; - const char *rules; - const char *storage; - char *storage_options; - char **storage_argv; - int storage_argc; - uint32_t ttl; - uint32_t debug; -} CACHE_CONFIG; - -static const CACHE_CONFIG DEFAULT_CONFIG = -{ - CACHE_DEFAULT_MAX_RESULTSET_ROWS, - CACHE_DEFAULT_MAX_RESULTSET_SIZE, - NULL, - NULL, - NULL, - NULL, - 0, - CACHE_DEFAULT_TTL, - CACHE_DEFAULT_DEBUG -}; - -typedef struct cache_instance -{ - const char *name; - CACHE_CONFIG config; - CACHE_RULES *rules; - CACHE_STORAGE_MODULE *module; - CACHE_STORAGE *storage; -} CACHE_INSTANCE; - -typedef enum cache_session_state -{ - CACHE_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. - CACHE_EXPECTING_FIELDS, // A select has been sent, and we want more fields. - CACHE_EXPECTING_ROWS, // A select has been sent, and we want more rows. - CACHE_EXPECTING_NOTHING, // We are not expecting anything from the server. - CACHE_EXPECTING_USE_RESPONSE, // A "USE DB" was issued. - CACHE_IGNORING_RESPONSE, // We are not interested in the data received from the server. -} cache_session_state_t; - -typedef struct cache_response_state -{ - GWBUF* data; /**< Response data, possibly incomplete. */ - size_t n_totalfields; /**< The number of fields a resultset contains. */ - size_t n_fields; /**< How many fields we have received, <= n_totalfields. */ - size_t n_rows; /**< How many rows we have received. */ - size_t offset; /**< Where we are in the response buffer. */ -} CACHE_RESPONSE_STATE; - -static void cache_response_state_reset(CACHE_RESPONSE_STATE *state); - -typedef struct cache_session_data -{ - CACHE_INSTANCE *instance; /**< The cache instance the session is associated with. */ - CACHE_STORAGE_API *api; /**< The storage API to be used. */ - CACHE_STORAGE *storage; /**< The storage to be used with this session data. */ - DOWNSTREAM down; /**< The previous filter or equivalent. */ - UPSTREAM up; /**< The next filter or equivalent. */ - CACHE_RESPONSE_STATE res; /**< The response state. */ - SESSION *session; /**< The session this data is associated with. */ - char key[CACHE_KEY_MAXLEN]; /**< Key storage. */ - char *default_db; /**< The default database. */ - char *use_db; /**< Pending default database. Needs server response. */ - cache_session_state_t state; -} CACHE_SESSION_DATA; - -static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, SESSION *session); -static void cache_session_data_free(CACHE_SESSION_DATA *data); - -static int handle_expecting_fields(CACHE_SESSION_DATA *csdata); -static int handle_expecting_nothing(CACHE_SESSION_DATA *csdata); -static int handle_expecting_response(CACHE_SESSION_DATA *csdata); -static int handle_expecting_rows(CACHE_SESSION_DATA *csdata); -static int handle_expecting_use_response(CACHE_SESSION_DATA *csdata); -static int handle_ignoring_response(CACHE_SESSION_DATA *csdata); -static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); -static bool route_using_cache(CACHE_SESSION_DATA *sdata, const GWBUF *key, GWBUF **value); - -static int send_upstream(CACHE_SESSION_DATA *csdata); - -static void store_result(CACHE_SESSION_DATA *csdata); - -// -// API BEGIN -// - -/** - * Create an instance of the cache filter for a particular service - * within MaxScale. - * - * @param name The name of the instance (as defined in the config file). - * @param options The options for this filter - * @param params The array of name/value pair parameters for the filter - * - * @return The instance data for this new instance - */ -static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **params) -{ - CACHE_INSTANCE *cinstance = NULL; - CACHE_CONFIG config = DEFAULT_CONFIG; - - if (process_params(options, params, &config)) - { - CACHE_RULES *rules = NULL; - - if (config.rules) - { - rules = cache_rules_load(config.rules, config.debug); - } - else - { - rules = cache_rules_create(config.debug); - } - - if (rules) - { - if ((cinstance = MXS_CALLOC(1, sizeof(CACHE_INSTANCE))) != NULL) - { - CACHE_STORAGE_MODULE *module = cache_storage_open(config.storage); - - if (module) - { - uint32_t ttl = config.ttl; - int argc = config.storage_argc; - char** argv = config.storage_argv; - - CACHE_STORAGE *storage = module->api->createInstance(name, ttl, argc, argv); - - if (storage) - { - cinstance->name = name; - cinstance->config = config; - cinstance->rules = rules; - cinstance->module = module; - cinstance->storage = storage; - - MXS_NOTICE("Cache storage %s opened and initialized.", config.storage); - } - else - { - MXS_ERROR("Could not create storage instance for '%s'.", name); - cache_rules_free(rules); - cache_storage_close(module); - MXS_FREE(cinstance); - cinstance = NULL; - } - } - else - { - MXS_ERROR("Could not load cache storage module '%s'.", name); - cache_rules_free(rules); - MXS_FREE(cinstance); - cinstance = NULL; - } - } - } - } - - return (FILTER*)cinstance; -} - -/** - * Associate a new session with this instance of the filter. - * - * @param instance The cache instance data - * @param session The session itself - * - * @return Session specific data for this session - */ -static void *newSession(FILTER *instance, SESSION *session) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = cache_session_data_create(cinstance, session); - - return csdata; -} - -/** - * A session has been closed. - * - * @param instance The cache instance data - * @param sdata The session data of the session being closed - */ -static void closeSession(FILTER *instance, void *sdata) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; -} - -/** - * Free the session data. - * - * @param instance The cache instance data - * @param sdata The session data of the session being closed - */ -static void freeSession(FILTER *instance, void *sdata) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - cache_session_data_free(csdata); -} - -/** - * Set the downstream component for this filter. - * - * @param instance The cache instance data - * @param sdata The session data of the session - * @param down The downstream filter or router - */ -static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *down) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - csdata->down = *down; -} - -/** - * Set the upstream component for this filter. - * - * @param instance The cache instance data - * @param sdata The session data of the session - * @param up The upstream filter or router - */ -static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *up) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - csdata->up = *up; -} - -/** - * A request on its way to a backend is delivered to this function. - * - * @param instance The filter instance data - * @param sdata The filter session data - * @param buffer Buffer containing an MySQL protocol packet. - */ -static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - uint8_t *data = GWBUF_DATA(packet); - - // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING - ss_dassert(GWBUF_IS_CONTIGUOUS(packet)); - ss_dassert(GWBUF_LENGTH(packet) >= MYSQL_HEADER_LEN + 1); - ss_dassert(MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN == GWBUF_LENGTH(packet)); - - bool use_default = true; - - cache_response_state_reset(&csdata->res); - csdata->state = CACHE_IGNORING_RESPONSE; - - int rv; - - switch ((int)MYSQL_GET_COMMAND(data)) - { - case MYSQL_COM_INIT_DB: - { - ss_dassert(!csdata->use_db); - size_t len = MYSQL_GET_PACKET_LEN(data) - 1; // Remove the command byte. - csdata->use_db = MXS_MALLOC(len + 1); - - if (csdata->use_db) - { - memcpy(csdata->use_db, data + MYSQL_HEADER_LEN + 1, len); - csdata->use_db[len] = 0; - csdata->state = CACHE_EXPECTING_USE_RESPONSE; - } - else - { - // Memory allocation failed. We need to remove the default database to - // prevent incorrect cache entries, since we won't know what the - // default db is. But we only need to do that if "USE " really - // succeeds. The right thing will happen by itself in - // handle_expecting_use_response(); if OK is returned, default_db will - // become NULL, if ERR, default_db will not be changed. - } - } - break; - - case MYSQL_COM_QUERY: - { - // We do not care whether the query was fully parsed or not. - // If a query cannot be fully parsed, the worst thing that can - // happen is that caching is not used, even though it would be - // possible. - if (qc_get_operation(packet) == QUERY_OP_SELECT) - { - SESSION *session = csdata->session; - - if ((session_is_autocommit(session) && !session_trx_is_active(session)) || - session_trx_is_read_only(session)) - { - if (cache_rules_should_store(cinstance->rules, csdata->default_db, packet)) - { - if (cache_rules_should_use(cinstance->rules, csdata->session)) - { - GWBUF *result; - use_default = !route_using_cache(csdata, packet, &result); - - if (use_default) - { - csdata->state = CACHE_EXPECTING_RESPONSE; - } - else - { - csdata->state = CACHE_EXPECTING_NOTHING; - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Using data from cache."); - } - gwbuf_free(packet); - DCB *dcb = csdata->session->client_dcb; - - // TODO: This is not ok. Any filters before this filter, will not - // TODO: see this data. - rv = dcb->func.write(dcb, result); - } - } - } - else - { - csdata->state = CACHE_IGNORING_RESPONSE; - } - } - else - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " - "storing to cache.", - session_is_autocommit(csdata->session) ? "ON" : "OFF", - session_trx_state_to_string(session_get_trx_state(csdata->session))); - } - } - } - break; - - default: - break; - } - } - - if (use_default) - { - rv = csdata->down.routeQuery(csdata->down.instance, csdata->down.session, packet); - } - - return rv; -} - -/** - * A response on its way to the client is delivered to this function. - * - * @param instance The filter instance data - * @param sdata The filter session data - * @param queue The query data - */ -static int clientReply(FILTER *instance, void *sdata, GWBUF *data) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - int rv; - - if (csdata->res.data) - { - gwbuf_append(csdata->res.data, data); - } - else - { - csdata->res.data = data; - } - - if (csdata->state != CACHE_IGNORING_RESPONSE) - { - if (gwbuf_length(csdata->res.data) > csdata->instance->config.max_resultset_size) - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Current size %uB of resultset, at least as much " - "as maximum allowed size %uKiB. Not caching.", - gwbuf_length(csdata->res.data), - csdata->instance->config.max_resultset_size / 1024); - } - - csdata->state = CACHE_IGNORING_RESPONSE; - } - } - - switch (csdata->state) - { - case CACHE_EXPECTING_FIELDS: - rv = handle_expecting_fields(csdata); - break; - - case CACHE_EXPECTING_NOTHING: - rv = handle_expecting_nothing(csdata); - break; - - case CACHE_EXPECTING_RESPONSE: - rv = handle_expecting_response(csdata); - break; - - case CACHE_EXPECTING_ROWS: - rv = handle_expecting_rows(csdata); - break; - - case CACHE_EXPECTING_USE_RESPONSE: - rv = handle_expecting_use_response(csdata); - break; - - case CACHE_IGNORING_RESPONSE: - rv = handle_ignoring_response(csdata); - break; - - default: - MXS_ERROR("Internal cache logic broken, unexpected state: %d", csdata->state); - ss_dassert(!true); - rv = send_upstream(csdata); - cache_response_state_reset(&csdata->res); - csdata->state = CACHE_IGNORING_RESPONSE; - } - - return rv; -} - -/** - * Diagnostics routine - * - * If csdata is NULL then print diagnostics on the instance as a whole, - * otherwise print diagnostics for the particular session. - * - * @param instance The filter instance - * @param fsession Filter session, may be NULL - * @param dcb The DCB for diagnostic output - */ -static void diagnostics(FILTER *instance, void *sdata, DCB *dcb) -{ - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - - dcb_printf(dcb, "Hello World from Cache!\n"); -} - - -/** - * Capability routine. - * - * @return The capabilities of the filter. - */ -static uint64_t getCapabilities(void) -{ - return RCAP_TYPE_TRANSACTION_TRACKING; -} - -// -// API END -// - -/** - * Reset cache response state - * - * @param state Pointer to object. - */ -static void cache_response_state_reset(CACHE_RESPONSE_STATE *state) -{ - state->data = NULL; - state->n_totalfields = 0; - state->n_fields = 0; - state->n_rows = 0; - state->offset = 0; -} - -/** - * Create cache session data - * - * @param instance The cache instance this data is associated with. - * - * @return Session data or NULL if creation fails. - */ -static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, - SESSION* session) -{ - CACHE_SESSION_DATA *data = (CACHE_SESSION_DATA*)MXS_CALLOC(1, sizeof(CACHE_SESSION_DATA)); - - if (data) - { - char *default_db = NULL; - - ss_dassert(session->client_dcb); - ss_dassert(session->client_dcb->data); - MYSQL_session *mysql_session = (MYSQL_session*)session->client_dcb->data; - - if (mysql_session->db[0] != 0) - { - default_db = MXS_STRDUP(mysql_session->db); - } - - if ((mysql_session->db[0] == 0) || default_db) - { - data->instance = instance; - data->api = instance->module->api; - data->storage = instance->storage; - data->session = session; - data->state = CACHE_EXPECTING_NOTHING; - data->default_db = default_db; - } - else - { - MXS_FREE(data); - data = NULL; - } - } - - return data; -} - -/** - * Free cache session data. - * - * @param A cache session data previously allocated using session_data_create(). - */ -static void cache_session_data_free(CACHE_SESSION_DATA* data) -{ - if (data) - { - // In normal circumstances, only data->default_db may be non-NULL at - // this point. However, if the authentication with the backend fails - // and the session is closed, data->use_db may be non-NULL. - MXS_FREE(data->use_db); - MXS_FREE(data->default_db); - MXS_FREE(data); - } -} - -/** - * Called when resultset field information is handled. - * - * @param csdata The cache session data. - */ -static int handle_expecting_fields(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_FIELDS); - ss_dassert(csdata->res.data); - - int rv = 1; - - bool insufficient = false; - - size_t buflen = gwbuf_length(csdata->res.data); - - while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) - { - uint8_t header[MYSQL_HEADER_LEN + 1]; - gwbuf_copy_data(csdata->res.data, csdata->res.offset, MYSQL_HEADER_LEN + 1, header); - - size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); - - if (csdata->res.offset + packetlen <= buflen) - { - // We have at least one complete packet. - int command = (int)MYSQL_GET_COMMAND(header); - - switch (command) - { - case 0xfe: // EOF, the one after the fields. - csdata->res.offset += packetlen; - csdata->state = CACHE_EXPECTING_ROWS; - rv = handle_expecting_rows(csdata); - break; - - default: // Field information. - csdata->res.offset += packetlen; - ++csdata->res.n_fields; - ss_dassert(csdata->res.n_fields <= csdata->res.n_totalfields); - break; - } - } - else - { - // We need more data - insufficient = true; - } - } - - return rv; -} - -/** - * Called when data is received (even if nothing is expected) from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_nothing(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_NOTHING); - ss_dassert(csdata->res.data); - MXS_ERROR("Received data from the backend althoug we were expecting nothing."); - ss_dassert(!true); - - return send_upstream(csdata); -} - -/** - * Called when a response is received from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_RESPONSE); - ss_dassert(csdata->res.data); - - int rv = 1; - - size_t buflen = gwbuf_length(csdata->res.data); - - if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. - { - // Reserve enough space to accomodate for the largest length encoded integer, - // which is type field + 8 bytes. - uint8_t header[MYSQL_HEADER_LEN + 1 + 8]; - gwbuf_copy_data(csdata->res.data, 0, MYSQL_HEADER_LEN + 1, header); - - switch ((int)MYSQL_GET_COMMAND(header)) - { - case 0x00: // OK - case 0xff: // ERR - store_result(csdata); - - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - break; - - case 0xfb: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - break; - - default: - if (csdata->res.n_totalfields != 0) - { - // We've seen the header and have figured out how many fields there are. - csdata->state = CACHE_EXPECTING_FIELDS; - rv = handle_expecting_fields(csdata); - } - else - { - // leint_bytes() returns the length of the int type field + the size of the - // integer. - size_t n_bytes = leint_bytes(&header[4]); - - if (MYSQL_HEADER_LEN + n_bytes <= buflen) - { - // Now we can figure out how many fields there are, but first we - // need to copy some more data. - gwbuf_copy_data(csdata->res.data, - MYSQL_HEADER_LEN + 1, n_bytes - 1, &header[MYSQL_HEADER_LEN + 1]); - - csdata->res.n_totalfields = leint_value(&header[4]); - csdata->res.offset = MYSQL_HEADER_LEN + n_bytes; - - csdata->state = CACHE_EXPECTING_FIELDS; - rv = handle_expecting_fields(csdata); - } - else - { - // We need more data. We will be called again, when data is available. - } - } - break; - } - } - - return rv; -} - -/** - * Called when resultset rows are handled. - * - * @param csdata The cache session data. - */ -static int handle_expecting_rows(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_ROWS); - ss_dassert(csdata->res.data); - - int rv = 1; - - bool insufficient = false; - - size_t buflen = gwbuf_length(csdata->res.data); - - while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) - { - uint8_t header[MYSQL_HEADER_LEN + 1]; - gwbuf_copy_data(csdata->res.data, csdata->res.offset, MYSQL_HEADER_LEN + 1, header); - - size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); - - if (csdata->res.offset + packetlen <= buflen) - { - // We have at least one complete packet. - int command = (int)MYSQL_GET_COMMAND(header); - - switch (command) - { - case 0xfe: // EOF, the one after the rows. - csdata->res.offset += packetlen; - ss_dassert(csdata->res.offset == buflen); - - store_result(csdata); - - rv = send_upstream(csdata); - csdata->state = CACHE_EXPECTING_NOTHING; - break; - - case 0xfb: // NULL - default: // length-encoded-string - csdata->res.offset += packetlen; - ++csdata->res.n_rows; - - if (csdata->res.n_rows > csdata->instance->config.max_resultset_rows) - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Max rows %lu reached, not caching result.", csdata->res.n_rows); - } - rv = send_upstream(csdata); - csdata->res.offset = buflen; // To abort the loop. - csdata->state = CACHE_IGNORING_RESPONSE; - } - break; - } - } - else - { - // We need more data - insufficient = true; - } - } - - return rv; -} - -/** - * Called when a response to a "USE db" is received from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_use_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_USE_RESPONSE); - ss_dassert(csdata->res.data); - - int rv = 1; - - size_t buflen = gwbuf_length(csdata->res.data); - - if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. - { - uint8_t command; - - gwbuf_copy_data(csdata->res.data, MYSQL_HEADER_LEN, 1, &command); - - switch (command) - { - case 0x00: // OK - // In case csdata->use_db could not be allocated in routeQuery(), we will - // in fact reset the default db here. That's ok as it will prevent broken - // entries in the cache. - MXS_FREE(csdata->default_db); - csdata->default_db = csdata->use_db; - csdata->use_db = NULL; - break; - - case 0xff: // ERR - MXS_FREE(csdata->use_db); - csdata->use_db = NULL; - break; - - default: - MXS_ERROR("\"USE %s\" received unexpected server response %d.", - csdata->use_db ? csdata->use_db : "", command); - MXS_FREE(csdata->default_db); - MXS_FREE(csdata->use_db); - csdata->default_db = NULL; - csdata->use_db = NULL; - } - - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - } - - return rv; -} - -/** - * Called when all data from the server is ignored. - * - * @param csdata The cache session data. - */ -static int handle_ignoring_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_IGNORING_RESPONSE); - ss_dassert(csdata->res.data); - - return send_upstream(csdata); -} - -/** - * Processes the cache params - * - * @param options Options as passed to the filter. - * @param params Parameters as passed to the filter. - * @param config Pointer to config instance where params will be stored. - * - * @return True if all parameters could be processed, false otherwise. - */ -static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config) -{ - bool error = false; - - for (int i = 0; params[i]; ++i) - { - const FILTER_PARAMETER *param = params[i]; - - if (strcmp(param->name, "max_resultset_rows") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->max_resultset_rows = v; - } - else - { - config->max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; - } - } - else if (strcmp(param->name, "max_resultset_size") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->max_resultset_size = v * 1024; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", param->name); - error = true; - } - } - else if (strcmp(param->name, "rules") == 0) - { - if (*param->value == '/') - { - config->rules = MXS_STRDUP(param->value); - } - else - { - const char *datadir = get_datadir(); - size_t len = strlen(datadir) + 1 + strlen(param->value) + 1; - - char *rules = MXS_MALLOC(len); - - if (rules) - { - sprintf(rules, "%s/%s", datadir, param->value); - config->rules = rules; - } - } - - if (!config->rules) - { - error = true; - } - } - else if (strcmp(param->name, "storage_options") == 0) - { - config->storage_options = MXS_STRDUP(param->value); - - if (config->storage_options) - { - int argc = 1; - char *arg = config->storage_options; - - while ((arg = strchr(config->storage_options, ','))) - { - ++argc; - } - - config->storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); - - if (config->storage_argv) - { - config->storage_argc = argc; - - int i = 0; - arg = config->storage_options; - config->storage_argv[i++] = arg; - - while ((arg = strchr(config->storage_options, ','))) - { - *arg = 0; - ++arg; - config->storage_argv[i++] = arg; - } - - config->storage_argv[i] = NULL; - } - else - { - MXS_FREE(config->storage_options); - config->storage_options = NULL; - } - } - else - { - error = true; - } - } - else if (strcmp(param->name, "storage") == 0) - { - config->storage = param->value; - } - else if (strcmp(param->name, "ttl") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->ttl = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", param->name); - error = true; - } - } - else if (strcmp(param->name, "debug") == 0) - { - int v = atoi(param->value); - - if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) - { - config->debug = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be between %d and %d, inclusive.", - param->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); - error = true; - } - } - else if (!filter_standard_parameter(params[i]->name)) - { - MXS_ERROR("Unknown configuration entry '%s'.", param->name); - error = true; - } - } - - return !error; -} - -/** - * Route a query via the cache. - * - * @param csdata Session data - * @param key A SELECT packet. - * @param value The result. - * @return True if the query was satisfied from the query. - */ -static bool route_using_cache(CACHE_SESSION_DATA *csdata, - const GWBUF *query, - GWBUF **value) -{ - cache_result_t result = csdata->api->getKey(csdata->storage, csdata->default_db, query, csdata->key); - - if (result == CACHE_RESULT_OK) - { - result = csdata->api->getValue(csdata->storage, csdata->key, value); - } - else - { - MXS_ERROR("Could not create cache key."); - } - - return result == CACHE_RESULT_OK; -} - -/** - * Send data upstream. - * - * @param csdata Session data - * - * @return Whatever the upstream returns. - */ -static int send_upstream(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->res.data != NULL); - - int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, csdata->res.data); - csdata->res.data = NULL; - - return rv; -} - -/** - * Store the data. - * - * @param csdata Session data - */ -static void store_result(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->res.data); - - GWBUF *data = gwbuf_make_contiguous(csdata->res.data); - - if (data) - { - csdata->res.data = data; - - cache_result_t result = csdata->api->putValue(csdata->storage, - csdata->key, - csdata->res.data); - - if (result != CACHE_RESULT_OK) - { - MXS_ERROR("Could not store cache item."); - } - } -} diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc new file mode 100644 index 000000000..cefdde9a7 --- /dev/null +++ b/server/modules/filter/cache/cache.cc @@ -0,0 +1,228 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#define MXS_MODULE_NAME "cache" +#include "cache.h" +#include +#include +#include +#include "storagefactory.h" +#include "storage.h" + +// Initial size of hashtable used for storing keys of queries that +// are being fetches. +#define CACHE_PENDING_ITEMS 50 + +/** + * Hashes a cache key to an integer. + * + * @param key Pointer to cache key. + * + * @returns Corresponding integer hash. + */ +static int hash_of_key(const void* key) +{ + int hash = 0; + + const char* i = (const char*)key; + const char* end = i + CACHE_KEY_MAXLEN; + + while (i < end) + { + int c = *i; + hash = c + (hash << 6) + (hash << 16) - hash; + ++i; + } + + return hash; +} + +static int hashfn(const void* address) +{ + // TODO: Hash the address; pointers are not evenly distributed. + return (long)address; +} + +static int hashcmp(const void* address1, const void* address2) +{ + return (long)address2 - (long)address1; +} + + +Cache::Cache(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending) + : m_zName(zName) + , m_config(config) + , m_pRules(pRules) + , m_pFactory(pFactory) + , m_pStorage(pStorage) + , m_pPending(pPending) +{ + cache_config_reset(config); +} + +Cache::~Cache() +{ + // TODO: Free everything. + ss_dassert(false); +} + +//static +bool Cache::Create(const CACHE_CONFIG& config, + CACHE_RULES** ppRules, + StorageFactory** ppFactory, + HASHTABLE** ppPending) +{ + CACHE_RULES* pRules = NULL; + HASHTABLE* pPending = NULL; + StorageFactory* pFactory = NULL; + + if (config.rules) + { + pRules = cache_rules_load(config.rules, config.debug); + } + else + { + pRules = cache_rules_create(config.debug); + } + + if (pRules) + { + pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); + + if (pPending) + { + pFactory = StorageFactory::Open(config.storage); + + if (!pFactory) + { + MXS_ERROR("Could not open storage factory '%s'.", config.storage); + } + } + } + + bool rv = (pRules && pPending && pFactory); + + if (rv) + { + *ppRules = pRules; + *ppPending = pPending; + *ppFactory = pFactory; + } + else + { + cache_rules_free(pRules); + hashtable_free(pPending); + delete pFactory; + } + + return rv; +} + +bool Cache::shouldStore(const char* zDefaultDb, const GWBUF* pQuery) +{ + return cache_rules_should_store(m_pRules, zDefaultDb, pQuery); +} + +bool Cache::shouldUse(const SESSION* pSession) +{ + return cache_rules_should_use(m_pRules, pSession); +} + +bool Cache::mustRefresh(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hash_of_key(pKey); + + spinlock_acquire(&m_lockPending); + // TODO: Remove the internal locking of hashtable. The internal + // TODO: locking is no good if you need transactional behaviour. + // TODO: Now we lock twice. + void *pValue = hashtable_fetch(m_pPending, (void*)pKey); + if (!pValue) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(m_pPending, (void*)pKey, (void*)pSessionCache); + } + spinlock_release(&m_lockPending); + + return pValue == NULL; +} + +void Cache::refreshed(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hash_of_key(pKey); + + spinlock_acquire(&m_lockPending); + ss_dassert(hashtable_fetch(m_pPending, (void*)pKey) == pSessionCache); + ss_debug(int n =) hashtable_delete(m_pPending, (void*)pKey); + ss_dassert(n == 1); + spinlock_release(&m_lockPending); +} + +cache_result_t Cache::getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey) +{ + return m_pStorage->getKey(zDefaultDb, pQuery, pKey); +} + +cache_result_t Cache::getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue) +{ + return m_pStorage->getValue(pKey, flags, ppValue); +} + +cache_result_t Cache::putValue(const char* pKey, + const GWBUF* pValue) +{ + return m_pStorage->putValue(pKey, pValue); +} + +cache_result_t Cache::delValue(const char* pKey) +{ + return m_pStorage->delValue(pKey); +} + +// protected +long Cache::hashOfKey(const char* pKey) +{ + return hash_of_key(pKey); +} + +// protected +bool Cache::mustRefresh(long key, const SessionCache* pSessionCache) +{ + void *pValue = hashtable_fetch(m_pPending, (void*)key); + if (!pValue) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(m_pPending, (void*)key, (void*)pSessionCache); + } + + return !pValue; +} + +// protected +void Cache::refreshed(long key, const SessionCache* pSessionCache) +{ + ss_dassert(hashtable_fetch(m_pPending, (void*)key) == pSessionCache); + ss_debug(int n =) hashtable_delete(m_pPending, (void*)key); + ss_dassert(n == 1); +} + + diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index 5b725f147..8de701fa9 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -1,6 +1,4 @@ #pragma once -#ifndef _MAXSCALE_FILTER_CACHE_CACHE_H -#define _MAXSCALE_FILTER_CACHE_CACHE_H /* * Copyright (c) 2016 MariaDB Corporation Ab * @@ -15,31 +13,95 @@ */ #include -#include +#include +#include +#include +#include "cachefilter.h" +#include "cache_storage_api.h" -MXS_BEGIN_DECLS +class SessionCache; -#define CACHE_DEBUG_NONE 0 /* 0b00000 */ -#define CACHE_DEBUG_MATCHING 1 /* 0b00001 */ -#define CACHE_DEBUG_NON_MATCHING 2 /* 0b00010 */ -#define CACHE_DEBUG_USE 4 /* 0b00100 */ -#define CACHE_DEBUG_NON_USE 8 /* 0b01000 */ -#define CACHE_DEBUG_DECISIONS 16 /* 0b10000 */ +class Cache +{ +public: + ~Cache(); -#define CACHE_DEBUG_RULES (CACHE_DEBUG_MATCHING | CACHE_DEBUG_NON_MATCHING) -#define CACHE_DEBUG_USAGE (CACHE_DEBUG_USE | CACHE_DEBUG_NON_USE) -#define CACHE_DEBUG_MIN CACHE_DEBUG_NONE -#define CACHE_DEBUG_MAX (CACHE_DEBUG_RULES | CACHE_DEBUG_USAGE) + /** + * Returns whether the results of a particular query should be stored. + * + * @param zDefaultDb The current default database. + * @param pQuery Buffer containing a SELECT. + * + * @return True of the result should be cached. + */ + bool shouldStore(const char* zDefaultDb, const GWBUF* pQuery); -// Count -#define CACHE_DEFAULT_MAX_RESULTSET_ROWS UINT_MAX -// Bytes -#define CACHE_DEFAULT_MAX_RESULTSET_SIZE 64 * 1024 -// Seconds -#define CACHE_DEFAULT_TTL 10 -// Integer value -#define CACHE_DEFAULT_DEBUG 0 + /** + * Returns whether cached results should be used. + * + * @param pSession The session in question. + * + * @return True of cached results should be used. + */ + bool shouldUse(const SESSION* pSession); -MXS_END_DECLS + /** + * Specifies whether a particular SessioCache should refresh the data. + * + * @param pKey The hashed key for a query. + * @param pSessionCache The session cache asking. + * + * @return True, if the session cache should refresh the data. + */ + virtual bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); -#endif + /** + * To inform the cache that a particular item has been updated upon request. + * + * @param pKey The hashed key for a query. + * @param pSessionCache The session cache informing. + */ + virtual void refreshed(const char* pKey, const SessionCache* pSessionCache); + + const CACHE_CONFIG& config() const { return m_config; } + + cache_result_t getKey(const char* zDefaultDb, const GWBUF* pQuery, char* pKey); + + cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppValue); + + cache_result_t putValue(const char* pKey, const GWBUF* pValue); + + cache_result_t delValue(const char* pKey); + +protected: + Cache(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending); + + static bool Create(const CACHE_CONFIG& config, + CACHE_RULES** ppRules, + StorageFactory** ppFactory, + HASHTABLE** ppPending); + + long hashOfKey(const char* pKey); + + bool mustRefresh(long key, const SessionCache* pSessionCache); + + void refreshed(long key, const SessionCache* pSessionCache); + +private: + Cache(const Cache&); + Cache& operator = (const Cache&); + +protected: + const char* m_zName; // The name of the instance; the section name in the config. + CACHE_CONFIG m_config; // The configuration of the cache instance. + CACHE_RULES* m_pRules; // The rules of the cache instance. + StorageFactory* m_pFactory; // The storage factory. + Storage* m_pStorage; // The storage instance to use. + HASHTABLE* m_pPending; // Pending items; being fetched from the backend. + SPINLOCK m_lockPending; // Lock used for protecting 'pending'. +}; diff --git a/server/modules/filter/cache/cache_storage_api.h b/server/modules/filter/cache/cache_storage_api.h index a413afe54..e5bfeacb5 100644 --- a/server/modules/filter/cache/cache_storage_api.h +++ b/server/modules/filter/cache/cache_storage_api.h @@ -26,10 +26,17 @@ typedef enum cache_result { CACHE_RESULT_OK, CACHE_RESULT_NOT_FOUND, + CACHE_RESULT_STALE, CACHE_RESULT_OUT_OF_RESOURCES, CACHE_RESULT_ERROR } cache_result_t; +typedef enum cache_flags +{ + CACHE_FLAGS_NONE = 0x00, + CACHE_FLAGS_INCLUDE_STALE = 0x01, +} cache_flags_t; + typedef void* CACHE_STORAGE; enum @@ -88,14 +95,18 @@ typedef struct cache_storage_api * * @param storage Pointer to a CACHE_STORAGE. * @param key A key generated with getKey. + * @param flags Mask of cache_flags_t values. * @param result Pointer to variable that after a successful return will * point to a GWBUF. * @return CACHE_RESULT_OK if item was found, + * CACHE_RESULT_STALE if CACHE_FLAGS_INCLUDE_STALE was specified in + * flags and the item was found but stale, * CACHE_RESULT_NOT_FOUND if item was not found (which may be because * the ttl was reached), or some other error code. */ cache_result_t (*getValue)(CACHE_STORAGE* storage, const char* key, + uint32_t flags, GWBUF** result); /** @@ -112,6 +123,17 @@ typedef struct cache_storage_api cache_result_t (*putValue)(CACHE_STORAGE* storage, const char* key, const GWBUF* value); + + /** + * Delete a value from the cache. + * + * @param storage Pointer to a CACHE_STORAGE. + * @param key A key generated with getKey. + * @return CACHE_RESULT_OK if item was successfully deleted. Note that + * CACHE_RESULT_OK may be returned also if the entry was not present. + */ + cache_result_t (*delValue)(CACHE_STORAGE* storage, + const char* key); } CACHE_STORAGE_API; #define CACHE_STORAGE_ENTRY_POINT "CacheGetStorageAPI" diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc new file mode 100644 index 000000000..ac03179a7 --- /dev/null +++ b/server/modules/filter/cache/cachefilter.cc @@ -0,0 +1,489 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#define MXS_MODULE_NAME "cache" +#include "cachefilter.h" +#include +#include +#include +#include +#include "cachemt.h" +#include "sessioncache.h" + +static char VERSION_STRING[] = "V1.0.0"; + +static const CACHE_CONFIG DEFAULT_CONFIG = +{ + CACHE_DEFAULT_MAX_RESULTSET_ROWS, + CACHE_DEFAULT_MAX_RESULTSET_SIZE, + NULL, + NULL, + NULL, + NULL, + 0, + CACHE_DEFAULT_TTL, + CACHE_DEFAULT_DEBUG +}; + +static FILTER* createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams); +static void* newSession(FILTER* pInstance, SESSION* pSession); +static void closeSession(FILTER* pInstance, void* pSessionData); +static void freeSession(FILTER* pInstance, void* pSessionData); +static void setDownstream(FILTER* pInstance, void* pSessionData, DOWNSTREAM* pDownstream); +static void setUpstream(FILTER* pInstance, void* pSessionData, UPSTREAM* pUpstream); +static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket); +static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket); +static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb); +static uint64_t getCapabilities(void); + +static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config); + +// +// Global symbols of the Module +// + +MODULE_INFO info = +{ + MODULE_API_FILTER, + MODULE_IN_DEVELOPMENT, + FILTER_VERSION, + "A caching filter that is capable of caching and returning cached data." +}; + +extern "C" char *version() +{ + return VERSION_STRING; +} + +/** + * The module initialization functions, called when the module has + * been loaded. + */ +extern "C" void ModuleInit() +{ +} + +/** + * The module entry point function, called when the module is loaded. + * + * @return The module object. + */ +extern "C" FILTER_OBJECT *GetModuleObject() +{ + static FILTER_OBJECT object = + { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostics, + getCapabilities, + NULL, // destroyInstance + }; + + return &object; +}; + +// +// API Implementation BEGIN +// + +/** + * Create an instance of the cache filter for a particular service + * within MaxScale. + * + * @param zName The name of the instance (as defined in the config file). + * @param pzOptions The options for this filter + * @param ppparams The array of name/value pair parameters for the filter + * + * @return The instance data for this new instance + */ +static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) +{ + Cache* pCache = NULL; + CACHE_CONFIG config = DEFAULT_CONFIG; + + if (process_params(pzOptions, ppParams, config)) + { + CPP_GUARD(pCache = CacheMT::Create(zName, config)); + + if (!pCache) + { + cache_config_finish(config); + } + } + + return reinterpret_cast(pCache); +} + +/** + * Associate a new session with this instance of the filter. + * + * @param pInstance The cache instance data + * @param pSession The session itself + * + * @return Session specific data for this session + */ +static void *newSession(FILTER* pInstance, SESSION* pSession) +{ + Cache* pCache = reinterpret_cast(pInstance); + + SessionCache* pSessionCache = NULL; + CPP_GUARD(pSessionCache = SessionCache::Create(pCache, pSession)); + + return pSessionCache; +} + +/** + * A session has been closed. + * + * @param pInstance The cache instance data + * @param pSessionData The session data of the session being closed + */ +static void closeSession(FILTER* pInstance, void* pSessionData) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + CPP_GUARD(pSessionCache->close()); +} + +/** + * Free the session data. + * + * @param pInstance The cache instance data + * @param pSessionData The session data of the session being closed + */ +static void freeSession(FILTER* pInstance, void* pSessionData) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + delete pSessionCache; +} + +/** + * Set the downstream component for this filter. + * + * @param pInstance The cache instance data + * @param pSessionData The session data of the session + * @param pDownstream The downstream filter or router + */ +static void setDownstream(FILTER* pInstance, void* pSessionData, DOWNSTREAM* pDownstream) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + CPP_GUARD(pSessionCache->setDownstream(pDownstream)); +} + +/** + * Set the upstream component for this filter. + * + * @param pInstance The cache instance data + * @param pSessionData The session data of the session + * @param pUpstream The upstream filter or router + */ +static void setUpstream(FILTER* pInstance, void* pSessionData, UPSTREAM* pUpstream) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + CPP_GUARD(pSessionCache->setUpstream(pUpstream)); +} + +/** + * A request on its way to a backend is delivered to this function. + * + * @param pInstance The filter instance data + * @param pSessionData The filter session data + * @param pPacket Buffer containing an MySQL protocol packet. + */ +static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + int rv = 0; + CPP_GUARD(rv = pSessionCache->routeQuery(pPacket)); + + return rv; +} + +/** + * A response on its way to the client is delivered to this function. + * + * @param pInstance The filter instance data + * @param pSessionData The filter session data + * @param pPacket The response data + */ +static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + int rv = 0; + CPP_GUARD(rv = pSessionCache->clientReply(pPacket)); + + return rv; +} + +/** + * Diagnostics routine + * + * If cpSessionData is NULL then print diagnostics on the instance as a whole, + * otherwise print diagnostics for the particular session. + * + * @param pInstance The filter instance + * @param pSessionData Filter session, may be NULL + * @param pDcb The DCB for diagnostic output + */ +static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb) +{ + SessionCache* pSessionCache = static_cast(pSessionData); + + CPP_GUARD(pSessionCache->diagnostics(pDcb)); +} + + +/** + * Capability routine. + * + * @return The capabilities of the filter. + */ +static uint64_t getCapabilities(void) +{ + return RCAP_TYPE_TRANSACTION_TRACKING; +} + +// +// API Implementation END +// + +/** + * Processes the cache params + * + * @param options Options as passed to the filter. + * @param params Parameters as passed to the filter. + * @param config Reference to config instance where params will be stored. + * + * @return True if all parameters could be processed, false otherwise. + */ +static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config) +{ + bool error = false; + + for (int i = 0; ppParams[i]; ++i) + { + const FILTER_PARAMETER *pParam = ppParams[i]; + + if (strcmp(pParam->name, "max_resultset_rows") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.max_resultset_rows = v; + } + else + { + config.max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; + } + } + else if (strcmp(pParam->name, "max_resultset_size") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.max_resultset_size = v * 1024; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "rules") == 0) + { + if (*pParam->value == '/') + { + config.rules = MXS_STRDUP(pParam->value); + } + else + { + const char* datadir = get_datadir(); + size_t len = strlen(datadir) + 1 + strlen(pParam->value) + 1; + + char *rules = (char*)MXS_MALLOC(len); + + if (rules) + { + sprintf(rules, "%s/%s", datadir, pParam->value); + config.rules = rules; + } + } + + if (!config.rules) + { + error = true; + } + } + else if (strcmp(pParam->name, "storage_options") == 0) + { + config.storage_options = MXS_STRDUP(pParam->value); + + if (config.storage_options) + { + int argc = 1; + char *arg = config.storage_options; + + while ((arg = strchr(config.storage_options, ','))) + { + ++argc; + } + + config.storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); + + if (config.storage_argv) + { + config.storage_argc = argc; + + int i = 0; + arg = config.storage_options; + config.storage_argv[i++] = arg; + + while ((arg = strchr(config.storage_options, ','))) + { + *arg = 0; + ++arg; + config.storage_argv[i++] = arg; + } + + config.storage_argv[i] = NULL; + } + else + { + MXS_FREE(config.storage_options); + config.storage_options = NULL; + } + } + else + { + error = true; + } + } + else if (strcmp(pParam->name, "storage") == 0) + { + config.storage = MXS_STRDUP(pParam->value); + + if (!config.storage) + { + error = true; + } + } + else if (strcmp(pParam->name, "ttl") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.ttl = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "debug") == 0) + { + int v = atoi(pParam->value); + + if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) + { + config.debug = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be between %d and %d, inclusive.", + pParam->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); + error = true; + } + } + else if (!filter_standard_parameter(pParam->name)) + { + MXS_ERROR("Unknown configuration entry '%s'.", pParam->name); + error = true; + } + } + + if (error) + { + cache_config_finish(config); + } + + return !error; +} + +/** + * Frees all data of a config object, but not the object itself + * + * @param pConfig Pointer to a config object. + */ +void cache_config_finish(CACHE_CONFIG& config) +{ + MXS_FREE(config.rules); + MXS_FREE(config.storage); + MXS_FREE(config.storage_options); + + for (int i = 0; i < config.storage_argc; ++i) + { + MXS_FREE(config.storage_argv[i]); + } + + config.max_resultset_rows = 0; + config.max_resultset_size = 0; + config.rules = NULL; + config.storage = NULL; + config.storage_options = NULL; + config.storage_argc = 0; + config.storage_argv = NULL; + config.ttl = 0; + config.debug = 0; +} + +/** + * Frees all data of a config object, and the object itself + * + * @param pConfig Pointer to a config object. + */ +void cache_config_free(CACHE_CONFIG* pConfig) +{ + if (pConfig) + { + cache_config_finish(*pConfig); + MXS_FREE(pConfig); + } +} + +/** + * Resets the data without freeing anything. + * + * @param config Reference to a config object. + */ +void cache_config_reset(CACHE_CONFIG& config) +{ + memset(&config, 0, sizeof(config)); +} diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h new file mode 100644 index 000000000..6fcdca1d1 --- /dev/null +++ b/server/modules/filter/cache/cachefilter.h @@ -0,0 +1,69 @@ +#pragma once +#ifndef _MAXSCALE_FILTER_CACHE_CACHE_H +#define _MAXSCALE_FILTER_CACHE_CACHE_H +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include +#include +#include +#include +#include "rules.h" + +class Storage; +class StorageFactory; + +#define CACHE_DEBUG_NONE 0 /* 0b00000 */ +#define CACHE_DEBUG_MATCHING 1 /* 0b00001 */ +#define CACHE_DEBUG_NON_MATCHING 2 /* 0b00010 */ +#define CACHE_DEBUG_USE 4 /* 0b00100 */ +#define CACHE_DEBUG_NON_USE 8 /* 0b01000 */ +#define CACHE_DEBUG_DECISIONS 16 /* 0b10000 */ + +#define CACHE_DEBUG_RULES (CACHE_DEBUG_MATCHING | CACHE_DEBUG_NON_MATCHING) +#define CACHE_DEBUG_USAGE (CACHE_DEBUG_USE | CACHE_DEBUG_NON_USE) +#define CACHE_DEBUG_MIN CACHE_DEBUG_NONE +#define CACHE_DEBUG_MAX (CACHE_DEBUG_RULES | CACHE_DEBUG_USAGE | CACHE_DEBUG_DECISIONS) + +// Count +#define CACHE_DEFAULT_MAX_RESULTSET_ROWS UINT_MAX +// Bytes +#define CACHE_DEFAULT_MAX_RESULTSET_SIZE 64 * 1024 +// Seconds +#define CACHE_DEFAULT_TTL 10 +// Integer value +#define CACHE_DEFAULT_DEBUG 0 + +typedef struct cache_config +{ + uint32_t max_resultset_rows; + uint32_t max_resultset_size; + char* rules; + char* storage; + char* storage_options; + char** storage_argv; + int storage_argc; + uint32_t ttl; + uint32_t debug; +} CACHE_CONFIG; + +void cache_config_finish(CACHE_CONFIG& config); +void cache_config_free(CACHE_CONFIG* pConfig); +void cache_config_reset(CACHE_CONFIG& config); + +#define CPP_GUARD(statement)\ + do { try { statement; } \ + catch (const std::exception& x) { MXS_ERROR("Caught standard exception: %s", x.what()); }\ + catch (...) { MXS_ERROR("Caught unknown exception."); } } while (false) + +#endif diff --git a/server/modules/filter/cache/cachemt.cc b/server/modules/filter/cache/cachemt.cc new file mode 100644 index 000000000..6fcc0e5cf --- /dev/null +++ b/server/modules/filter/cache/cachemt.cc @@ -0,0 +1,91 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include "cachemt.h" +#include +#include +#include "storage.h" +#include "storagefactory.h" + +CacheMT::CacheMT(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending) + : Cache(zName, config, pRules, pFactory, pStorage, pPending) +{ + spinlock_init(&m_lockPending); +} + +CacheMT::~CacheMT() +{ +} + +CacheMT* CacheMT::Create(const char* zName, CACHE_CONFIG& config) +{ + CacheMT* pCache = NULL; + + CACHE_RULES* pRules = NULL; + HASHTABLE* pPending = NULL; + StorageFactory* pFactory = NULL; + + if (Cache::Create(config, &pRules, &pFactory, &pPending)) + { + uint32_t ttl = config.ttl; + int argc = config.storage_argc; + char** argv = config.storage_argv; + + Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); + + if (pStorage) + { + CPP_GUARD(pCache = new CacheMT(zName, + config, + pRules, + pFactory, + pStorage, + pPending)); + + if (!pCache) + { + cache_rules_free(pRules); + hashtable_free(pPending); + delete pStorage; + delete pFactory; + } + } + } + + return pCache; +} + +bool CacheMT::mustRefresh(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hashOfKey(pKey); + + spinlock_acquire(&m_lockPending); + bool rv = Cache::mustRefresh(key, pSessionCache); + spinlock_release(&m_lockPending); + + return rv; +} + +void CacheMT::refreshed(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hashOfKey(pKey); + + spinlock_acquire(&m_lockPending); + Cache::refreshed(key, pSessionCache); + spinlock_release(&m_lockPending); +} diff --git a/server/modules/filter/cache/cachemt.h b/server/modules/filter/cache/cachemt.h new file mode 100644 index 000000000..315224a9e --- /dev/null +++ b/server/modules/filter/cache/cachemt.h @@ -0,0 +1,43 @@ +#pragma once +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include +#include "cache.h" + +class CacheMT : public Cache +{ +public: + ~CacheMT(); + + static CacheMT* Create(const char* zName, CACHE_CONFIG& config); + + bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); + + void refreshed(const char* pKey, const SessionCache* pSessionCache); + +private: + CacheMT(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending); + +private: + CacheMT(const CacheMT&); + CacheMT& operator = (const CacheMT&); + +private: + SPINLOCK m_lockPending; // Lock used for protecting 'pending'. +}; diff --git a/server/modules/filter/cache/rules.c b/server/modules/filter/cache/rules.cc similarity index 99% rename from server/modules/filter/cache/rules.c rename to server/modules/filter/cache/rules.cc index c4917aaa5..26d6ee95a 100644 --- a/server/modules/filter/cache/rules.c +++ b/server/modules/filter/cache/rules.cc @@ -20,7 +20,7 @@ #include #include #include -#include "cache.h" +#include "cachefilter.h" static const char KEY_ATTRIBUTE[] = "attribute"; static const char KEY_OP[] = "op"; @@ -41,8 +41,8 @@ static const char VALUE_OP_UNLIKE[] = "unlike"; struct cache_attribute_mapping { - const char* name; - int value; + const char* name; + cache_rule_attribute_t value; }; static struct cache_attribute_mapping cache_store_attributes[] = @@ -51,13 +51,13 @@ static struct cache_attribute_mapping cache_store_attributes[] = { VALUE_ATTRIBUTE_DATABASE, CACHE_ATTRIBUTE_DATABASE }, { VALUE_ATTRIBUTE_QUERY, CACHE_ATTRIBUTE_QUERY }, { VALUE_ATTRIBUTE_TABLE, CACHE_ATTRIBUTE_TABLE }, - { NULL, 0 } + { NULL, static_cast(0) } }; static struct cache_attribute_mapping cache_use_attributes[] = { { VALUE_ATTRIBUTE_USER, CACHE_ATTRIBUTE_USER }, - { NULL, 0 } + { NULL, static_cast(0) } }; static bool cache_rule_attribute_get(struct cache_attribute_mapping *mapping, @@ -567,6 +567,7 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut char *at = strchr(value, '@'); char *user = value; char *host; + char any[2]; // sizeof("%") if (at) { @@ -575,7 +576,8 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut } else { - host = "%"; + strcpy(any, "%"); + host = any; } if (dequote_mysql(user)) @@ -610,7 +612,7 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut // No wildcard, no need to use regexp. rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); - char *value = MXS_MALLOC(strlen(user) + 1 + strlen(host) + 1); + char *value = (char*)MXS_MALLOC(strlen(user) + 1 + strlen(host) + 1); if (rule && value) { @@ -827,7 +829,7 @@ static CACHE_RULE *cache_rule_create_simple_query(cache_rule_attribute_t attribu ss_dassert(attribute == CACHE_ATTRIBUTE_QUERY); ss_dassert((op == CACHE_OP_EQ) || (op == CACHE_OP_NEQ)); - CACHE_RULE *rule = MXS_CALLOC(1, sizeof(CACHE_RULE)); + CACHE_RULE *rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); char *value = MXS_STRDUP(cvalue); if (rule && value) diff --git a/server/modules/filter/cache/sessioncache.cc b/server/modules/filter/cache/sessioncache.cc new file mode 100644 index 000000000..c1c91f666 --- /dev/null +++ b/server/modules/filter/cache/sessioncache.cc @@ -0,0 +1,661 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#define MXS_MODULE_NAME "cache" +#include "sessioncache.h" +#include +#include +#include +#include +#include "storage.h" + +SessionCache::SessionCache(Cache* pCache, SESSION* pSession, char* zDefaultDb) + : m_state(CACHE_EXPECTING_NOTHING) + , m_pCache(pCache) + , m_pSession(pSession) + , m_zDefaultDb(zDefaultDb) + , m_zUseDb(NULL) + , m_refreshing(false) +{ + memset(&m_down, 0, sizeof(m_down)); + memset(&m_up, 0, sizeof(m_up)); + memset(m_key, 0, CACHE_KEY_MAXLEN); + + reset_response_state(); +} + +SessionCache::~SessionCache() +{ + MXS_FREE(m_zUseDb); + MXS_FREE(m_zDefaultDb); +} + +//static +SessionCache* SessionCache::Create(Cache* pCache, SESSION* pSession) +{ + SessionCache* pSessionCache = NULL; + + ss_dassert(pSession->client_dcb); + ss_dassert(pSession->client_dcb->data); + + MYSQL_session *pMysqlSession = (MYSQL_session*)pSession->client_dcb->data; + char* zDefaultDb = NULL; + + if (pMysqlSession->db[0] != 0) + { + zDefaultDb = MXS_STRDUP(pMysqlSession->db); + } + + if ((pMysqlSession->db[0] == 0) || zDefaultDb) + { + pSessionCache = new (std::nothrow) SessionCache(pCache, pSession, zDefaultDb); + + if (!pSessionCache) + { + MXS_FREE(zDefaultDb); + } + } + + return pSessionCache; +} + +void SessionCache::close() +{ +} + +void SessionCache::setDownstream(DOWNSTREAM* pDown) +{ + m_down = *pDown; +} + +void SessionCache::setUpstream(UPSTREAM* pUp) +{ + m_up = *pUp; +} + +int SessionCache::routeQuery(GWBUF* pPacket) +{ + uint8_t* pData = static_cast(GWBUF_DATA(pPacket)); + + // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING + ss_dassert(GWBUF_IS_CONTIGUOUS(pPacket)); + ss_dassert(GWBUF_LENGTH(pPacket) >= MYSQL_HEADER_LEN + 1); + ss_dassert(MYSQL_GET_PACKET_LEN(pData) + MYSQL_HEADER_LEN == GWBUF_LENGTH(pPacket)); + + bool fetch_from_server = true; + + reset_response_state(); + m_state = CACHE_IGNORING_RESPONSE; + + int rv; + + switch ((int)MYSQL_GET_COMMAND(pData)) + { + case MYSQL_COM_INIT_DB: + { + ss_dassert(!m_zUseDb); + size_t len = MYSQL_GET_PACKET_LEN(pData) - 1; // Remove the command byte. + m_zUseDb = (char*)MXS_MALLOC(len + 1); + + if (m_zUseDb) + { + memcpy(m_zUseDb, (char*)(pData + MYSQL_HEADER_LEN + 1), len); + m_zUseDb[len] = 0; + m_state = CACHE_EXPECTING_USE_RESPONSE; + } + else + { + // Memory allocation failed. We need to remove the default database to + // prevent incorrect cache entries, since we won't know what the + // default db is. But we only need to do that if "USE " really + // succeeds. The right thing will happen by itself in + // handle_expecting_use_response(); if OK is returned, default_db will + // become NULL, if ERR, default_db will not be changed. + } + } + break; + + case MYSQL_COM_QUERY: + { + // We do not care whether the query was fully parsed or not. + // If a query cannot be fully parsed, the worst thing that can + // happen is that caching is not used, even though it would be + // possible. + if (qc_get_operation(pPacket) == QUERY_OP_SELECT) + { + SESSION *session = m_pSession; + + if ((session_is_autocommit(session) && !session_trx_is_active(session)) || + session_trx_is_read_only(session)) + { + if (m_pCache->shouldStore(m_zDefaultDb, pPacket)) + { + if (m_pCache->shouldUse(m_pSession)) + { + GWBUF* pResponse; + cache_result_t result = get_cached_response(pPacket, &pResponse); + + switch (result) + { + case CACHE_RESULT_STALE: + { + // The value was found, but it was stale. Now we need to + // figure out whether somebody else is already fetching it. + + if (m_pCache->mustRefresh(m_key, this)) + { + // We were the first ones who hit the stale item. It's + // our responsibility now to fetch it. + if (log_decisions()) + { + MXS_NOTICE("Cache data is stale, fetching fresh from server."); + } + m_refreshing = true; + fetch_from_server = true; + break; + } + else + { + // Somebody is already fetching the new value. So, let's + // use the stale value. No point in hitting the server twice. + if (log_decisions()) + { + MXS_NOTICE("Cache data is stale but returning it, fresh " + "data is being fetched already."); + } + fetch_from_server = false; + } + } + break; + + case CACHE_RESULT_OK: + if (log_decisions()) + { + MXS_NOTICE("Using fresh data from cache."); + } + fetch_from_server = false; + break; + + default: + fetch_from_server = true; + } + + if (fetch_from_server) + { + m_state = CACHE_EXPECTING_RESPONSE; + } + else + { + m_state = CACHE_EXPECTING_NOTHING; + gwbuf_free(pPacket); + DCB *dcb = m_pSession->client_dcb; + + // TODO: This is not ok. Any filters before this filter, will not + // TODO: see this data. + rv = dcb->func.write(dcb, pResponse); + } + } + } + else + { + m_state = CACHE_IGNORING_RESPONSE; + } + } + else + { + if (log_decisions()) + { + MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " + "storing to cache.", + session_is_autocommit(m_pSession) ? "ON" : "OFF", + session_trx_state_to_string(session_get_trx_state(m_pSession))); + } + } + } + break; + + default: + break; + } + } + + if (fetch_from_server) + { + rv = m_down.routeQuery(m_down.instance, m_down.session, pPacket); + } + + return rv; +} + +int SessionCache::clientReply(GWBUF* pData) +{ + int rv; + + if (m_res.pData) + { + gwbuf_append(m_res.pData, pData); + } + else + { + m_res.pData = pData; + } + + if (m_state != CACHE_IGNORING_RESPONSE) + { + if (gwbuf_length(m_res.pData) > m_pCache->config().max_resultset_size) + { + if (log_decisions()) + { + MXS_NOTICE("Current size %uB of resultset, at least as much " + "as maximum allowed size %uKiB. Not caching.", + gwbuf_length(m_res.pData), + m_pCache->config().max_resultset_size / 1024); + } + + m_state = CACHE_IGNORING_RESPONSE; + } + } + + switch (m_state) + { + case CACHE_EXPECTING_FIELDS: + rv = handle_expecting_fields(); + break; + + case CACHE_EXPECTING_NOTHING: + rv = handle_expecting_nothing(); + break; + + case CACHE_EXPECTING_RESPONSE: + rv = handle_expecting_response(); + break; + + case CACHE_EXPECTING_ROWS: + rv = handle_expecting_rows(); + break; + + case CACHE_EXPECTING_USE_RESPONSE: + rv = handle_expecting_use_response(); + break; + + case CACHE_IGNORING_RESPONSE: + rv = handle_ignoring_response(); + break; + + default: + MXS_ERROR("Internal cache logic broken, unexpected state: %d", m_state); + ss_dassert(!true); + rv = send_upstream(); + reset_response_state(); + m_state = CACHE_IGNORING_RESPONSE; + } + + return rv; +} + +void SessionCache::diagnostics(DCB* pDcb) +{ + dcb_printf(pDcb, "Hello World from Cache!\n"); +} + +/** + * Called when resultset field information is handled. + */ +int SessionCache::handle_expecting_fields() +{ + ss_dassert(m_state == CACHE_EXPECTING_FIELDS); + ss_dassert(m_res.pData); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(m_res.pData); + + while (!insufficient && (buflen - m_res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MYSQL_HEADER_LEN + 1]; + gwbuf_copy_data(m_res.pData, m_res.offset, MYSQL_HEADER_LEN + 1, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (m_res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xfe: // EOF, the one after the fields. + m_res.offset += packetlen; + m_state = CACHE_EXPECTING_ROWS; + rv = handle_expecting_rows(); + break; + + default: // Field information. + m_res.offset += packetlen; + ++m_res.nFields; + ss_dassert(m_res.nFields <= m_res.nTotalFields); + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when data is received (even if nothing is expected) from the server. + */ +int SessionCache::handle_expecting_nothing() +{ + ss_dassert(m_state == CACHE_EXPECTING_NOTHING); + ss_dassert(m_res.pData); + MXS_ERROR("Received data from the backend althoug we were expecting nothing."); + ss_dassert(!true); + + return send_upstream(); +} + +/** + * Called when a response is received from the server. + */ +int SessionCache::handle_expecting_response() +{ + ss_dassert(m_state == CACHE_EXPECTING_RESPONSE); + ss_dassert(m_res.pData); + + int rv = 1; + + size_t buflen = gwbuf_length(m_res.pData); + + if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. + { + // Reserve enough space to accomodate for the largest length encoded integer, + // which is type field + 8 bytes. + uint8_t header[MYSQL_HEADER_LEN + 1 + 8]; + gwbuf_copy_data(m_res.pData, 0, MYSQL_HEADER_LEN + 1, header); + + switch ((int)MYSQL_GET_COMMAND(header)) + { + case 0x00: // OK + case 0xff: // ERR + store_result(); + + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + break; + + case 0xfb: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + break; + + default: + if (m_res.nTotalFields != 0) + { + // We've seen the header and have figured out how many fields there are. + m_state = CACHE_EXPECTING_FIELDS; + rv = handle_expecting_fields(); + } + else + { + // leint_bytes() returns the length of the int type field + the size of the + // integer. + size_t n_bytes = leint_bytes(&header[4]); + + if (MYSQL_HEADER_LEN + n_bytes <= buflen) + { + // Now we can figure out how many fields there are, but first we + // need to copy some more data. + gwbuf_copy_data(m_res.pData, + MYSQL_HEADER_LEN + 1, n_bytes - 1, &header[MYSQL_HEADER_LEN + 1]); + + m_res.nTotalFields = leint_value(&header[4]); + m_res.offset = MYSQL_HEADER_LEN + n_bytes; + + m_state = CACHE_EXPECTING_FIELDS; + rv = handle_expecting_fields(); + } + else + { + // We need more data. We will be called again, when data is available. + } + } + break; + } + } + + return rv; +} + +/** + * Called when resultset rows are handled. + */ +int SessionCache::handle_expecting_rows() +{ + ss_dassert(m_state == CACHE_EXPECTING_ROWS); + ss_dassert(m_res.pData); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(m_res.pData); + + while (!insufficient && (buflen - m_res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MYSQL_HEADER_LEN + 1]; + gwbuf_copy_data(m_res.pData, m_res.offset, MYSQL_HEADER_LEN + 1, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (m_res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xfe: // EOF, the one after the rows. + m_res.offset += packetlen; + ss_dassert(m_res.offset == buflen); + + store_result(); + + rv = send_upstream(); + m_state = CACHE_EXPECTING_NOTHING; + break; + + case 0xfb: // NULL + default: // length-encoded-string + m_res.offset += packetlen; + ++m_res.nRows; + + if (m_res.nRows > m_pCache->config().max_resultset_rows) + { + if (log_decisions()) + { + MXS_NOTICE("Max rows %lu reached, not caching result.", m_res.nRows); + } + rv = send_upstream(); + m_res.offset = buflen; // To abort the loop. + m_state = CACHE_IGNORING_RESPONSE; + } + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when a response to a "USE db" is received from the server. + */ +int SessionCache::handle_expecting_use_response() +{ + ss_dassert(m_state == CACHE_EXPECTING_USE_RESPONSE); + ss_dassert(m_res.pData); + + int rv = 1; + + size_t buflen = gwbuf_length(m_res.pData); + + if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. + { + uint8_t command; + + gwbuf_copy_data(m_res.pData, MYSQL_HEADER_LEN, 1, &command); + + switch (command) + { + case 0x00: // OK + // In case m_zUseDb could not be allocated in routeQuery(), we will + // in fact reset the default db here. That's ok as it will prevent broken + // entries in the cache. + MXS_FREE(m_zDefaultDb); + m_zDefaultDb = m_zUseDb; + m_zUseDb = NULL; + break; + + case 0xff: // ERR + MXS_FREE(m_zUseDb); + m_zUseDb = NULL; + break; + + default: + MXS_ERROR("\"USE %s\" received unexpected server response %d.", + m_zUseDb ? m_zUseDb : "", command); + MXS_FREE(m_zDefaultDb); + MXS_FREE(m_zUseDb); + m_zDefaultDb = NULL; + m_zUseDb = NULL; + } + + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + } + + return rv; +} + +/** + * Called when all data from the server is ignored. + */ +int SessionCache::handle_ignoring_response() +{ + ss_dassert(m_state == CACHE_IGNORING_RESPONSE); + ss_dassert(m_res.pData); + + return send_upstream(); +} + +/** + * Send data upstream. + * + * @return Whatever the upstream returns. + */ +int SessionCache::send_upstream() +{ + ss_dassert(m_res.pData != NULL); + + int rv = m_up.clientReply(m_up.instance, m_up.session, m_res.pData); + m_res.pData = NULL; + + return rv; +} + +/** + * Reset cache response state + */ +void SessionCache::reset_response_state() +{ + m_res.pData = NULL; + m_res.nTotalFields = 0; + m_res.nFields = 0; + m_res.nRows = 0; + m_res.offset = 0; +} + +/** + * Route a query via the cache. + * + * @param key A SELECT packet. + * @param value The result. + * @return True if the query was satisfied from the query. + */ +cache_result_t SessionCache::get_cached_response(const GWBUF *pQuery, GWBUF **ppResponse) +{ + cache_result_t result = m_pCache->getKey(m_zDefaultDb, pQuery, m_key); + + if (result == CACHE_RESULT_OK) + { + uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; + + result = m_pCache->getValue(m_key, flags, ppResponse); + } + else + { + MXS_ERROR("Could not create cache key."); + } + + return result; +} + +/** + * Store the data. + * + * @param csdata Session data + */ +void SessionCache::store_result() +{ + ss_dassert(m_res.pData); + + GWBUF *pData = gwbuf_make_contiguous(m_res.pData); + + if (pData) + { + m_res.pData = pData; + + cache_result_t result = m_pCache->putValue(m_key, m_res.pData); + + if (result != CACHE_RESULT_OK) + { + MXS_ERROR("Could not store cache item, deleting it."); + + result = m_pCache->delValue(m_key); + + if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) + { + MXS_ERROR("Could not delete cache item."); + } + } + } + + if (m_refreshing) + { + m_pCache->refreshed(m_key, this); + m_refreshing = false; + } +} diff --git a/server/modules/filter/cache/sessioncache.h b/server/modules/filter/cache/sessioncache.h new file mode 100644 index 000000000..6606e975b --- /dev/null +++ b/server/modules/filter/cache/sessioncache.h @@ -0,0 +1,142 @@ +#pragma once +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include +#include +#include +#include "cache.h" +#include "cachefilter.h" +#include "cache_storage_api.h" + +class Cache; + +class SessionCache +{ +public: + enum cache_session_state_t + { + CACHE_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. + CACHE_EXPECTING_FIELDS, // A select has been sent, and we want more fields. + CACHE_EXPECTING_ROWS, // A select has been sent, and we want more rows. + CACHE_EXPECTING_NOTHING, // We are not expecting anything from the server. + CACHE_EXPECTING_USE_RESPONSE, // A "USE DB" was issued. + CACHE_IGNORING_RESPONSE, // We are not interested in the data received from the server. + }; + + struct CACHE_RESPONSE_STATE + { + GWBUF* pData; /**< Response data, possibly incomplete. */ + size_t nTotalFields; /**< The number of fields a resultset contains. */ + size_t nFields; /**< How many fields we have received, <= n_totalfields. */ + size_t nRows; /**< How many rows we have received. */ + size_t offset; /**< Where we are in the response buffer. */ + }; + + /** + * Releases all resources held by the session cache. + */ + ~SessionCache(); + + /** + * Creates a SessionCache instance. + * + * @param pCache Pointer to the cache instance to which this session cache + * belongs. Must remain valid for the lifetime of the SessionCache + * instance being created. + * @param pSession Pointer to the session this session cache instance is + * specific for. Must remain valid for the lifetime of the SessionCache + * instance being created. + * + * @return A new instance or NULL if memory allocation fails. + */ + static SessionCache* Create(Cache* pCache, SESSION* pSession); + + /** + * The session has been closed. + */ + void close(); + + /** + * Set the downstream component for this session. + * + * @param pDown The downstream filter or router + */ + void setDownstream(DOWNSTREAM* pDownstream); + + /** + * Set the upstream component for this session. + * + * @param pUp The upstream filter or router + */ + void setUpstream(UPSTREAM* pUpstream); + + /** + * A request on its way to a backend is delivered to this function. + * + * @param pPacket Buffer containing an MySQL protocol packet. + */ + int routeQuery(GWBUF* pPacket); + + /** + * A response on its way to the client is delivered to this function. + * + * @param pData Response data. + */ + int clientReply(GWBUF* pPacket); + + /** + * Print diagnostics of the session cache. + */ + void diagnostics(DCB *dcb); + +private: + int handle_expecting_fields(); + int handle_expecting_nothing(); + int handle_expecting_response(); + int handle_expecting_rows(); + int handle_expecting_use_response(); + int handle_ignoring_response(); + + int send_upstream(); + + void reset_response_state(); + + cache_result_t get_cached_response(const GWBUF *pQuery, GWBUF **ppResponse); + + bool log_decisions() const + { + return m_pCache->config().debug & CACHE_DEBUG_DECISIONS ? true : false; + } + + void store_result(); + +private: + SessionCache(Cache* pCache, SESSION* pSession, char* zDefaultDb); + + SessionCache(const SessionCache&); + SessionCache& operator = (const SessionCache&); + +private: + cache_session_state_t m_state; /**< What state is the session in, what data is expected. */ + Cache* m_pCache; /**< The cache instance the session is associated with. */ + SESSION* m_pSession; /**< The session this data is associated with. */ + DOWNSTREAM m_down; /**< The previous filter or equivalent. */ + UPSTREAM m_up; /**< The next filter or equivalent. */ + CACHE_RESPONSE_STATE m_res; /**< The response state. */ + char m_key[CACHE_KEY_MAXLEN]; /**< Key storage. */ + char* m_zDefaultDb; /**< The default database. */ + char* m_zUseDb; /**< Pending default database. Needs server response. */ + bool m_refreshing; /**< Whether the session is updating a stale cache entry. */ +}; + diff --git a/server/modules/filter/cache/storage.c b/server/modules/filter/cache/storage.c deleted file mode 100644 index 165905dd1..000000000 --- a/server/modules/filter/cache/storage.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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/bsl. - * - * Change Date: 2019-07-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. - */ - -#include "storage.h" -#include -#include -#include -#include -#include - -CACHE_STORAGE_MODULE* cache_storage_open(const char *name) -{ - CACHE_STORAGE_MODULE* module = (CACHE_STORAGE_MODULE*)MXS_CALLOC(1, sizeof(CACHE_STORAGE_MODULE)); - - if (module) - { - char path[MAXPATHLEN + 1]; - sprintf(path, "%s/lib%s.so", get_libdir(), name); - - void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); - - if (handle) - { - module->handle = handle; - - void *f = dlsym(module->handle, CACHE_STORAGE_ENTRY_POINT); - - if (f) - { - module->api = ((CacheGetStorageAPIFN)f)(); - - if (module->api) - { - if (!(module->api->initialize)()) - { - MXS_ERROR("Initialization of %s failed.", path); - - (void)dlclose(module->handle); - MXS_FREE(module); - module = NULL; - } - } - else - { - MXS_ERROR("Could not obtain API object from %s.", name); - - (void)dlclose(module->handle); - MXS_FREE(module); - module = NULL; - } - } - else - { - const char* s = dlerror(); - MXS_ERROR("Could not look up symbol %s from %s: %s", - name, CACHE_STORAGE_ENTRY_POINT, s ? s : ""); - MXS_FREE(module); - module = NULL; - } - } - else - { - const char* s = dlerror(); - MXS_ERROR("Could not load %s: %s", name, s ? s : ""); - MXS_FREE(module); - module = NULL; - } - } - - return module; -} - - -void cache_storage_close(CACHE_STORAGE_MODULE *module) -{ - if (module) - { - if (dlclose(module->handle) != 0) - { - const char *s = dlerror(); - MXS_ERROR("Could not close module %s: ", s ? s : ""); - } - - MXS_FREE(module); - } -} diff --git a/server/modules/filter/cache/storage.cc b/server/modules/filter/cache/storage.cc new file mode 100644 index 000000000..626dff108 --- /dev/null +++ b/server/modules/filter/cache/storage.cc @@ -0,0 +1,53 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#define MXS_MODULE_NAME "cache" +#include "storage.h" + + +Storage::Storage(CACHE_STORAGE_API* pApi, CACHE_STORAGE* pStorage) + : m_pApi(pApi) + , m_pStorage(pStorage) +{ + ss_dassert(m_pApi); + ss_dassert(m_pStorage); +} + +Storage::~Storage() +{ +} + +cache_result_t Storage::getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey) +{ + return m_pApi->getKey(m_pStorage, zDefaultDb, pQuery, pKey); +} + +cache_result_t Storage::getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue) +{ + return m_pApi->getValue(m_pStorage, pKey, flags, ppValue); +} + +cache_result_t Storage::putValue(const char* pKey, + const GWBUF* pValue) +{ + return m_pApi->putValue(m_pStorage, pKey, pValue); +} + +cache_result_t Storage::delValue(const char* pKey) +{ + return m_pApi->delValue(m_pStorage, pKey); +} diff --git a/server/modules/filter/cache/storage.h b/server/modules/filter/cache/storage.h index f0d2f8bef..a2eadfaaf 100644 --- a/server/modules/filter/cache/storage.h +++ b/server/modules/filter/cache/storage.h @@ -17,17 +17,35 @@ #include #include "cache_storage_api.h" -MXS_BEGIN_DECLS - -typedef struct cache_storage_module_t +class Storage { - void* handle; - CACHE_STORAGE_API* api; -} CACHE_STORAGE_MODULE; +public: + ~Storage(); -CACHE_STORAGE_MODULE* cache_storage_open(const char *name); -void cache_storage_close(CACHE_STORAGE_MODULE *module); + cache_result_t getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey); -MXS_END_DECLS + cache_result_t getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue); + + cache_result_t putValue(const char* pKey, + const GWBUF* pValue); + + cache_result_t delValue(const char* pKey); + +private: + friend class StorageFactory; + + Storage(CACHE_STORAGE_API* pApi, CACHE_STORAGE* pStorage); + + Storage(const Storage&); + Storage& operator = (const Storage&); + +private: + CACHE_STORAGE_API* m_pApi; + CACHE_STORAGE* m_pStorage; +}; #endif diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc index 966bfdf8b..d09c45d1d 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc @@ -403,7 +403,7 @@ cache_result_t RocksDBStorage::getKey(const char* zDefaultDB, const GWBUF* pQuer return CACHE_RESULT_OK; } -cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) +cache_result_t RocksDBStorage::getValue(const char* pKey, uint32_t flags, GWBUF** ppResult) { // Use the root DB so that we get the value *with* the timestamp at the end. rocksdb::DB* pDb = m_sDb->GetRootDB(); @@ -419,7 +419,9 @@ cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) case rocksdb::Status::kOk: if (value.length() >= RocksDBInternals::TS_LENGTH) { - if (!RocksDBInternals::IsStale(value, m_ttl, rocksdb::Env::Default())) + bool isStale = RocksDBInternals::IsStale(value, m_ttl, rocksdb::Env::Default()); + + if (!isStale || ((flags & CACHE_FLAGS_INCLUDE_STALE) != 0)) { size_t length = value.length() - RocksDBInternals::TS_LENGTH; @@ -429,7 +431,14 @@ cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) { memcpy(GWBUF_DATA(*ppResult), value.data(), length); - result = CACHE_RESULT_OK; + if (isStale) + { + result = CACHE_RESULT_STALE; + } + else + { + result = CACHE_RESULT_OK; + } } } else @@ -461,9 +470,20 @@ cache_result_t RocksDBStorage::putValue(const char* pKey, const GWBUF* pValue) ss_dassert(GWBUF_IS_CONTIGUOUS(pValue)); rocksdb::Slice key(pKey, ROCKSDB_KEY_LENGTH); - rocksdb::Slice value(static_cast(GWBUF_DATA(pValue)), GWBUF_LENGTH(pValue)); + rocksdb::Slice value((char*)GWBUF_DATA(pValue), GWBUF_LENGTH(pValue)); rocksdb::Status status = m_sDb->Put(writeOptions(), key, value); return status.ok() ? CACHE_RESULT_OK : CACHE_RESULT_ERROR; } + +cache_result_t RocksDBStorage::delValue(const char* pKey) +{ + ss_dassert(pKey); + + rocksdb::Slice key(pKey, ROCKSDB_KEY_LENGTH); + + rocksdb::Status status = m_sDb->Delete(writeOptions(), key); + + return status.ok() ? CACHE_RESULT_OK : CACHE_RESULT_ERROR; +} diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h index a3bf85ba7..a416be72a 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h @@ -30,8 +30,9 @@ public: ~RocksDBStorage(); cache_result_t getKey(const char* zDefaultDB, const GWBUF* pQuery, char* pKey); - cache_result_t getValue(const char* pKey, GWBUF** ppResult); + cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppResult); cache_result_t putValue(const char* pKey, const GWBUF* pValue); + cache_result_t delValue(const char* pKey); private: RocksDBStorage(std::unique_ptr& sDb, diff --git a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc index d5e72aabd..5c32a9b2e 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc @@ -86,7 +86,10 @@ cache_result_t getKey(CACHE_STORAGE* pStorage, return result; } -cache_result_t getValue(CACHE_STORAGE* pStorage, const char* pKey, GWBUF** ppResult) +cache_result_t getValue(CACHE_STORAGE* pStorage, + const char* pKey, + uint32_t flags, + GWBUF** ppResult) { ss_dassert(pStorage); ss_dassert(pKey); @@ -96,7 +99,7 @@ cache_result_t getValue(CACHE_STORAGE* pStorage, const char* pKey, GWBUF** ppRes try { - result = reinterpret_cast(pStorage)->getValue(pKey, ppResult); + result = reinterpret_cast(pStorage)->getValue(pKey, flags, ppResult); } catch (const std::bad_alloc&) { @@ -144,6 +147,34 @@ cache_result_t putValue(CACHE_STORAGE* pStorage, return result; } +cache_result_t delValue(CACHE_STORAGE* pStorage, + const char* pKey) +{ + ss_dassert(pStorage); + ss_dassert(pKey); + + cache_result_t result = CACHE_RESULT_ERROR; + + try + { + result = reinterpret_cast(pStorage)->delValue(pKey); + } + catch (const std::bad_alloc&) + { + MXS_OOM(); + } + catch (const std::exception& x) + { + MXS_ERROR("Standard exception caught: %s", x.what()); + } + catch (...) + { + MXS_ERROR("Unknown exception caught."); + } + + return result; +} + } extern "C" @@ -158,7 +189,8 @@ CACHE_STORAGE_API* CacheGetStorageAPI() freeInstance, getKey, getValue, - putValue + putValue, + delValue, }; return &api; diff --git a/server/modules/filter/cache/storagefactory.cc b/server/modules/filter/cache/storagefactory.cc new file mode 100644 index 000000000..09e2f984a --- /dev/null +++ b/server/modules/filter/cache/storagefactory.cc @@ -0,0 +1,155 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#define MXS_MODULE_NAME "cache" +#include "storagefactory.h" +#include +#include +#include +#include +#include +#include +#include "storage.h" + + +namespace +{ + +bool open_cache_storage(const char* zName, void** pHandle, CACHE_STORAGE_API** ppApi) +{ + bool rv = false; + + char path[MAXPATHLEN + 1]; + sprintf(path, "%s/lib%s.so", get_libdir(), zName); + + void* handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + + if (handle) + { + void* f = dlsym(handle, CACHE_STORAGE_ENTRY_POINT); + + if (f) + { + CACHE_STORAGE_API* pApi = ((CacheGetStorageAPIFN)f)(); + + if (pApi) + { + if ((pApi->initialize)()) + { + *pHandle = handle; + *ppApi = pApi; + + rv = true; + } + else + { + MXS_ERROR("Initialization of %s failed.", path); + + (void)dlclose(handle); + } + } + else + { + MXS_ERROR("Could not obtain API object from %s.", zName); + + (void)dlclose(handle); + } + } + else + { + const char* s = dlerror(); + MXS_ERROR("Could not look up symbol %s from %s: %s", + zName, CACHE_STORAGE_ENTRY_POINT, s ? s : ""); + } + } + else + { + const char* s = dlerror(); + MXS_ERROR("Could not load %s: %s", zName, s ? s : ""); + } + + return rv; +} + + +void close_cache_storage(void* handle, CACHE_STORAGE_API* pApi) +{ + // TODO: pApi->finalize(); + + if (dlclose(handle) != 0) + { + const char *s = dlerror(); + MXS_ERROR("Could not close module %s: ", s ? s : ""); + } +} + +} + +StorageFactory::StorageFactory(void* handle, CACHE_STORAGE_API* pApi) + : m_handle(handle) + , m_pApi(pApi) +{ + ss_dassert(handle); + ss_dassert(pApi); +} + +StorageFactory::~StorageFactory() +{ + close_cache_storage(m_handle, m_pApi); + m_handle = 0; + m_pApi = 0; +} + +//static +StorageFactory* StorageFactory::Open(const char* zName) +{ + StorageFactory* pFactory = 0; + + void* handle; + CACHE_STORAGE_API* pApi; + + if (open_cache_storage(zName, &handle, &pApi)) + { + pFactory = new (std::nothrow) StorageFactory(handle, pApi); + + if (!pFactory) + { + close_cache_storage(handle, pApi); + } + } + + return pFactory; +} + +Storage* StorageFactory::createStorage(const char* zName, + uint32_t ttl, + int argc, char* argv[]) +{ + ss_dassert(m_handle); + ss_dassert(m_pApi); + + Storage* pStorage = 0; + CACHE_STORAGE* pRawStorage = m_pApi->createInstance(zName, ttl, argc, argv); + + if (pRawStorage) + { + pStorage = new (std::nothrow) Storage(m_pApi, pRawStorage); + + if (!pStorage) + { + m_pApi->freeInstance(pRawStorage); + } + } + + return pStorage; +} diff --git a/server/modules/filter/cache/storagefactory.h b/server/modules/filter/cache/storagefactory.h new file mode 100644 index 000000000..e87f57742 --- /dev/null +++ b/server/modules/filter/cache/storagefactory.h @@ -0,0 +1,44 @@ +#pragma once +#ifndef _MAXSCALE_FILTER_CACHE_STORAGEFACTORY_H +#define _MAXSCALE_FILTER_CACHE_STORAGEFACTORY_H +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + +#include +#include "cache_storage_api.h" + +class Storage; + +class StorageFactory +{ +public: + ~StorageFactory(); + + static StorageFactory* Open(const char* zName); + + Storage* createStorage(const char* zName, + uint32_t ttl, + int argc, char* argv[]); + +private: + StorageFactory(void* handle, CACHE_STORAGE_API* pApi); + + StorageFactory(const StorageFactory&); + StorageFactory& operator = (const StorageFactory&); + +private: + void* m_handle; + CACHE_STORAGE_API* m_pApi; +}; + +#endif diff --git a/server/modules/filter/cache/test/CMakeLists.txt b/server/modules/filter/cache/test/CMakeLists.txt index 061d2323f..c2d8e3cf7 100644 --- a/server/modules/filter/cache/test/CMakeLists.txt +++ b/server/modules/filter/cache/test/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(testrules testrules.c ../rules.c) +add_executable(testrules testrules.cc ../rules.cc) include_directories(..) target_link_libraries(testrules maxscale-common jansson) diff --git a/server/modules/filter/cache/test/testrules.c b/server/modules/filter/cache/test/testrules.cc similarity index 96% rename from server/modules/filter/cache/test/testrules.c rename to server/modules/filter/cache/test/testrules.cc index 601d0e2bc..c613305a8 100644 --- a/server/modules/filter/cache/test/testrules.c +++ b/server/modules/filter/cache/test/testrules.cc @@ -13,6 +13,8 @@ #include #include "rules.h" +#include +#include #include #include #include @@ -78,7 +80,7 @@ int test_user() { int errors = 0; - for (int i = 0; i < n_user_test_cases; ++i) + for (size_t i = 0; i < n_user_test_cases; ++i) { const struct user_test_case *test_case = &user_test_cases[i]; @@ -180,9 +182,9 @@ int test_store() { int errors = 0; - for (int i = 0; i < n_store_test_cases; ++i) + for (size_t i = 0; i < n_store_test_cases; ++i) { - printf("TC : %d\n", i + 1); + printf("TC : %d\n", (int)(i + 1)); const struct store_test_case *test_case = &store_test_cases[i]; CACHE_RULES *rules = cache_rules_parse(test_case->rule, 0); @@ -234,8 +236,10 @@ int main() if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) { + set_libdir(MXS_STRDUP_A("../../../../../query_classifier/qc_sqlite/")); if (qc_init("qc_sqlite", "")) { + set_libdir(MXS_STRDUP_A("../")); rc = test(); } else diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index 42fcbada9..e4774459e 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -60,26 +60,32 @@ *@endcode */ -#include +#include + #include -#include #include +#include +#include +#include +#include + +#include #include +#include #include #include #include #include +#include #include -#include -#include -#include +#include #include -#include "dbfwfilter.h" -#include -#include -#include #include +#include "dbfwfilter.h" +#include "ruleparser.yy.h" +#include "lex.yy.h" + /** Older versions of Bison don't include the parsing function in the header */ #ifndef dbfw_yyparse int dbfw_yyparse(void*); @@ -167,6 +173,8 @@ const char* rule_names[] = "CLAUSE" }; +const int rule_names_len = sizeof(rule_names) / sizeof(char**); + /** * Linked list of strings. */ @@ -228,6 +236,10 @@ typedef struct rulebook_t struct rulebook_t* next; /*< The next rule in the book */ } RULE_BOOK; +thread_local int thr_rule_version = 0; +thread_local RULE *thr_rules = NULL; +thread_local HASHTABLE *thr_users = NULL; + /** * A temporary template structure used in the creation of actual users. * This is also used to link the user definitions with the rules. @@ -260,12 +272,12 @@ typedef struct user_t */ typedef struct { - HASHTABLE* users; /*< User hashtable */ - RULE* rules; /*< List of all the rules */ enum fw_actions action; /*< Default operation mode, defaults to deny */ int log_match; /*< Log matching and/or non-matching queries */ SPINLOCK lock; /*< Instance spinlock */ int idgen; /*< UID generator */ + char *rulefile; /*< Path to the rule file */ + int rule_version; /*< Latest rule file version, incremented on reload */ } FW_INSTANCE; /** @@ -281,6 +293,24 @@ typedef struct bool parse_at_times(const char** tok, char** saveptr, RULE* ruledef); bool parse_limit_queries(FW_INSTANCE* instance, RULE* ruledef, const char* rule, char** saveptr); +static void rule_free_all(RULE* rule); +static bool process_rule_file(const char* filename, RULE** rules, HASHTABLE **users); +bool replace_rules(FW_INSTANCE* instance); + +static void print_rule(RULE *rules, char *dest) +{ + int type = 0; + + if ((int)rules->type > 0 && (int)rules->type < rule_names_len) + { + type = (int)rules->type; + } + + sprintf(dest, "%s, %s, %d", + rules->name, + rule_names[type], + rules->times_matched); +} /** * Push a string onto a string stack @@ -674,6 +704,95 @@ TIMERANGE* split_reverse_time(TIMERANGE* tr) return tmp; } +bool dbfw_reload_rules(const MODULECMD_ARG *argv) +{ + bool rval = true; + FILTER_DEF *filter = argv->argv[0].value.filter; + FW_INSTANCE *inst = (FW_INSTANCE*)filter->filter; + + if (modulecmd_arg_is_present(argv, 1)) + { + /** We need to change the rule file */ + char *newname = MXS_STRDUP(argv->argv[1].value.string); + + if (newname) + { + spinlock_acquire(&inst->lock); + + char *oldname = inst->rulefile; + inst->rulefile = newname; + + spinlock_release(&inst->lock); + + MXS_FREE(oldname); + } + else + { + modulecmd_set_error("Memory allocation failed"); + rval = false; + } + } + + spinlock_acquire(&inst->lock); + char filename[strlen(inst->rulefile) + 1]; + strcpy(filename, inst->rulefile); + spinlock_release(&inst->lock); + + RULE *rules = NULL; + HASHTABLE *users = NULL; + + if (rval && access(filename, R_OK) == 0) + { + if (process_rule_file(filename, &rules, &users)) + { + atomic_add(&inst->rule_version, 1); + } + else + { + modulecmd_set_error("Failed to process rule file '%s'. See log " + "file for more details.", filename); + rval = false; + } + } + else + { + char err[MXS_STRERROR_BUFLEN]; + modulecmd_set_error("Failed to read rules at '%s': %d, %s", filename, + errno, strerror_r(errno, err, sizeof(err))); + rval = false; + } + + rule_free_all(rules); + hashtable_free(users); + + return rval; +} + +bool dbfw_show_rules(const MODULECMD_ARG *argv) +{ + DCB *dcb = argv->argv[0].value.dcb; + FILTER_DEF *filter = argv->argv[1].value.filter; + FW_INSTANCE *inst = (FW_INSTANCE*)filter->filter; + + dcb_printf(dcb, "Rule, Type, Times Matched\n"); + + if (!thr_rules || !thr_users) + { + if (!replace_rules(inst)) + { + return 0; + } + } + + for (RULE *rule = thr_rules; rule; rule = rule->next) + { + char buf[strlen(rule->name) + 200]; // Some extra space + print_rule(rule, buf); + dcb_printf(dcb, "%s\n", buf); + } + + return true; +} /** * Implementation of the mandatory version entry point * @@ -692,7 +811,23 @@ char * version() /*lint -e14 */ void ModuleInit() { + modulecmd_arg_type_t args_rules_reload[] = + { + {MODULECMD_ARG_FILTER, "Filter to reload"}, + {MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, "Path to rule file"} + }; + + modulecmd_register_command("dbfwfilter", "rules/reload", dbfw_reload_rules, 2, args_rules_reload); + + modulecmd_arg_type_t args_rules_show[] = + { + {MODULECMD_ARG_OUTPUT, "DCB where result is written"}, + {MODULECMD_ARG_FILTER, "Filter to inspect"} + }; + + modulecmd_register_command("dbfwfilter", "rules", dbfw_show_rules, 2, args_rules_show); } + /*lint +e14 */ /** @@ -708,60 +843,6 @@ FILTER_OBJECT * GetModuleObject() return &MyObject; } -/** - * Apply a rule set to a user - * - * @param instance Filter instance - * @param user User name - * @param rulebook List of rules to apply - * @param type Matching type, one of FWTOK_MATCH_ANY, FWTOK_MATCH_ALL or FWTOK_MATCH_STRICT_ALL - * @return True of the rules were successfully applied. False if memory allocation - * fails - */ -static bool apply_rule_to_user(FW_INSTANCE *instance, char *username, - RULE_BOOK *rulebook, enum match_type type) -{ - DBFW_USER* user; - ss_dassert(type == FWTOK_MATCH_ANY || type == FWTOK_MATCH_STRICT_ALL || type == FWTOK_MATCH_ALL); - if ((user = (DBFW_USER*) hashtable_fetch(instance->users, username)) == NULL) - { - /**New user*/ - if ((user = (DBFW_USER*) MXS_CALLOC(1, sizeof(DBFW_USER))) == NULL) - { - return false; - } - spinlock_init(&user->lock); - } - - user->name = (char*) MXS_STRDUP_A(username); - user->qs_limit = NULL; - RULE_BOOK *tl = (RULE_BOOK*) rulebook_clone(rulebook); - RULE_BOOK *tail = tl; - - while (tail && tail->next) - { - tail = tail->next; - } - - switch (type) - { - case FWTOK_MATCH_ANY: - tail->next = user->rules_or; - user->rules_or = tl; - break; - case FWTOK_MATCH_STRICT_ALL: - tail->next = user->rules_and; - user->rules_strict_and = tl; - break; - case FWTOK_MATCH_ALL: - tail->next = user->rules_and; - user->rules_and = tl; - break; - } - hashtable_add(instance->users, (void *) username, (void *) user); - return true; -} - /** * Free a TIMERANGE struct * @param tr pointer to a TIMERANGE struct @@ -1238,6 +1319,7 @@ static bool process_user_templates(HASHTABLE *users, user_template_t *templates, user->rules_and = NULL; user->rules_or = NULL; user->rules_strict_and = NULL; + user->qs_limit = NULL; spinlock_init(&user->lock); hashtable_add(users, user->name, user); } @@ -1360,36 +1442,45 @@ static bool process_rule_file(const char* filename, RULE** rules, HASHTABLE **us return rc == 0; } - /** - * @brief Replace the rule file used by this filter instance + * @brief Replace the rule file used by this thread * - * This function does no locking. An external lock needs to protect this function - * call to prevent any connections from using the data when it is being replaced. + * This function replaces or initializes the thread local list of rules and users. * - * @param filename File where the rules are located * @param instance Filter instance - * @return True on success, false on error. If the return value is false, the - * old rules remain active. + * @return True if the session can continue, false on fatal error. */ -bool replace_rule_file(const char* filename, FW_INSTANCE* instance) +bool replace_rules(FW_INSTANCE* instance) { - bool rval = false; + bool rval = true; + spinlock_acquire(&instance->lock); + + size_t len = strlen(instance->rulefile); + char filename[len + 1]; + strcpy(filename, instance->rulefile); + + spinlock_release(&instance->lock); + RULE *rules; HASHTABLE *users; if (process_rule_file(filename, &rules, &users)) { - /** Rules processed successfully, free the old ones */ - rule_free_all(instance->rules); - hashtable_free(instance->users); - instance->rules = rules; - instance->users = users; + rule_free_all(thr_rules); + hashtable_free(thr_users); + thr_rules = rules; + thr_users = users; rval = true; } + else if (thr_rules && thr_users) + { + MXS_ERROR("Failed to parse rules at '%s'. Old rules are still used.", filename); + } else { - MXS_ERROR("Failed to process rule file at '%s', old rules are still active.", filename); + MXS_ERROR("Failed to parse rules at '%s'. No previous rules available, " + "closing session.", filename); + rval = false; } return rval; @@ -1474,11 +1565,22 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) err = true; } - if (err || !process_rule_file(filename, &my_instance->rules, &my_instance->users)) + RULE *rules = NULL; + HASHTABLE *users = NULL; + my_instance->rulefile = MXS_STRDUP(filename); + + if (err || !my_instance->rulefile || !process_rule_file(filename, &rules, &users)) { MXS_FREE(my_instance); my_instance = NULL; } + else + { + atomic_add(&my_instance->rule_version, 1); + } + + rule_free_all(rules); + hashtable_free(users); return (FILTER *) my_instance; } @@ -1830,15 +1932,15 @@ bool rule_matches(FW_INSTANCE* my_instance, break; case RT_PERMISSION: - { - matches = true; - msg = MXS_STRDUP_A("Permission denied at this time."); - char buffer[32]; // asctime documentation requires 26 - asctime_r(&tm_now, buffer); - MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulebook->rule->name, buffer); - goto queryresolved; - } - break; + { + matches = true; + msg = MXS_STRDUP_A("Permission denied at this time."); + char buffer[32]; // asctime documentation requires 26 + asctime_r(&tm_now, buffer); + MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulebook->rule->name, buffer); + goto queryresolved; + } + break; case RT_COLUMN: if (is_sql && is_real) @@ -2205,6 +2307,16 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) DCB *dcb = my_session->session->client_dcb; int rval = 0; ss_dassert(dcb && dcb->session); + int rule_version = my_instance->rule_version; + + if (thr_rule_version < rule_version) + { + if (!replace_rules(my_instance)) + { + return 0; + } + thr_rule_version = rule_version; + } if (modutil_is_SQL(queue) && modutil_count_statements(queue) > 1) { @@ -2217,7 +2329,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } else { - DBFW_USER *user = find_user_data(my_instance->users, dcb->user, dcb->remote); + DBFW_USER *user = find_user_data(thr_users, dcb->user, dcb->remote); bool query_ok = false; if (user) @@ -2304,6 +2416,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) rval = dcb->func.write(dcb, forward); } } + return rval; } @@ -2321,34 +2434,15 @@ static void diagnostic(FILTER *instance, void *fsession, DCB *dcb) { FW_INSTANCE *my_instance = (FW_INSTANCE *) instance; - RULE* rules; - int type; - if (my_instance) + dcb_printf(dcb, "Firewall Filter\n"); + dcb_printf(dcb, "Rule, Type, Times Matched\n"); + + for (RULE *rule = thr_rules; rule; rule = rule->next) { - spinlock_acquire(&my_instance->lock); - rules = my_instance->rules; - - dcb_printf(dcb, "Firewall Filter\n"); - dcb_printf(dcb, "%-24s%-24s%-24s\n", "Rule", "Type", "Times Matched"); - while (rules) - { - if ((int) rules->type > 0 && - (int) rules->type < sizeof(rule_names) / sizeof(char**)) - { - type = (int) rules->type; - } - else - { - type = 0; - } - dcb_printf(dcb, "%-24s%-24s%-24d\n", - rules->name, - rule_names[type], - rules->times_matched); - rules = rules->next; - } - spinlock_release(&my_instance->lock); + char buf[strlen(rule->name) + 200]; + print_rule(rule, buf); + dcb_printf(dcb, "%s\n", buf); } } diff --git a/server/modules/filter/hintfilter/hintparser.c b/server/modules/filter/hintfilter/hintparser.c index 1f3dbe79f..0db3498fd 100644 --- a/server/modules/filter/hintfilter/hintparser.c +++ b/server/modules/filter/hintfilter/hintparser.c @@ -220,7 +220,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) if (buf) { len = GWBUF_LENGTH(buf); - ptr = GWBUF_DATA(buf); + ptr = (char*)GWBUF_DATA(buf); } } while (buf); @@ -243,7 +243,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) buf = buf->next; if (buf) { - ptr = GWBUF_DATA(buf); + ptr = (char*)GWBUF_DATA(buf); } else { diff --git a/server/modules/protocol/CDC/cdc.c b/server/modules/protocol/CDC/cdc.c index e4612780e..05031ed84 100644 --- a/server/modules/protocol/CDC/cdc.c +++ b/server/modules/protocol/CDC/cdc.c @@ -191,7 +191,7 @@ cdc_read_event(DCB* dcb) case CDC_STATE_HANDLE_REQUEST: // handle CLOSE command, it shoudl be routed as well and client connection closed after last transmission - if (strncmp(GWBUF_DATA(head), "CLOSE", GWBUF_LENGTH(head)) == 0) + if (strncmp((char*)GWBUF_DATA(head), "CLOSE", GWBUF_LENGTH(head)) == 0) { MXS_INFO("%s: Client [%s] has requested CLOSE action", dcb->service->name, dcb->remote != NULL ? dcb->remote : ""); diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 49695b136..df10a1d1f 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -1440,7 +1440,7 @@ static int route_by_statement(SESSION* session, uint64_t capabilities, GWBUF** p if (rcap_type_required(capabilities, RCAP_TYPE_TRANSACTION_TRACKING)) { - uint32_t *data = GWBUF_DATA(packetbuf); + uint8_t *data = GWBUF_DATA(packetbuf); if (MYSQL_GET_COMMAND(data) == MYSQL_COM_QUERY) { diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 9873b30ae..9dad9552a 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1086,11 +1086,11 @@ bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session) * @param dcb DCB where packet is written * @param sequence Packet sequence number * @param affected_rows Number of affected rows - * * @param message SQL message + * @param message SQL message * @return 1 on success, 0 on error * */ -int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* message) +int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* message) { uint8_t *outbuf = NULL; uint32_t mysql_payload_size = 0; diff --git a/server/modules/protocol/maxscaled/maxscaled.c b/server/modules/protocol/maxscaled/maxscaled.c index d3a7a7eeb..60d809e81 100644 --- a/server/modules/protocol/maxscaled/maxscaled.c +++ b/server/modules/protocol/maxscaled/maxscaled.c @@ -102,7 +102,7 @@ static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb) username = gwbuf_alloc(strlen(protocol->username) + 1); - strcpy(GWBUF_DATA(username), protocol->username); + strcpy((char*)GWBUF_DATA(username), protocol->username); /* Authenticate the user */ if (dcb->authfunc.extract(dcb, username) == 0 && @@ -261,7 +261,7 @@ static int maxscaled_read_event(DCB* dcb) { case MAXSCALED_STATE_LOGIN: { - maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + maxscaled->username = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); maxscaled->state = MAXSCALED_STATE_PASSWD; dcb_printf(dcb, MAXADMIN_AUTH_PASSWORD_PROMPT); gwbuf_free(head); @@ -270,7 +270,7 @@ static int maxscaled_read_event(DCB* dcb) case MAXSCALED_STATE_PASSWD: { - char *password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + char *password = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); if (admin_verify_inet_user(maxscaled->username, password)) { dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY); diff --git a/server/modules/protocol/telnetd/telnetd.c b/server/modules/protocol/telnetd/telnetd.c index df802bf8e..a855d12d8 100644 --- a/server/modules/protocol/telnetd/telnetd.c +++ b/server/modules/protocol/telnetd/telnetd.c @@ -181,7 +181,7 @@ static int telnetd_read_event(DCB* dcb) switch (telnetd->state) { case TELNETD_STATE_LOGIN: - telnetd->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + telnetd->username = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); /* Strip the cr/lf from the username */ t = strstr(telnetd->username, "\r\n"); if (t) @@ -194,7 +194,7 @@ static int telnetd_read_event(DCB* dcb) gwbuf_consume(head, GWBUF_LENGTH(head)); break; case TELNETD_STATE_PASSWD: - password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + password = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); /* Strip the cr/lf from the username */ t = strstr(password, "\r\n"); if (t) diff --git a/server/modules/routing/avro/avro_client.c b/server/modules/routing/avro/avro_client.c index 1caa06221..fc1341e0a 100644 --- a/server/modules/routing/avro/avro_client.c +++ b/server/modules/routing/avro/avro_client.c @@ -130,7 +130,7 @@ avro_client_do_registration(AVRO_INSTANCE *router, AVRO_CLIENT *client, GWBUF *d const char reg_uuid[] = "REGISTER UUID="; const int reg_uuid_len = strlen(reg_uuid); int data_len = GWBUF_LENGTH(data) - reg_uuid_len; - char *request = GWBUF_DATA(data); + char *request = (char*)GWBUF_DATA(data); int ret = 0; if (strstr(request, reg_uuid) != NULL) diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 10b58dbf4..68b5da163 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -1896,7 +1896,7 @@ int blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { char result[BLRM_COM_STATISTICS_SIZE + 1] = ""; - char *ptr; + uint8_t *ptr; GWBUF *ret; unsigned long len; @@ -1932,7 +1932,7 @@ blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) int blr_ping(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { - char *ptr; + uint8_t *ptr; GWBUF *ret; if ((ret = gwbuf_alloc(5)) == NULL) diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index 7235251fe..84224760f 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -349,7 +349,7 @@ blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) char *ptr; extern char *strcasestr(); - qtext = GWBUF_DATA(queue); + qtext = (char*)GWBUF_DATA(queue); query_len = extract_field((uint8_t *)qtext, 24) - 1; qtext += 5; // Skip header and first byte of the payload query_text = strndup(qtext, query_len); @@ -4712,35 +4712,28 @@ blr_handle_change_master_token(char *input, char *error, CHANGE_MASTER_OPTIONS * static char * blr_get_parsed_command_value(char *input) { - /* space+TAB+= */ - char *sep = " \t="; char *ret = NULL; - char *word; - char *value = NULL; - if (strlen(input)) + if (input && *input) { - value = MXS_STRDUP_A(input); - } - else - { - return ret; - } + char value[strlen(input) + 1]; + strcpy(value, input); - if ((word = get_next_token(NULL, sep, &input)) != NULL) - { - char *ptr; + /* space+TAB+= */ + char *sep = " \t="; + char *word; - /* remove trailing spaces */ - ptr = value + strlen(value) - 1; - while (ptr > value && isspace(*ptr)) + if ((word = get_next_token(NULL, sep, &input)) != NULL) { - *ptr-- = 0; + /* remove trailing spaces */ + char *ptr = value + strlen(value) - 1; + while (ptr > value && isspace(*ptr)) + { + *ptr-- = 0; + } + + ret = MXS_STRDUP_A(strstr(value, word)); } - - ret = MXS_STRDUP_A(strstr(value, word)); - - MXS_FREE(value); } return ret; diff --git a/server/modules/routing/binlog/test/testbinlog.c b/server/modules/routing/binlog/test/testbinlog.c index 2eda6e461..24078b42d 100644 --- a/server/modules/routing/binlog/test/testbinlog.c +++ b/server/modules/routing/binlog/test/testbinlog.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -47,6 +46,9 @@ #include +// This isn't really a clean way of testing +#include "../../../../core/maxscale/service.h" + static void printVersion(const char *progname); static void printUsage(const char *progname); extern int blr_test_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config); diff --git a/server/modules/routing/cli/cli.c b/server/modules/routing/cli/cli.c index 478a2dcdf..61e6a21f8 100644 --- a/server/modules/routing/cli/cli.c +++ b/server/modules/routing/cli/cli.c @@ -270,7 +270,7 @@ execute(ROUTER *instance, void *router_session, GWBUF *queue) /* Extract the characters */ while (queue && (cmdlen < CMDBUFLEN - 1)) { - const char* data = GWBUF_DATA(queue); + const char* data = (char*)GWBUF_DATA(queue); int len = GWBUF_LENGTH(queue); int n = MXS_MIN(len, CMDBUFLEN - cmdlen - 1); diff --git a/server/modules/routing/debugcli/debugcli.c b/server/modules/routing/debugcli/debugcli.c index 1f86c82fc..6660c00bc 100644 --- a/server/modules/routing/debugcli/debugcli.c +++ b/server/modules/routing/debugcli/debugcli.c @@ -291,7 +291,7 @@ execute(ROUTER *instance, void *router_session, GWBUF *queue) /* Extract the characters */ while (queue && (cmdlen < CMDBUFLEN - 1)) { - const char* data = GWBUF_DATA(queue); + const char* data = (char*)GWBUF_DATA(queue); int len = GWBUF_LENGTH(queue); int n = MXS_MIN(len, CMDBUFLEN - cmdlen - 1); diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 8934abf11..fb584b268 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -51,11 +51,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -71,6 +73,7 @@ #include #include #include +#include #include #include @@ -285,6 +288,59 @@ struct subcommand showoptions[] = { EMPTY_OPTION} }; +bool listfuncs_cb(const MODULECMD *cmd, void *data) +{ + DCB *dcb = (DCB*)data; + + dcb_printf(dcb, "%s::%s(", cmd->domain, cmd->identifier); + + + for (int i = 0; i < cmd->arg_count_max; i++) + { + modulecmd_arg_type_t *type = &cmd->arg_types[i]; + + if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT) + { + char *t = modulecmd_argtype_to_str(&cmd->arg_types[i]); + + if (t) + { + dcb_printf(dcb, "%s%s", t, i < cmd->arg_count_max - 1 ? " " : ""); + MXS_FREE(t); + } + } + } + + dcb_printf(dcb, ")\n"); + + + for (int i = 0; i < cmd->arg_count_max; i++) + { + modulecmd_arg_type_t *type = &cmd->arg_types[i]; + + if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT) + { + + char *t = modulecmd_argtype_to_str(&cmd->arg_types[i]); + + if (t) + { + dcb_printf(dcb, " %s - %s\n", t, cmd->arg_types[i].description); + MXS_FREE(t); + } + } + } + + dcb_printf(dcb, "\n"); + + return true; +} + +void dListFunctions(DCB *dcb) +{ + modulecmd_foreach(NULL, NULL, listfuncs_cb, dcb); +} + /** * The subcommands of the list command */ @@ -350,6 +406,12 @@ struct subcommand listoptions[] = "List the status of the polling threads in MaxScale", {0, 0, 0} }, + { + "functions", 0, 0, dListFunctions, + "List registered functions", + "List all registered functions", + {0} + }, { EMPTY_OPTION} }; @@ -743,28 +805,9 @@ static void cmd_AddServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3 for (int i = 0; i < items && values[i]; i++) { - SERVICE *service = service_find(values[i]); - MONITOR *monitor = monitor_find(values[i]); - - if (service || monitor) + if (runtime_link_server(server, values[i])) { - ss_dassert(service == NULL || monitor == NULL); - - if (service) - { - serviceAddBackend(service, server); - service_serialize_servers(service); - } - else if (monitor) - { - monitorAddServer(monitor, server); - monitor_serialize_servers(monitor); - } - - const char *target = service ? "service" : "monitor"; - - MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, target, values[i]); - dcb_printf(dcb, "Added server '%s' to %s '%s'\n", server->unique_name, target, values[i]); + dcb_printf(dcb, "Added server '%s' to '%s'\n", server->unique_name, values[i]); } else { @@ -811,27 +854,9 @@ static void cmd_RemoveServer(DCB *dcb, SERVER *server, char *v1, char *v2, char for (int i = 0; i < items && values[i]; i++) { - SERVICE *service = service_find(values[i]); - MONITOR *monitor = monitor_find(values[i]); - - if (service || monitor) + if (runtime_unlink_server(server, values[i])) { - ss_dassert(service == NULL || monitor == NULL); - - if (service) - { - serviceRemoveBackend(service, server); - service_serialize_servers(service); - } - else if (monitor) - { - monitorRemoveServer(monitor, server); - monitor_serialize_servers(monitor); - } - - const char *target = service ? "service" : "monitor"; - MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, target, values[i]); - dcb_printf(dcb, "Removed server '%s' from %s '%s'\n", server->unique_name, target, values[i]); + dcb_printf(dcb, "Removed server '%s' from '%s'\n", server->unique_name, values[i]); } else { @@ -983,7 +1008,7 @@ static void createServer(DCB *dcb, char *name, char *address, char *port, if (server_find_by_unique_name(name) == NULL) { - if (server_create(name, address, port, protocol, authenticator, authenticator_options)) + if (runtime_create_server(name, address, port, protocol, authenticator, authenticator_options)) { dcb_printf(dcb, "Created server '%s'\n", name); } @@ -1000,6 +1025,23 @@ static void createServer(DCB *dcb, char *name, char *address, char *port, spinlock_release(&server_mod_lock); } +static void createListener(DCB *dcb, SERVICE *service, char *name, char *address, + char *port, char *protocol, char *authenticator, + char *authenticator_options, char *key, char *cert, + char *ca, char *version, char *depth) +{ + if (runtime_create_listener(service, name, address, port, protocol, + authenticator, authenticator_options, + key, cert, ca, version, depth)) + { + dcb_printf(dcb, "Listener '%s' created\n", name); + } + else + { + dcb_printf(dcb, "Failed to create listener '%s', see log for more details\n", name); + } +} + struct subcommand createoptions[] = { { @@ -1019,6 +1061,33 @@ struct subcommand createoptions[] = ARG_TYPE_STRING, ARG_TYPE_STRING } }, + { + "listener", 2, 12, createListener, + "Create a new listener for a service", + "Usage: create listener SERVICE NAME [HOST] [PORT] [PROTOCOL] [AUTHENTICATOR] [OPTIONS]\n" + " [SSL_KEY] [SSL_CERT] [SSL_CA] [SSL_VERSION] [SSL_VERIFY_DEPTH]\n\n" + "Create a new server from the following parameters.\n" + "SERVICE Service where this listener is added\n" + "NAME Listener name\n" + "HOST Listener host address (default 0.0.0.0)\n" + "PORT Listener port (default 3306)\n" + "PROTOCOL Listener protocol (default MySQLClient)\n" + "AUTHENTICATOR Authenticator module name (default MySQLAuth)\n" + "OPTIONS Options for the authenticator module\n" + "SSL_KEY Path to SSL private key\n" + "SSL_CERT Path to SSL certificate\n" + "SSL_CA Path to CA certificate\n" + "SSL_VERSION SSL version (default MAX)\n" + "SSL_VERIFY_DEPTH Certificate verification depth\n\n" + "The first two parameters are required, the others are optional.\n" + "Any of the optional parameters can also have the value 'default'\n" + "which will be replaced with the default value.\n", + { + ARG_TYPE_SERVICE, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + } + }, { EMPTY_OPTION } @@ -1032,7 +1101,7 @@ static void destroyServer(DCB *dcb, SERVER *server) char name[strlen(server->unique_name) + 1]; strcpy(name, server->unique_name); - if (server_destroy(server)) + if (runtime_destroy_server(server)) { dcb_printf(dcb, "Destroyed server '%s'\n", name); } @@ -1042,6 +1111,18 @@ static void destroyServer(DCB *dcb, SERVER *server) } } +static void destroyListener(DCB *dcb, SERVICE *service, const char *name) +{ + if (runtime_destroy_listener(service, name)) + { + dcb_printf(dcb, "Destroyed listener '%s'\n", name); + } + else + { + dcb_printf(dcb, "Failed to destroy listener '%s', see log file for more details\n", name); + } +} + struct subcommand destroyoptions[] = { { @@ -1050,76 +1131,17 @@ struct subcommand destroyoptions[] = "Usage: destroy server NAME", {ARG_TYPE_SERVER} }, + { + "listener", 2, 2, destroyListener, + "Destroy a listener", + "Usage: destroy listener SERVICE NAME", + {ARG_TYPE_SERVICE, ARG_TYPE_STRING} + }, { EMPTY_OPTION } }; -static bool handle_alter_server(SERVER *server, char *key, char *value) -{ - bool valid = true; - - if (strcmp(key, "address") == 0) - { - server_update_address(server, value); - } - else if (strcmp(key, "port") == 0) - { - server_update_port(server, atoi(value)); - } - else if (strcmp(key, "monuser") == 0) - { - server_update_credentials(server, value, server->monpw); - } - else if (strcmp(key, "monpw") == 0) - { - server_update_credentials(server, server->monuser, value); - } - else - { - valid = false; - } - - return valid; -} - -void handle_server_ssl(DCB *dcb, SERVER *server, CONFIG_CONTEXT *obj) -{ - if (config_have_required_ssl_params(obj)) - { - int err = 0; - SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err); - - if (err == 0 && ssl && listener_init_SSL(ssl) == 0) - { - /** Sync to prevent reads on partially initialized server_ssl */ - atomic_synchronize(); - - server->server_ssl = ssl; - if (server_serialize(server)) - { - dcb_printf(dcb, "SSL enabled for server '%s'\n", server->unique_name); - } - else - { - dcb_printf(dcb, "SSL enabled for server '%s' but persisting " - "it to disk failed, see log for more details.\n", - server->unique_name); - } - } - else - { - dcb_printf(dcb, "Enabling SSL for server '%s' failed, see log " - "for more details.\n", server->unique_name); - } - } - else - { - dcb_printf(dcb, "Error: SSL configuration requires the following parameters:\n" - "ssl=required ssl_key=PATH ssl_cert=PATH ssl_ca_cert=PATH\n"); - } -} - /** * @brief Process multiple alter operations at once * @@ -1134,6 +1156,12 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11}; const int items = sizeof(values) / sizeof(values[0]); CONFIG_CONTEXT *obj = NULL; + char *ssl_key = NULL; + char *ssl_cert = NULL; + char *ssl_ca = NULL; + char *ssl_version = NULL; + char *ssl_depth = NULL; + bool enable = false; for (int i = 0; i < items && values[i]; i++) { @@ -1146,19 +1174,33 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, if (config_is_ssl_parameter(key)) { - /** - * All the required SSL parameters must be defined at once to - * enable SSL for created servers. This removes the problem - * of partial configuration and allows a somewhat atomic - * operation. - */ - if ((obj == NULL && (obj = config_context_create(server->unique_name)) == NULL) || - (!config_add_param(obj, key, value))) + if (strcmp("ssl_cert", key) == 0) { - dcb_printf(dcb, "Internal error, see log for more details\n"); + ssl_cert = value; + } + else if (strcmp("ssl_ca_cert", key) == 0) + { + ssl_ca = value; + } + else if (strcmp("ssl_key", key) == 0) + { + ssl_key = value; + } + else if (strcmp("ssl_version", key) == 0) + { + ssl_version = value; + } + else if (strcmp("ssl_cert_verify_depth", key) == 0) + { + ssl_depth = value; + } + else + { + enable = strcmp("ssl", key) == 0 && strcmp(value, "required") == 0; + /** Must be 'ssl' */ } } - else if (!handle_alter_server(server, key, value)) + else if (!runtime_alter_server(server, key, value)) { dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value); } @@ -1169,87 +1211,24 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, } } - if (obj) + if (enable || ssl_key || ssl_cert || ssl_ca) { - /** We have SSL parameters, try to process them */ - handle_server_ssl(dcb, server, obj); - config_context_free(obj); - } -} - -/** - * @brief Convert a string value to a positive integer - * - * If the value is not a positive integer, an error is printed to @c dcb. - * - * @param value String value - * @return 0 on error, otherwise a positive integer - */ -static long get_positive_int(const char *value) -{ - char *endptr; - long ival = strtol(value, &endptr, 10); - - if (*endptr == '\0' && ival > 0) - { - return ival; - } - - return 0; -} - -static bool handle_alter_monitor(MONITOR *monitor, char *key, char *value) -{ - bool valid = false; - - if (strcmp(key, "user") == 0) - { - valid = true; - monitorAddUser(monitor, value, monitor->password); - } - else if (strcmp(key, "password") == 0) - { - valid = true; - monitorAddUser(monitor, monitor->user, value); - } - else if (strcmp(key, "monitor_interval") == 0) - { - long ival = get_positive_int(value); - if (ival) + if (enable && ssl_key && ssl_cert && ssl_ca) { - valid = true; - monitorSetInterval(monitor, ival); + /** We have SSL parameters, try to process them */ + if (!runtime_enable_server_ssl(server, ssl_key, ssl_cert, ssl_ca, + ssl_version, ssl_depth)) + { + dcb_printf(dcb, "Enabling SSL for server '%s' failed, see log " + "for more details.\n", server->unique_name); + } + } + else + { + dcb_printf(dcb, "Error: SSL configuration requires the following parameters:\n" + "ssl=required ssl_key=PATH ssl_cert=PATH ssl_ca_cert=PATH\n"); } } - else if (strcmp(key, "backend_connect_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); - } - } - else if (strcmp(key, "backend_write_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); - } - } - else if (strcmp(key, "backend_read_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); - } - } - - return valid; } static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v3, @@ -1268,7 +1247,7 @@ static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v { *value++ = '\0'; - if (!handle_alter_monitor(monitor, key, value)) + if (!runtime_alter_monitor(monitor, key, value)) { dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value); } @@ -1311,6 +1290,78 @@ struct subcommand alteroptions[] = } }; +static inline bool requires_output_dcb(const MODULECMD *cmd) +{ + modulecmd_arg_type_t *type = &cmd->arg_types[0]; + return cmd->arg_count_max > 0 && MODULECMD_GET_TYPE(type) == MODULECMD_ARG_OUTPUT; +} + +static void callFunction(DCB *dcb, char *domain, char *id, char *v3, + char *v4, char *v5, char *v6, char *v7, char *v8, char *v9, + char *v10, char *v11, char *v12) +{ + const void *values[11] = {v3, v4, v5, v6, v7, v8, v9, v10, v11, v12}; + const int valuelen = sizeof(values) / sizeof(values[0]); + int numargs = 0; + + while (numargs < valuelen && values[numargs]) + { + numargs++; + } + + const MODULECMD *cmd = modulecmd_find_command(domain, id); + + if (cmd) + { + if (requires_output_dcb(cmd)) + { + /** The function requires a DCB for output, add the client DCB + * as the first argument */ + for (int i = valuelen - 1; i > 0; i--) + { + values[i] = values[i - 1]; + } + values[0] = dcb; + numargs += numargs + 1 < valuelen - 1 ? 1 : 0; + } + + MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, numargs, values); + + if (arg) + { + if (!modulecmd_call_command(cmd, arg)) + { + dcb_printf(dcb, "Failed to call function: %s\n", modulecmd_get_error()); + } + modulecmd_arg_free(arg); + } + else + { + dcb_printf(dcb, "Failed to parse arguments: %s\n", modulecmd_get_error()); + } + } + else + { + dcb_printf(dcb, "Function not found: %s\n", modulecmd_get_error()); + } +} + +struct subcommand calloptions[] = +{ + { + "function", 2, 12, callFunction, + "Call module function", + "Usage: call function NAMESPACE FUNCTION ARGS...\n" + "To list all registered functions, run 'list functions'.\n", + { ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING} + }, + { + EMPTY_OPTION + } +}; + /** * The debug command table */ @@ -1335,7 +1386,8 @@ static struct { "restart", restartoptions }, { "shutdown", shutdownoptions }, { "show", showoptions }, - { "sync", syncoptions }, + { "sync", syncoptions }, + { "call", calloptions }, { NULL, NULL } }; @@ -1457,7 +1509,7 @@ execute_cmd(CLI_SESSION *cli) args[0] = cli->cmdbuf; ptr = args[0]; lptr = ptr; - i = 0; + i = 1; /* * Break the command line into a number of words. Whitespace is used * to delimit words and may be escaped by use of the \ character or @@ -1465,7 +1517,7 @@ execute_cmd(CLI_SESSION *cli) * The array args contains the broken down words, one per index. */ - while (*ptr) + while (*ptr && i <= MAXARGS + 2) { if (escape_next) { @@ -1489,19 +1541,7 @@ execute_cmd(CLI_SESSION *cli) break; } - if (args[i] == ptr) - { - args[i] = ptr + 1; - } - else - { - i++; - if (i >= MAXARGS - 1) - { - break; - } - args[i] = ptr + 1; - } + args[i++] = ptr + 1; ptr++; lptr++; } @@ -1523,14 +1563,13 @@ execute_cmd(CLI_SESSION *cli) } } *lptr = 0; - args[MXS_MIN(MAXARGS - 1, i + 1)] = NULL; + args[i] = NULL; if (args[0] == NULL || *args[0] == 0) { return 1; } - for (i = 0; args[i] && *args[i]; i++) - ; + argc = i - 2; /* The number of extra arguments to commands */ if (!strcasecmp(args[0], "help")) @@ -1571,7 +1610,7 @@ execute_cmd(CLI_SESSION *cli) dcb_printf(dcb, "Available options to the %s command:\n", args[1]); for (j = 0; cmds[i].options[j].arg1; j++) { - dcb_printf(dcb, "'%s' - %s\n\n\t%s\n\n", cmds[i].options[j].arg1, + dcb_printf(dcb, "'%s' - %s\n\n%s\n\n", cmds[i].options[j].arg1, cmds[i].options[j].help, cmds[i].options[j].devhelp); } @@ -1755,7 +1794,7 @@ shutdown_service(DCB *dcb, SERVICE *service) static void restart_service(DCB *dcb, SERVICE *service) { - serviceRestart(service); + serviceStart(service); } /** diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 05eaa8d4d..3115899d8 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -422,7 +422,8 @@ getCapabilities(void) static int maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) { - char result[1000], *ptr; + char result[1000]; + uint8_t *ptr; GWBUF *ret; int len; @@ -456,7 +457,7 @@ maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) static int maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) { - char *ptr; + uint8_t *ptr; GWBUF *ret; int len; diff --git a/server/modules/routing/maxinfo/maxinfo_exec.c b/server/modules/routing/maxinfo/maxinfo_exec.c index d8a990c3d..a82433b1b 100644 --- a/server/modules/routing/maxinfo/maxinfo_exec.c +++ b/server/modules/routing/maxinfo/maxinfo_exec.c @@ -694,7 +694,7 @@ void exec_restart_service(DCB *dcb, MAXINFO_TREE *tree) SERVICE* service = service_find(tree->value); if (service) { - serviceRestart(service); + serviceStart(service); maxinfo_send_ok(dcb); } else diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c index 3b526c612..88bb810d7 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c @@ -780,6 +780,8 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, else if (!trx_active && !load_active && !qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) && !qc_query_is_type(qtype, QUERY_TYPE_WRITE) && + !qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) && + !qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) && (qc_query_is_type(qtype, QUERY_TYPE_READ) || qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c index b6b43cb23..8eaa689ea 100644 --- a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c +++ b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c @@ -327,7 +327,7 @@ bool check_for_multi_stmt(GWBUF *buf, void *protocol, mysql_server_cmd_t packet_ if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS && packet_type == MYSQL_COM_QUERY) { - char *ptr, *data = GWBUF_DATA(buf) + 5; + char *ptr, *data = (char*)GWBUF_DATA(buf) + 5; /** Payload size without command byte */ int buflen = gw_mysql_get_byte3((uint8_t *)GWBUF_DATA(buf)) - 1; diff --git a/server/test/.gitignore b/server/test/.gitignore deleted file mode 100644 index 184d7f57f..000000000 --- a/server/test/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# directories generated/filled by "make testall" -bin/ -Documentation/ -etc/ -lib/ -log/ -modules/ -mysql/