diff --git a/.travis.yml b/.travis.yml index 052d7da42..a420bd115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ addons: - pandoc - uuid - uuid-dev + - libmicrohttpd-dev coverity_scan: project: name: "mariadb-corporation/MaxScale" diff --git a/CMakeLists.txt b/CMakeLists.txt index e3974d5af..c1e0cbda4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,9 +65,10 @@ endif() if(NOT JANSSON_FOUND) message(STATUS "Building Jansson from source.") include(cmake/BuildJansson.cmake) - include_directories(${JANSSON_INCLUDE_DIR}) endif() +include_directories(${JANSSON_INCLUDE_DIR}) + # You can find the variables set by this in the FindCURL.cmake file # which is a default module in CMake. diff --git a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md index 787c6b69e..71e1edc2b 100644 --- a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md +++ b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md @@ -11,6 +11,7 @@ requirements are as follows: * Bison 2.7 or later * Flex 2.5.35 or later * libuuid +* libmicrohttpd ## Required packages @@ -30,7 +31,8 @@ You can install the packages with the following commands. ``` sudo yum install git gcc gcc-c++ ncurses-devel bison flex glibc-devel cmake \ libgcc perl make libtool openssl openssl-devel libcurl-devel pcre-devel \ - tcl tcl-devel systemtap-sdt-devel libuuid libuuid-devel sqlite3 sqlite3-devel + tcl tcl-devel systemtap-sdt-devel libuuid libuuid-devel sqlite3 sqlite3-devel \ + libmicrohttpd-devel ``` ### Required packages on Ubuntu and Debian systems @@ -40,7 +42,7 @@ require other packages in addition to these. ``` git build-essential libssl-dev ncurses-dev bison flex cmake perl libtool \ -libcurl4-openssl-dev libpcre3-dev tlc tcl-dev uuid uuid-dev sqlite3-dev +libcurl4-openssl-dev libpcre3-dev tlc tcl-dev uuid uuid-dev sqlite3-dev libmicrohttpd-dev ``` You can install the packages with the following command. @@ -48,7 +50,7 @@ You can install the packages with the following command. ``` sudo apt-get install git build-essential libssl-dev ncurses-dev bison flex \ cmake perl libtool libcurl4-openssl-dev libpcre3-dev tcl tcl-dev uuid \ - uuid-dev libsqlite3-dev + uuid-dev libsqlite3-dev libmicrohttpd-dev ``` ## Preparing the MariaDB MaxScale build diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 4d86b0eee..086a939dc 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -519,6 +519,60 @@ This will log all statements that cannot be parsed completely. This may be useful if you suspect that MariaDB MaxScale routes statements to the wrong server (e.g. to a slave instead of to a master). +### REST API Configuration + +The MaxScale REST API is an HTTP interface that provides JSON format data +intended to be consumed by monitoring appllications and visualization tools. + +The following options must be defined under the `[maxscale]` section in the +configuration file. + +#### `admin_host` + +The network interface where the HTTP admin interface listens on. The default +value is the IPv6 address `::` which listens on all available network +interfaces. + +#### `admin_port` + +The port where the HTTP admin interface listens on. The default value is port +8989. + +#### `admin_auth` + +Enable HTTP admin interface authentication using HTTP Basic Access +authentication. This is not a secure method of authentication but it does add a +small layer of security. This option id disabled by default. + +#### `admin_user` + +The HTTP admin interface username. This is the username which is used when +_admin_auth_ is enabled. The default user for the HTTP admin interface is +`admin`. + +#### `admin_password` + +The HTTP admin interface password. This is the which which is used when +_admin_auth_ is enabled. The default password for the HTTP admin interface is +`mariadb`. + +#### `admin_ssl_key` + +The path to the TLS private key in PEM format for the admin interface. + +If the `admin_ssl_key`, `admin_ssl_cert` and `admin_ssl_ca_cert` options are all +defined, the admin interface will use encrypted HTTPS instead of plain HTTP. + +#### `admin_ssl_cert` + +The path to the TLS public certificate in PEM format. See `admin_ssl_key` +documentation for more details. + +#### `admin_ssl_ca_cert` + +The path to the TLS CA certificate in PEM format. See `admin_ssl_key` +documentation for more details. + ### Service A service represents the database service that MariaDB MaxScale offers to the @@ -1368,8 +1422,8 @@ The following list of service parameters can be updated at runtime. * max_slave_connections * max_slave_replication_lag -In addition to these parameters, the server specific user credentials, _monuser_ -and _monpw_, can also be updated at runtime. +In addition to these parameters, the server specific user credentials, _monitoruser_ +and _monitorpw_, can also be updated at runtime. ### Limitations diff --git a/Documentation/REST-API/API.md b/Documentation/REST-API/API.md index afa08573b..1a297126d 100644 --- a/Documentation/REST-API/API.md +++ b/Documentation/REST-API/API.md @@ -15,6 +15,12 @@ This document describes the version 1 of the MaxScale REST API. - [Resources](#resources) - [Common Request Parameter](#common-request-parameters) +## Note About Syntax + +Although JSON does not define a syntax for comments, some of the JSON examples +have C-style inline comments in them. These comments use `//` to mark the start +of the comment and extend to the end of the current line. + ## HTTP Headers ### Request Headers @@ -421,12 +427,65 @@ The MaxScale REST API provides the following resources. - [/sessions](Resources-Session.md) - [/users](Resources-User.md) +### Resource Relationships + +All resources return complete JSON objects. The returned objects can have a +_relationships_ field that represents any relations the object has to other +objects. This closely resembles the JSON API definition of links. + +In the _relationships_ objects, all resources have a _self_ link that points to +the resource itself. This allows for easier updating of resources as the reply +URL is included in the response itself. + +The following lists the resources and the types of links each resource can have +in addition to the _self_ link. + +- `services` - Service resource + + - `servers` + + List of servers used by the service + + - `filters` + + List of filters used by the service + +- `monitors` - Monitor resource + + - `servers` + + List of servers used by the monitor + +- `filters` - Filter resource + + - `services` + + List of services that use this filter + +- `servers` - Server resource + + - `services` + + List of services that use this server + + - `monitors` + + List of monitors that use this server + ## Common Request Parameters Most of the resources that support GET also support the following parameters. See the resource documentation for a list of supported request parameters. +- `pretty` + + - Pretty-print output. + + If this parameter is set to `true` then the returned objects are + formatted in a more human readable format. All resources support this + parameter. + - `fields` - A list of fields to return. diff --git a/Documentation/REST-API/Resources-Server.md b/Documentation/REST-API/Resources-Server.md index a73b0d531..b2bc34569 100644 --- a/Documentation/REST-API/Resources-Server.md +++ b/Documentation/REST-API/Resources-Server.md @@ -6,40 +6,65 @@ A server resource represents a backend database server. ### Get a server -Get a single server. The _:name_ in the URI must be a valid server name with all -whitespace replaced with hyphens. The server names are case-insensitive. - ``` GET /servers/:name ``` +Get a single server. The _:name_ in the URI must be a valid server name with all +whitespace replaced with hyphens. The server names are case-insensitive. + +**Note**: The _parameters_ field contains all custom parameters for + servers, including the server weighting parameters. + #### Response ``` Status: 200 OK { - "name": "db-serv-1", - "address": "192.168.121.58", - "port": 3306, - "protocol": "MySQLBackend", - "status": [ - "master", - "running" - ], + "name": "server1", "parameters": { - "report_weight": 10, - "app_weight": 2 + "address": "127.0.0.1", + "port": 3000, + "protocol": "MySQLBackend", + "monitoruser": "maxuser", + "monitorpw": "maxpwd" + }, + "status": "Master, Running", + "version": "10.1.22-MariaDB", + "node_id": 3000, + "master_id": -1, + "replication_depth": 0, + "slaves": [ + 3001 + ], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/server1", + "services": [ + "http://localhost:8989/services/RW-Split-Router", + "http://localhost:8989/services/Read-Connection-Router" + ], + "monitors": [ + "http://localhost:8989/monitors/MySQL-Monitor" + ] } } ``` -**Note**: The _parameters_ field contains all custom parameters for - servers, including the server weighting parameters. +Server not found: + +``` +Status: 404 Not Found +``` #### Supported Request Parameter -- `fields` +- `pretty` ### Get all servers @@ -49,35 +74,72 @@ GET /servers #### Response +Response contains an array of all servers. + ``` Status: 200 OK [ { - "name": "db-serv-1", - "address": "192.168.121.58", - "port": 3306, - "protocol": "MySQLBackend", - "status": [ - "master", - "running" - ], + "name": "server1", "parameters": { - "report_weight": 10, - "app_weight": 2 + "address": "127.0.0.1", + "port": 3000, + "protocol": "MySQLBackend", + "monitoruser": "maxuser", + "monitorpw": "maxpwd" + }, + "status": "Master, Running", + "version": "10.1.22-MariaDB", + "node_id": 3000, + "master_id": -1, + "replication_depth": 0, + "slaves": [ + 3001 + ], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/server1", + "services": [ + "http://localhost:8989/services/RW-Split-Router", + "http://localhost:8989/services/Read-Connection-Router" + ], + "monitors": [ + "http://localhost:8989/monitors/MySQL-Monitor" + ] } }, { - "name": "db-serv-2", - "address": "192.168.121.175", - "port": 3306, - "status": [ - "slave", - "running" - ], - "protocol": "MySQLBackend", + "name": "server2", "parameters": { - "app_weight": 6 + "address": "127.0.0.1", + "port": 3001, + "protocol": "MySQLBackend", + "my-weighting-parameter": "3" + }, + "status": "Slave, Running", + "version": "10.1.22-MariaDB", + "node_id": 3001, + "master_id": 3000, + "replication_depth": 1, + "slaves": [], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/server2", + "services": [ + "http://localhost:8989/services/RW-Split-Router" + ], + "monitors": [ + "http://localhost:8989/monitors/MySQL-Monitor" + ] } } ] @@ -85,38 +147,141 @@ Status: 200 OK #### Supported Request Parameter -- `fields` -- `range` +- `pretty` -### Update a server - -**Note**: The update mechanisms described here are provisional and most likely - will change in the future. This description is only for design purposes and - does not yet work. - -Partially update a server. The _:name_ in the URI must map to a server name with -all whitespace replaced with hyphens and the request body must be a valid JSON -Patch document which is applied to the resource. +### Create a server ``` -PATCH /servers/:name +POST /servers ``` -### Modifiable Fields - -|Field |Type |Description | -|-----------|------------|-----------------------------------------------------------------------------| -|address |string |Server address | -|port |number |Server port | -|parameters |object |Server extra parameters | -|state |string array|Server state, array of `master`, `slave`, `synced`, `running` or `maintenance`. An empty array is interpreted as a server that is down.| +Create a new server by defining the resource. The posted object must define the +_name_ field with the name of the server and the _parameters_ field with JSON +object containing values for the _address_ and _port_ parameters. The following +is the minimal required JSON object for defining a new server. ``` { - { "op": "replace", "path": "/address", "value": "192.168.0.100" }, - { "op": "replace", "path": "/port", "value": 4006 }, - { "op": "add", "path": "/state/0", "value": "maintenance" }, - { "op": "replace", "path": "/parameters/report_weight", "value": 1 } + "name": "test-server", + "parameters": { + "address": "127.0.0.1", + "port": 3003 + } +} +``` + +#### Response + +Response contains the created resource. + +``` +Status: 200 OK + +{ + "name": "test-server", + "parameters": { + "address": "127.0.0.1", + "port": 3003, + "protocol": "MySQLBackend" + }, + "status": "Running", + "node_id": -1, + "master_id": -1, + "replication_depth": -1, + "slaves": [], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/test-server" + } +} +``` + +Invalid JSON body: + +``` +Status: 400 Bad Request +``` + +#### Supported Request Parameter + +- `pretty` + +### Update a server + +``` +PUT /servers/:name +``` + +The _:name_ in the URI must map to a server name with all whitespace replaced +with hyphens and the request body must be a valid JSON document representing the +modified server. If the server in question is not found, a 404 Not Found +response is returned. + +### Modifiable Fields + +The following standard server parameter can be modified. + +- [address](../Getting-Started/Configuration-Guide.md#address) +- [port](../Getting-Started/Configuration-Guide.md#port) +- [monitoruser](../Getting-Started/Configuration-Guide.md#monitoruser) +- [monitorpw](../Getting-Started/Configuration-Guide.md#monitorpw) + +Refer to the documentation on these parameters for valid values. + +The server weighting parameters can also be added, removed and updated. To +remove a parameter, define the value of that parameter as the _null_ JSON type +e.g. `{ "my-param": null }`. To add a parameter, add a new key-value pair to +the _parameters_ object with a name that does not conflict with the standard +parameters. To modify a weighting parameter, simply change the value. + +In addition to standard parameters, the _services_ and _monitors_ fields of the +_relationships_ object can be modified. Removal, addition and modification of +the links will change which service and monitors use this server. + +For example, removing the first value in the _services_ list in the +_relationships_ object from the following JSON document will remove the +_server1_ from the service _RW-Split-Router_. + +Removing a service from a server is analogous to removing the server from the +service. Both unlink the two objects from each other. + +``` +{ + "name": "server1", + "parameters": { + "address": "127.0.0.1", + "port": 3000, + "protocol": "MySQLBackend", + "monitoruser": "maxuser", + "monitorpw": "maxpwd" + }, + "status": "Master, Running", + "version": "10.1.22-MariaDB", + "node_id": 3000, + "master_id": -1, + "replication_depth": 0, + "slaves": [ + 3001 + ], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/server1", + "services": [ + "http://localhost:8989/services/RW-Split-Router", // This value is removed + "http://localhost:8989/services/Read-Connection-Router" + ], + "monitors": [ + "http://localhost:8989/monitors/MySQL-Monitor" + ] + } } ``` @@ -128,21 +293,89 @@ Response contains the modified resource. Status: 200 OK { - "name": "db-serv-1", - "protocol": "MySQLBackend", - "address": "192.168.0.100", - "port": 4006, - "state": [ - "maintenance", - "running" - ], + "name": "server1", "parameters": { - "report_weight": 1, - "app_weight": 2 + "address": "127.0.0.1", + "port": 3000, + "protocol": "MySQLBackend", + "monitoruser": "maxuser", + "monitorpw": "maxpwd" + }, + "status": "Master, Running", + "version": "10.1.22-MariaDB", + "node_id": 3000, + "master_id": -1, + "replication_depth": 0, + "slaves": [ + 3001 + ], + "statictics": { + "connections": 0, + "total_connections": 0, + "active_operations": 0 + }, + "relationships": { + "self": "http://localhost:8989/servers/server1", + "services": [ + "http://localhost:8989/services/Read-Connection-Router" + ], + "monitors": [ + "http://localhost:8989/monitors/MySQL-Monitor" + ] } } ``` +Server not found: + +``` +Status: 404 Not Found +``` + +Invalid JSON body: + +``` +Status: 400 Bad Request +``` + +#### Supported Request Parameter + +- `pretty` + +### Destroy a server + +``` +DELETE /servers/:name +``` + +The _:name_ in the URI must map to a server name with all whitespace replaced +with hyphens. + +A server can only be deleted if the only relations in the _relationships_ object +is the _self_ link. + +#### Response + +OK: + +``` +Status: 204 No Content +``` + +Server not found: + +``` +Status: 404 Not Found +``` + +Server is in use: + +``` +Status: 400 Bad Request +``` + +# **TODO:** Implement the following features + ### Get all connections to a server Get all connections that are connected to a server. diff --git a/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md index b7caa39a6..e315436d4 100644 --- a/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md @@ -10,6 +10,13 @@ report at [Jira](https://jira.mariadb.org). ## Changed Features +### Whitespace in Object Names + +Significant whitespace in object names is now deprecated. All object names +(services, servers, etc.) will be converted to a compatible format by +squeezing repeating whitespace and replacing it with hyphens. If any +object name conversions take place, a warning will be logged. + ### NamedServerFilter This filter now uses the PCRE2-libarary to match queries. Previously, it used diff --git a/LICENSE-THIRDPARTY.TXT b/LICENSE-THIRDPARTY.TXT index 45f5071f5..a56be2180 100644 --- a/LICENSE-THIRDPARTY.TXT +++ b/LICENSE-THIRDPARTY.TXT @@ -84,3 +84,25 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The Jansson JSON parsing library is distributed under the MIT license. + +Copyright (c) 2009-2016 Petri Lehtinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/cmake/FindJansson.cmake b/cmake/FindJansson.cmake index 8c2216cac..67ad57525 100644 --- a/cmake/FindJansson.cmake +++ b/cmake/FindJansson.cmake @@ -6,7 +6,9 @@ # JANSSON_INCLUDE_DIR - Path to Jansson headers find_path(JANSSON_INCLUDE_DIR jansson.h) -find_library(JANSSON_LIBRARIES NAMES libjansson.so libjansson.a) + +# Use the static library +find_library(JANSSON_LIBRARIES NAMES libjansson.a) if (JANSSON_INCLUDE_DIR AND JANSSON_LIBRARIES) message(STATUS "Found Jansson: ${JANSSON_LIBRARIES}") diff --git a/examples/roundrobinrouter.cpp b/examples/roundrobinrouter.cpp index 1b104a729..c7a1c5fbe 100644 --- a/examples/roundrobinrouter.cpp +++ b/examples/roundrobinrouter.cpp @@ -573,6 +573,7 @@ static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session); static void freeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session); static int routeQuery(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* querybuf); static void diagnostics(MXS_ROUTER* instance, DCB* dcb); +static json_t* diagnostics_json(const MXS_ROUTER* instance); static void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session, GWBUF* resultbuf, DCB* backend_dcb); static void handleError(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session, @@ -600,6 +601,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, routeQuery, diagnostics, + diagnostics_json, clientReply, handleError, getCapabilities, @@ -767,6 +769,27 @@ static void diagnostics(MXS_ROUTER* instance, DCB* dcb) dcb_printf(dcb, "\t\tClient replies routed: %lu\n", router->m_routing_c); } +/** + * @brief Diagnostics routine (API) + * + * Print router statistics to the DCB passed in. This is usually called by the + * MaxInfo or MaxAdmin modules. + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static json_t* diagnostics_json(const MXS_ROUTER* instance) +{ + const RRRouter* router = static_cast(instance); + json_t* rval = json_object(); + + json_object_set_new(rval, "queries_ok", json_integer(router->m_routing_s)); + json_object_set_new(rval, "queries_failed", json_integer(router->m_routing_f)); + json_object_set_new(rval, "replies", json_integer(router->m_routing_c)); + + return rval; +} + /** * @brief Client Reply routine (API) * diff --git a/examples/testfilter.c b/examples/testfilter.c index 522f894ca..8b2a2ab3d 100644 --- a/examples/testfilter.c +++ b/examples/testfilter.c @@ -29,20 +29,17 @@ * @endverbatim */ - -static MXS_FILTER *createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params); -static MXS_FILTER_SESSION *newSession(MXS_FILTER *instance, MXS_SESSION *session); -static void closeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); -static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); -static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); -static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); +static MXS_FILTER *createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params); +static MXS_FILTER_SESSION *newSession(MXS_FILTER *instance, MXS_SESSION *session); +static void closeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); +static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); +static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); +static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); static void destroyInstance(MXS_FILTER *instance); - - - /** * A dummy instance structure */ @@ -82,6 +79,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No clientReply diagnostic, + diagnostic_json, getCapabilities, destroyInstance, }; @@ -239,6 +237,22 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) my_instance->sessions); } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * 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 json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + return NULL; +} + /** * Capability routine. * diff --git a/examples/testroute.c b/examples/testroute.c index c344917d0..1cbdfa469 100644 --- a/examples/testroute.c +++ b/examples/testroute.c @@ -22,6 +22,7 @@ static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue, DCB*); static void diagnostic(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostic_json(const MXS_ROUTER *instance); static uint64_t getCapabilities(MXS_ROUTER* instance); static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, @@ -56,6 +57,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, routeQuery, diagnostic, + diagnostic_json, clientReply, handleError, getCapabilities, @@ -150,6 +152,17 @@ diagnostic(MXS_ROUTER *instance, DCB *dcb) { } +/** + * Diagnostics routine + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static json_t* diagnostic_json(const MXS_ROUTER *instance) +{ + return NULL; +} + static uint64_t getCapabilities(MXS_ROUTER* instance) { return RCAP_TYPE_NONE; diff --git a/include/maxscale/adminusers.h.in b/include/maxscale/adminusers.h.in index a9540db30..89de638d3 100644 --- a/include/maxscale/adminusers.h.in +++ b/include/maxscale/adminusers.h.in @@ -30,6 +30,9 @@ MXS_BEGIN_DECLS /** Default user for the administrative interface */ #define DEFAULT_ADMIN_USER "@DEFAULT_ADMIN_USER@" +static const char INET_DEFAULT_USERNAME[] = "admin"; +static const char INET_DEFAULT_PASSWORD[] = "mariadb"; + /* * MySQL session specific data * diff --git a/include/maxscale/atomic.h b/include/maxscale/atomic.h index 72172c270..639faa216 100644 --- a/include/maxscale/atomic.h +++ b/include/maxscale/atomic.h @@ -60,6 +60,7 @@ uint64_t atomic_add_uint64(uint64_t *variable, int64_t value); int atomic_load_int32(int *variable); int64_t atomic_load_int64(int64_t *variable); uint64_t atomic_load_uint64(uint64_t *variable); +void* atomic_load_ptr(void **variable); /** * Implementation of an atomic store operation for the GCC environment. @@ -73,6 +74,7 @@ uint64_t atomic_load_uint64(uint64_t *variable); void atomic_store_int32(int *variable, int value); void atomic_store_int64(int64_t *variable, int64_t value); void atomic_store_uint64(uint64_t *variable, uint64_t value); +void atomic_store_ptr(void **variable, void *value); /** * @brief Impose a full memory barrier diff --git a/include/maxscale/authenticator.h b/include/maxscale/authenticator.h index 94346c909..4da3bada3 100644 --- a/include/maxscale/authenticator.h +++ b/include/maxscale/authenticator.h @@ -21,6 +21,7 @@ #include #include +#include MXS_BEGIN_DECLS @@ -83,6 +84,20 @@ typedef struct mxs_authenticator int (*loadusers)(struct servlistener *); void (*diagnostic)(struct dcb*, struct servlistener *); + /** + * @brief Return diagnostic information about the authenticator + * + * The authenticator module should return information about its internal + * state when this function is called. + * + * @params Listener object + * + * @return JSON representation of the listener + * + * @see jansson.h + */ + json_t* (*diagnostic_json)(const struct servlistener *listener); + /** This entry point was added to avoid calling authenticator functions * directly when a COM_CHANGE_USER command is executed. */ int (*reauthenticate)(struct dcb *, const char *user, diff --git a/include/maxscale/config.h b/include/maxscale/config.h index 49c15c4e7..e82cb518b 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -23,10 +23,97 @@ #include #include +#include MXS_BEGIN_DECLS +/** Default port where the REST API listens */ +#define DEFAULT_ADMIN_HTTP_PORT 8989 +#define DEFAULT_ADMIN_HOST "::" + #define _RELEASE_STR_LENGTH 256 /**< release len */ +#define MAX_ADMIN_USER_LEN 1024 +#define MAX_ADMIN_PW_LEN 1024 +#define MAX_ADMIN_HOST_LEN 1024 + +/** + * Common configuration parameters names + * + * All of the constants resolve to a lowercase version without the CN_ prefix. + * For example CN_PASSWORD resolves to the static string "password". This means + * that the sizeof(CN_) returns the actual size of that string. + */ +extern const char CN_ADDRESS[]; +extern const char CN_ADMIN_AUTH[]; +extern const char CN_ADMIN_HOST[]; +extern const char CN_ADMIN_PASSWORD[]; +extern const char CN_ADMIN_PORT[]; +extern const char CN_ADMIN_USER[]; +extern const char CN_ADMIN_SSL_KEY[]; +extern const char CN_ADMIN_SSL_CERT[]; +extern const char CN_ADMIN_SSL_CA_CERT[]; +extern const char CN_AUTHENTICATOR[]; +extern const char CN_AUTHENTICATOR_OPTIONS[]; +extern const char CN_AUTH_ALL_SERVERS[]; +extern const char CN_AUTH_CONNECT_TIMEOUT[]; +extern const char CN_AUTH_READ_TIMEOUT[]; +extern const char CN_AUTH_WRITE_TIMEOUT[]; +extern const char CN_AUTO[]; +extern const char CN_CONNECTION_TIMEOUT[]; +extern const char CN_DEFAULT[]; +extern const char CN_ENABLE_ROOT_USER[]; +extern const char CN_FEEDBACK[]; +extern const char CN_FILTERS[]; +extern const char CN_FILTER[]; +extern const char CN_GATEWAY[]; +extern const char CN_LISTENER[]; +extern const char CN_LISTENERS[]; +extern const char CN_LOCALHOST_MATCH_WILDCARD_HOST[]; +extern const char CN_LOG_AUTH_WARNINGS[]; +extern const char CN_LOG_THROTTLING[]; +extern const char CN_MAXSCALE[]; +extern const char CN_MAX_CONNECTIONS[]; +extern const char CN_MAX_RETRY_INTERVAL[]; +extern const char CN_MODULE[]; +extern const char CN_MONITORS[]; +extern const char CN_MONITOR[]; +extern const char CN_MS_TIMESTAMP[]; +extern const char CN_NAME[]; +extern const char CN_NON_BLOCKING_POLLS[]; +extern const char CN_OPTIONS[]; +extern const char CN_PARAMETERS[]; +extern const char CN_PASSWORD[]; +extern const char CN_POLL_SLEEP[]; +extern const char CN_PORT[]; +extern const char CN_PROTOCOL[]; +extern const char CN_QUERY_CLASSIFIER[]; +extern const char CN_QUERY_CLASSIFIER_ARGS[]; +extern const char CN_RELATIONSHIPS[]; +extern const char CN_REQUIRED[]; +extern const char CN_RETRY_ON_FAILURE[]; +extern const char CN_ROUTER[]; +extern const char CN_ROUTER_OPTIONS[]; +extern const char CN_SELF[]; +extern const char CN_SERVERS[]; +extern const char CN_SERVER[]; +extern const char CN_SERVICES[]; +extern const char CN_SERVICE[]; +extern const char CN_SKIP_PERMISSION_CHECKS[]; +extern const char CN_SOCKET[]; +extern const char CN_STATE[]; +extern const char CN_STATUS[]; +extern const char CN_SSL[]; +extern const char CN_SSL_CA_CERT[]; +extern const char CN_SSL_CERT[]; +extern const char CN_SSL_CERT_VERIFY_DEPTH[]; +extern const char CN_SSL_KEY[]; +extern const char CN_SSL_VERSION[]; +extern const char CN_STRIP_DB_ESC[]; +extern const char CN_THREADS[]; +extern const char CN_TYPE[]; +extern const char CN_USER[]; +extern const char CN_VERSION_STRING[]; +extern const char CN_WEIGHTBY[]; /** * The config parameter @@ -74,6 +161,14 @@ typedef struct bool skip_permission_checks; /**< Skip service and monitor permission checks */ char qc_name[PATH_MAX]; /**< The name of the query classifier to load */ char* qc_args; /**< Arguments for the query classifier */ + char admin_user[MAX_ADMIN_USER_LEN]; /**< Admin interface user */ + char admin_password[MAX_ADMIN_PW_LEN]; /**< Admin interface password */ + char admin_host[MAX_ADMIN_HOST_LEN]; /**< Admin interface host */ + uint16_t admin_port; /**< Admin interface port */ + bool admin_auth; /**< Admin interface authentication */ + char admin_ssl_key[PATH_MAX]; /**< Admin SSL key */ + char admin_ssl_cert[PATH_MAX]; /**< Admin SSL cert */ + char admin_ssl_ca_cert[PATH_MAX]; /**< Admin SSL CA cert */ } MXS_CONFIG; /** @@ -301,6 +396,12 @@ void config_disable_feedback_task(void); */ bool config_reload(void); -static const char BACKEND_CONNECT_ATTEMPTS[] = "backend_connect_attempts"; +/** + * @brief List all path parameters as JSON + * + * @param host Hostname of this server + * @return JSON object representing the paths used by MaxScale + */ +json_t* config_paths_to_json(const char* host); MXS_END_DECLS diff --git a/include/maxscale/filter.h b/include/maxscale/filter.h index c1a91b15c..1952b1331 100644 --- a/include/maxscale/filter.h +++ b/include/maxscale/filter.h @@ -23,6 +23,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -178,6 +179,18 @@ typedef struct mxs_filter_object */ void (*diagnostics)(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); + /** + * @brief Called for diagnostic output + * + * @param instance Filter instance + * @param fsession Filter session, NULL if general information about the filter is queried + * + * @return JSON formatted information about the filter + * + * @see jansson.h + */ + json_t* (*diagnostics_json)(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); + /** * @brief Called to obtain the capabilities of the filter * @@ -246,6 +259,25 @@ const char* filter_def_get_module_name(const MXS_FILTER_DEF* filter_def); */ MXS_FILTER* filter_def_get_instance(const MXS_FILTER_DEF* filter_def); +/** + * @brief Convert a filter to JSON + * + * @param filter Filter to convert + * @param host Hostname of this server + * + * @return Filter converted to JSON format + */ +json_t* filter_to_json(const MXS_FILTER_DEF* filter, const char* host); + +/** + * @brief Convert all filters into JSON + * + * @param host Hostname of this server + * + * @return A JSON array containing all filters + */ +json_t* filter_list_to_json(const char* host); + void dprintAllFilters(DCB *); void dprintFilter(DCB *, const MXS_FILTER_DEF *); void dListFilters(DCB *); diff --git a/include/maxscale/filter.hh b/include/maxscale/filter.hh index 1cb9b90c2..d66051b0a 100644 --- a/include/maxscale/filter.hh +++ b/include/maxscale/filter.hh @@ -149,6 +149,11 @@ public: */ void diagnostics(DCB *pDcb); + /** + * Called for obtaining diagnostics about the filter session. + */ + json_t* diagnostics_json() const; + protected: FilterSession(MXS_SESSION* pSession); @@ -290,6 +295,26 @@ public: } } + static json_t* diagnostics_json(const MXS_FILTER* pInstance, const MXS_FILTER_SESSION* pData) + { + json_t* rval = NULL; + + if (pData) + { + const FilterSessionType* pFilterSession = static_cast(pData); + + MXS_EXCEPTION_GUARD(rval = pFilterSession->diagnostics_json()); + } + else + { + const FilterType* pFilter = static_cast(pInstance); + + MXS_EXCEPTION_GUARD(rval = pFilter->diagnostics_json()); + } + + return rval; + } + static uint64_t getCapabilities(MXS_FILTER* pInstance) { uint64_t rv = 0; @@ -324,6 +349,7 @@ MXS_FILTER_OBJECT Filter::s_object = &Filter::routeQuery, &Filter::clientReply, &Filter::diagnostics, + &Filter::diagnostics_json, &Filter::getCapabilities, &Filter::destroyInstance, }; diff --git a/include/maxscale/jansson.hh b/include/maxscale/jansson.hh index b72a6fc66..6bab1e32a 100644 --- a/include/maxscale/jansson.hh +++ b/include/maxscale/jansson.hh @@ -13,6 +13,12 @@ */ #include + +#include +#include + +#include +#include #include #include @@ -41,4 +47,74 @@ struct CloserTraits } }; +/** + * @brief Convenience function for dumping JSON into a string + * + * @param json JSON to dump + * + * @return The JSON in string format + */ +static inline std::string json_dump(const json_t* json, int flags = 0) +{ + std::string rval; + char* js = json_dumps(json, flags); + + if (js) + { + rval = js; + MXS_FREE(js); + } + + return rval; +} + +static inline std::string json_dump(const Closer& json, int flags = 0) +{ + return json_dump(json.get(), flags); +} + +/** + * @brief Convert JSON to string + * + * @param JSON to convert + * + * @return The JSON value converted to a string + */ +static inline std::string json_to_string(json_t* json) +{ + std::stringstream ss; + + switch (json_typeof(json)) + { + case JSON_STRING: + ss << json_string_value(json); + break; + + case JSON_INTEGER: + ss << json_integer_value(json); + break; + + case JSON_REAL: + ss << json_real_value(json); + break; + + case JSON_TRUE: + ss << "true"; + break; + + case JSON_FALSE: + ss << "false"; + break; + + case JSON_NULL: + break; + + default: + ss_dassert(false); + break; + + } + + return ss.str(); +} } diff --git a/include/maxscale/listener.h b/include/maxscale/listener.h index 9c2c67c71..d52cd8f11 100644 --- a/include/maxscale/listener.h +++ b/include/maxscale/listener.h @@ -20,6 +20,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -61,6 +62,15 @@ typedef struct servlistener */ bool listener_serialize(const SERV_LISTENER *listener); +/** + * @brief Convert listener to JSON + * + * @param listener Listener to convert + * + * @return Converted listener + */ +json_t* listener_to_json(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); diff --git a/include/maxscale/modinfo.h b/include/maxscale/modinfo.h index aea093fcf..de0fb6c6a 100644 --- a/include/maxscale/modinfo.h +++ b/include/maxscale/modinfo.h @@ -20,6 +20,8 @@ #include +#include + MXS_BEGIN_DECLS /** @@ -217,4 +219,76 @@ typedef struct mxs_module /** Name of the symbol that MaxScale will load */ #define MXS_MODULE_SYMBOL_NAME "mxs_get_module_object" +static inline const char* mxs_module_param_type_to_string(enum mxs_module_param_type type) +{ + switch (type) + { + case MXS_MODULE_PARAM_COUNT: + return "count"; + case MXS_MODULE_PARAM_INT: + return "int"; + case MXS_MODULE_PARAM_SIZE: + return "size"; + case MXS_MODULE_PARAM_BOOL: + return "bool"; + case MXS_MODULE_PARAM_STRING: + return "string"; + case MXS_MODULE_PARAM_ENUM: + return "enum"; + case MXS_MODULE_PARAM_PATH: + return "path"; + case MXS_MODULE_PARAM_SERVICE: + return "service"; + case MXS_MODULE_PARAM_SERVER: + return "server"; + case MXS_MODULE_PARAM_SERVERLIST: + return "serverlist"; + default: + ss_dassert(!true); + return "unknown"; + } +} + +static inline const char* mxs_module_api_to_string(MXS_MODULE_API type) +{ + switch (type) + { + case MXS_MODULE_API_PROTOCOL: + return "protocol"; + case MXS_MODULE_API_ROUTER: + return "router"; + case MXS_MODULE_API_MONITOR: + return "monitor"; + case MXS_MODULE_API_FILTER: + return "filter"; + case MXS_MODULE_API_AUTHENTICATOR: + return "authenticator"; + case MXS_MODULE_API_QUERY_CLASSIFIER: + return "query_classifier"; + default: + ss_dassert(!true); + return "unknown"; + } +} + +static inline const char* mxs_module_status_to_string(MXS_MODULE_STATUS type) +{ + switch (type) + { + case MXS_MODULE_IN_DEVELOPMENT: + return "In development"; + case MXS_MODULE_ALPHA_RELEASE: + return "Alpha"; + case MXS_MODULE_BETA_RELEASE: + return "Beta"; + case MXS_MODULE_GA: + return "GA"; + case MXS_MODULE_EXPERIMENTAL: + return "Experimental"; + default: + ss_dassert(!true); + return "Unknown"; + } +} + MXS_END_DECLS diff --git a/include/maxscale/monitor.h b/include/maxscale/monitor.h index 02e5e2457..44e100fb4 100644 --- a/include/maxscale/monitor.h +++ b/include/maxscale/monitor.h @@ -23,6 +23,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -54,9 +55,39 @@ typedef struct mxs_monitor MXS_MONITOR; */ typedef struct mxs_monitor_object { + /** + * @brief Start the monitor + * + * This entry point is called when the monitor is started. If the monitor + * requires polling of the servers, it should create a separate monitoring + * thread. + * + * @param monitor The monitor object + * @param params Parameters for this monitor + * + * @return Pointer to the monitor specific data, stored in @c monitor->handle + */ void *(*startMonitor)(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER *params); + + /** + * @brief Stop the monitor + * + * This entry point is called when the monitor is stopped. If the monitor + * uses a polling thread, the thread should be stopped. + * + * @param monitor The monitor object + */ void (*stopMonitor)(MXS_MONITOR *monitor); void (*diagnostics)(DCB *, const MXS_MONITOR *); + + /** + * @brief Return diagnostic information about the monitor + * + * @return A JSON object representing the state of the monitor + * + * @see jansson.h + */ + json_t* (*diagnostics_json)(const MXS_MONITOR *monitor); } MXS_MONITOR_OBJECT; /** @@ -207,6 +238,17 @@ static const char MXS_MONITOR_EVENT_DEFAULT_VALUE[] = "master_down,master_up,sla "ndb_down,ndb_up,lost_master,lost_slave,lost_synced,lost_donor,lost_ndb," "new_master,new_slave,new_synced,new_donor,new_ndb"; +/** + * Monitor configuration parameters names + */ +extern const char CN_BACKEND_CONNECT_ATTEMPTS[]; +extern const char CN_BACKEND_READ_TIMEOUT[]; +extern const char CN_BACKEND_WRITE_TIMEOUT[]; +extern const char CN_BACKEND_CONNECT_TIMEOUT[]; +extern const char CN_MONITOR_INTERVAL[]; +extern const char CN_SCRIPT[]; +extern const char CN_EVENTS[]; + bool check_monitor_permissions(MXS_MONITOR* monitor, const char* query); void monitor_clear_pending_status(MXS_MONITOR_SERVERS *ptr, int bit); @@ -244,4 +286,33 @@ void mon_process_state_changes(MXS_MONITOR *monitor, const char *script, uint64_ */ void mon_hangup_failed_servers(MXS_MONITOR *monitor); +/** + * @brief Convert monitor to JSON + * + * @param monitor Monitor to convert + * @param host Hostname of this server + * + * @return JSON representation of the monitor + */ +json_t* monitor_to_json(const MXS_MONITOR* monitor, const char* host); + +/** + * @brief Convert all monitors to JSON + * + * @param host Hostname of this server + * + * @return JSON array containing all monitors + */ +json_t* monitor_list_to_json(const char* host); + +/** + * @brief Get links to monitors that relate to a server + * + * @param server Server to inspect + * @param host Hostname of this server + * + * @return Array of monitor links + */ +json_t* monitor_relations_to_server(const SERVER* server, const char* host); + MXS_END_DECLS diff --git a/include/maxscale/router.h b/include/maxscale/router.h index 0b29ad02f..cc6aa08cf 100644 --- a/include/maxscale/router.h +++ b/include/maxscale/router.h @@ -24,6 +24,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -153,6 +154,17 @@ typedef struct mxs_router_object */ void (*diagnostics)(MXS_ROUTER *instance, DCB *dcb); + /** + * @brief Called for diagnostic output + * + * @param instance Router instance + * + * @return Diagnostic information in JSON format + * + * @see jansson.h + */ + json_t* (*diagnostics_json)(const MXS_ROUTER *instance); + /** * @brief Called for each reply packet * diff --git a/include/maxscale/router.hh b/include/maxscale/router.hh index f26533db5..b3800ba50 100644 --- a/include/maxscale/router.hh +++ b/include/maxscale/router.hh @@ -180,6 +180,17 @@ public: MXS_EXCEPTION_GUARD(pRouter->diagnostics(pDcb)); } + static json_t* diagnostics_json(const MXS_ROUTER* pInstance) + { + const RouterType* pRouter = static_cast(pInstance); + + json_t* rval = NULL; + + MXS_EXCEPTION_GUARD(rval = pRouter->diagnostics_json()); + + return rval; + } + static void clientReply(MXS_ROUTER*, MXS_ROUTER_SESSION* pData, GWBUF* pPacket, DCB* pBackend) { RouterSessionType* pRouter_session = static_cast(pData); @@ -238,6 +249,7 @@ MXS_ROUTER_OBJECT Router::s_object = &Router::freeSession, &Router::routeQuery, &Router::diagnostics, + &Router::diagnostics_json, &Router::clientReply, &Router::handleError, &Router::getCapabilities, diff --git a/include/maxscale/server.h b/include/maxscale/server.h index 3c062953e..a6f5fbb6b 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -21,14 +21,24 @@ #include #include #include +#include MXS_BEGIN_DECLS #define MAX_SERVER_NAME_LEN 1024 -#define MAX_SERVER_MONUSER_LEN 512 -#define MAX_SERVER_MONPW_LEN 512 +#define MAX_SERVER_MONUSER_LEN 1024 +#define MAX_SERVER_MONPW_LEN 1024 #define MAX_NUM_SLAVES 128 /**< Maximum number of slaves under a single server*/ +/** + * Server configuration parameters names + */ +extern const char CN_MONITORPW[]; +extern const char CN_MONITORUSER[]; +extern const char CN_PERSISTMAXTIME[]; +extern const char CN_PERSISTPOOLMAX[]; +extern const char CN_USE_PROXY_PROTOCOL[]; + /** * The server parameters used for weighting routing decissions */ @@ -191,8 +201,6 @@ enum (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \ (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) -extern const char USE_PROXY_PROTOCOL[]; - /** * @brief Allocate a new server * @@ -265,6 +273,25 @@ bool server_remove_parameter(SERVER *server, const char *name); */ bool server_is_mxs_service(const SERVER *server); +/** + * @brief Convert a server to JSON format + * + * @param server Server to convert + * @param host Hostname of this server + * + * @return JSON representation of server or NULL if an error occurred + */ +json_t* server_to_json(const SERVER* server, const char* host); + +/** + * @brief Convert all servers into JSON format + * + * @param host Hostname of this server + * + * @return JSON array of servers or NULL if an error occurred + */ +json_t* server_list_to_json(const char* host); + extern int server_free(SERVER *server); extern SERVER *server_find_by_unique_name(const char *name); extern int server_find_by_unique_names(char **server_names, int size, SERVER*** output); @@ -275,7 +302,7 @@ extern void server_set_status_nolock(SERVER *server, unsigned bit); extern void server_clear_status_nolock(SERVER *server, unsigned bit); extern void server_transfer_status(SERVER *dest_server, const SERVER *source_server); extern void server_add_mon_user(SERVER *server, const char *user, const char *passwd); -extern const char *server_get_parameter(const SERVER *server, char *name); +extern const char *server_get_parameter(const SERVER *server, const char *name); extern void server_update_credentials(SERVER *server, const char *user, const char *passwd); extern DCB *server_get_persistent(SERVER *server, const char *user, const char *protocol, int id); extern void server_update_address(SERVER *server, const char *address); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index d425acda0..5a01b8f6a 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -19,7 +19,13 @@ */ #include + #include +#include +#include +#include +#include + #include #include #include @@ -30,10 +36,7 @@ #include #include #include -#include -#include -#include -#include +#include MXS_BEGIN_DECLS @@ -42,6 +45,11 @@ struct mxs_router; struct mxs_router_object; struct users; +#define MAX_SERVICE_USER_LEN 1024 +#define MAX_SERVICE_PASSWORD_LEN 1024 +#define MAX_SERVICE_WEIGHTBY_LEN 1024 +#define MAX_SERVICE_VERSION_LEN 1024 + /** * The service statistics structure */ @@ -61,8 +69,8 @@ typedef struct */ typedef struct { - char *name; /**< The user name to use to extract information */ - char *authdata; /**< The authentication data requied */ + char name[MAX_SERVICE_USER_LEN]; /**< The user name to use to extract information */ + char authdata[MAX_SERVICE_PASSWORD_LEN]; /**< The authentication data requied */ } SERVICE_USER; /** @@ -127,7 +135,7 @@ typedef struct service char **routerOptions; /**< Router specific option strings */ struct mxs_router_object *router; /**< The router we are using */ struct mxs_router *router_instance;/**< The router instance for this service */ - char *version_string; /**< version string for this service listeners */ + char version_string[MAX_SERVICE_VERSION_LEN]; /**< 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 */ @@ -146,7 +154,7 @@ typedef struct service MXS_FILTER_DEF **filters; /**< Ordered list of filters */ int n_filters; /**< Number of filters */ int64_t conn_idle_timeout; /**< Session timeout in seconds */ - char *weightby; /**< Service weighting parameter name */ + char weightby[MAX_SERVICE_WEIGHTBY_LEN]; /**< 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 */ @@ -251,17 +259,18 @@ bool serviceHasListener(SERVICE *service, const char *protocol, bool service_port_is_used(unsigned short port); int serviceGetUser(SERVICE *service, char **user, char **auth); -int serviceSetUser(SERVICE *service, char *user, char *auth); +int serviceSetUser(SERVICE *service, const char *user, const 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); +void serviceSetRetryOnFailure(SERVICE *service, const char* value); +void serviceWeightBy(SERVICE *service, const char *weightby); +const char* serviceGetWeightingParameter(SERVICE *service); int serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action); int serviceStripDbEsc(SERVICE* service, int action); int serviceAuthAllServers(SERVICE *service, int action); +void serviceSetVersionString(SERVICE *service, const char* value); int service_refresh_users(SERVICE *service); /** @@ -276,6 +285,45 @@ int service_refresh_users(SERVICE *service); */ void service_print_users(DCB *, const SERVICE *); +/** + * @brief Convert a service to JSON + * + * @param service Service to convert + * @param host Hostname of this server + * + * @return JSON representation of the service + */ +json_t* service_to_json(const SERVICE* service, const char* host); + +/** + * @brief Convert all services to JSON + * + * @param host Hostname of this server + * + * @return A JSON array with all services + */ +json_t* service_list_to_json(const char* host); + +/** + * @brief Get links to services that relate to a server + * + * @param server Server to inspect + * @param host Hostname of this server + * + * @return Array of service links + */ +json_t* service_relations_to_server(const SERVER* server, const char* host); + +/** + * @brief Get links to services that relate to a filter + * + * @param filter Filter to inspect + * @param host Hostname of this server + * + * @return Array of service links + */ +json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* host); + void dprintAllServices(DCB *dcb); void dprintService(DCB *dcb, SERVICE *service); void dListServices(DCB *dcb); diff --git a/include/maxscale/session.h b/include/maxscale/session.h index dc93b8342..e8746d8fe 100644 --- a/include/maxscale/session.h +++ b/include/maxscale/session.h @@ -25,6 +25,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -412,4 +413,23 @@ bool session_take_stmt(MXS_SESSION *session, GWBUF **buffer, const struct server */ void session_clear_stmt(MXS_SESSION *session); +/** + * @brief Convert a session to JSON + * + * @param session Session to convert + * @param host Hostname of this server + * + * @return New JSON object or NULL on error + */ +json_t* session_to_json(const MXS_SESSION *session, const char* host); + +/** + * @brief Convert all sessions to JSON + * + * @param host Hostname of this server + * + * @return A JSON array with all sessions + */ +json_t* session_list_to_json(const char* host); + MXS_END_DECLS diff --git a/include/maxscale/users.h b/include/maxscale/users.h index cf674c8e3..ab4accc59 100644 --- a/include/maxscale/users.h +++ b/include/maxscale/users.h @@ -120,6 +120,13 @@ int users_default_loadusers(SERV_LISTENER *port); */ void users_default_diagnostic(DCB *dcb, SERV_LISTENER *port); +/** + * @brief Default authenticator diagnostic function + * + * @param port Port whose data is to be printed + */ +json_t* users_default_diagnostic_json(const SERV_LISTENER *port); + /** * Print details of the users storage mechanism * diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index 6f34e47aa..2c51e68a2 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -87,6 +87,7 @@ int open_unix_socket(enum mxs_socket_type type, struct sockaddr_un *addr, const char *path); int setnonblocking(int fd); +int setblocking(int fd); char *gw_strend(register const char *s); static char gw_randomchar(); int gw_generate_random_str(char *output, int len); @@ -98,7 +99,9 @@ void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_le int gw_getsockerrno(int fd); char *create_hex_sha1_sha1_passwd(char *passwd); +/** String formatting functions */ char* trim(char *str); +void replace_whitespace(char* str); char* squeeze_whitespace(char* str); bool strip_escape_chars(char*); diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index f2436b79f..736d67e71 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(maxscale-common SHARED + admin.cc adminusers.cc alloc.cc atomic.cc @@ -12,6 +13,8 @@ add_library(maxscale-common SHARED hashtable.cc hint.cc housekeeper.cc + httprequest.cc + httpresponse.cc listener.cc load_utils.cc log_manager.cc @@ -30,6 +33,7 @@ add_library(maxscale-common SHARED queuemanager.cc random_jkiss.cc resultset.cc + resource.cc router.cc secrets.cc semaphore.cc @@ -53,7 +57,24 @@ elseif(WITH_TCMALLOC) target_link_libraries(maxscale-common ${TCMALLOC_LIBRARIES}) endif() -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++) +target_link_libraries(maxscale-common + ${MARIADB_CONNECTOR_LIBRARIES} + ${LZMA_LINK_FLAGS} + ${PCRE2_LIBRARIES} + ${CURL_LIBRARIES} + ${JANSSON_LIBRARIES} + ssl + pthread + crypt + dl + crypto + inih + z + rt + m + stdc++ + microhttpd +) add_dependencies(maxscale-common pcre2 connector-c) set_target_properties(maxscale-common PROPERTIES VERSION "1.0.0") diff --git a/server/core/admin.cc b/server/core/admin.cc new file mode 100644 index 000000000..c54072b85 --- /dev/null +++ b/server/core/admin.cc @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 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 The embedded HTTP protocol administrative interface + */ +#include "maxscale/admin.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "maxscale/resource.hh" +#include "maxscale/http.hh" + +using std::string; +using std::ifstream; + +static struct MHD_Daemon* http_daemon = NULL; + +/** In-memory certificates in PEM format */ +static char* admin_ssl_key = NULL; +static char* admin_ssl_cert = NULL; +static char* admin_ssl_ca_cert = NULL; + +static bool using_ssl = false; + +int kv_iter(void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) +{ + size_t* rval = (size_t*)cls; + + if (strcmp(key, "Content-Length") == 0) + { + *rval = atoi(value); + return MHD_NO; + } + + return MHD_YES; +} + +static inline size_t request_data_length(MHD_Connection *connection) +{ + size_t rval = 0; + MHD_get_connection_values(connection, MHD_HEADER_KIND, kv_iter, &rval); + return rval; +} + +static bool modifies_data(MHD_Connection *connection, string method) +{ + return (method == MHD_HTTP_METHOD_POST || method == MHD_HTTP_METHOD_PUT || + method == MHD_HTTP_METHOD_DELETE) && + request_data_length(connection); +} + +int Client::process(string url, string method, const char* upload_data, size_t *upload_size) +{ + json_t* json = NULL; + + if (*upload_size) + { + m_data.append(upload_data, *upload_size); + *upload_size = 0; + return MHD_YES; + } + + json_error_t err = {}; + + if (m_data.length() && + (json = json_loadb(m_data.c_str(), m_data.size(), 0, &err)) == NULL) + { + MHD_Response *response = + MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT); + MHD_queue_response(m_connection, MHD_HTTP_BAD_REQUEST, response); + MHD_destroy_response(response); + return MHD_YES; + } + + HttpRequest request(m_connection, url, method, json); + HttpResponse reply = resource_handle_request(request); + + string data; + + json_t* js = reply.get_response(); + + if (js) + { + int flags = request.get_option("pretty") == "true" ? JSON_INDENT(4) : 0; + data = mxs::json_dump(js, flags); + } + + MHD_Response *response = + MHD_create_response_from_buffer(data.size(), (void*)data.c_str(), + MHD_RESPMEM_MUST_COPY); + + const Headers& headers = reply.get_headers(); + + for (Headers::const_iterator it = headers.begin(); it != headers.end(); it++) + { + MHD_add_response_header(response, it->first.c_str(), it->second.c_str()); + } + + int rval = MHD_queue_response(m_connection, reply.get_code(), response); + MHD_destroy_response(response); + + return rval; +} + +void close_client(void *cls, + MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + Client* client = static_cast(*con_cls); + delete client; +} + +bool do_auth(MHD_Connection *connection) +{ + const char *admin_user = config_get_global_options()->admin_user; + const char *admin_pw = config_get_global_options()->admin_password; + bool admin_auth = config_get_global_options()->admin_auth; + + char* pw = NULL; + char* user = MHD_basic_auth_get_username_password(connection, &pw); + bool rval = true; + + if (admin_auth && (!user || !pw || strcmp(user, admin_user) || strcmp(pw, admin_pw))) + { + rval = false; + static char error_resp[] = "Access denied\r\n"; + MHD_Response *resp = + MHD_create_response_from_buffer(sizeof(error_resp) - 1, error_resp, + MHD_RESPMEM_PERSISTENT); + + MHD_queue_basic_auth_fail_response(connection, "maxscale", resp); + MHD_destroy_response(resp); + } + + return rval; +} + +int handle_client(void *cls, + MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) + +{ + if (!do_auth(connection)) + { + return MHD_YES; + } + + if (*con_cls == NULL) + { + if ((*con_cls = new (std::nothrow) Client(connection)) == NULL) + { + return MHD_NO; + } + else if (modifies_data(connection, method)) + { + // The first call doesn't have any data + return MHD_YES; + } + } + + Client* client = static_cast(*con_cls); + return client->process(url, method, upload_data, upload_data_size); +} + +static bool host_to_sockaddr(const char* host, uint16_t port, struct sockaddr_storage* addr) +{ + struct addrinfo *ai = NULL, hint = {}; + int rc; + hint.ai_socktype = SOCK_STREAM; + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_ALL; + + if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) + { + MXS_ERROR("Failed to obtain address for host %s: %s", host, gai_strerror(rc)); + return false; + } + + /* Take the first one */ + if (ai) + { + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + + if (addr->ss_family == AF_INET) + { + struct sockaddr_in *ip = (struct sockaddr_in*)addr; + ip->sin_port = htons(port); + } + else if (addr->ss_family == AF_INET6) + { + struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr; + ip->sin6_port = htons(port); + } + } + + freeaddrinfo(ai); + return true; +} + +static char* load_cert(const char* file) +{ + char* rval = NULL; + ifstream infile(file); + struct stat st; + + if (stat(file, &st) == 0 && + (rval = new (std::nothrow) char[st.st_size + 1])) + { + infile.read(rval, st.st_size); + rval[st.st_size] = '\0'; + + if (!infile.good()) + { + MXS_ERROR("Failed to load certificate file: %s", file); + delete rval; + rval = NULL; + } + } + + return rval; +} + +static bool load_ssl_certificates() +{ + bool rval = false; + const char* key = config_get_global_options()->admin_ssl_key; + const char* cert = config_get_global_options()->admin_ssl_cert; + const char* ca = config_get_global_options()->admin_ssl_ca_cert; + + if (*key && *cert && *ca) + { + if ((admin_ssl_key = load_cert(key)) && + (admin_ssl_cert = load_cert(cert)) && + (admin_ssl_ca_cert = load_cert(ca))) + { + rval = true; + } + else + { + delete admin_ssl_key; + delete admin_ssl_cert; + delete admin_ssl_ca_cert; + admin_ssl_key = NULL; + admin_ssl_cert = NULL; + admin_ssl_ca_cert = NULL; + } + } + + return rval; +} + +bool mxs_admin_init() +{ + struct sockaddr_storage addr; + + if (host_to_sockaddr(config_get_global_options()->admin_host, + config_get_global_options()->admin_port, + &addr)) + { + int options = MHD_USE_EPOLL_INTERNALLY_LINUX_ONLY; + + if (addr.ss_family == AF_INET6) + { + options |= MHD_USE_DUAL_STACK; + } + + if (load_ssl_certificates()) + { + using_ssl = true; + options |= MHD_USE_SSL; + } + + // The port argument is ignored and the port in the struct sockaddr is used instead + http_daemon = MHD_start_daemon(options, 0, NULL, NULL, handle_client, NULL, + MHD_OPTION_NOTIFY_COMPLETED, close_client, NULL, + MHD_OPTION_SOCK_ADDR, &addr, + !using_ssl ? MHD_OPTION_END : + MHD_OPTION_HTTPS_MEM_KEY, admin_ssl_key, + MHD_OPTION_HTTPS_MEM_CERT, admin_ssl_cert, + MHD_OPTION_HTTPS_MEM_TRUST, admin_ssl_cert, + MHD_OPTION_END); + } + + return http_daemon != NULL; +} + +void mxs_admin_shutdown() +{ + MHD_stop_daemon(http_daemon); +} + +bool mxs_admin_https_enabled() +{ + return using_ssl; +} diff --git a/server/core/adminusers.cc b/server/core/adminusers.cc index fc2a8f4e1..fbc7c49b1 100644 --- a/server/core/adminusers.cc +++ b/server/core/adminusers.cc @@ -62,9 +62,6 @@ static const int LINELEN = 80; static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users"; static const char INET_USERS_FILE_NAME[] = "passwd"; -static const char INET_DEFAULT_USERNAME[] = "admin"; -static const char INET_DEFAULT_PASSWORD[] = "mariadb"; - /** * Admin Users initialisation */ diff --git a/server/core/atomic.cc b/server/core/atomic.cc index d2a6cac40..979756555 100644 --- a/server/core/atomic.cc +++ b/server/core/atomic.cc @@ -80,6 +80,15 @@ uint64_t atomic_load_uint64(uint64_t *variable) #endif } +void* atomic_load_ptr(void **variable) +{ +#ifdef MXS_USE_ATOMIC_BUILTINS + return __atomic_load_n(variable, __ATOMIC_SEQ_CST); +#else + return __sync_fetch_and_or(variable, 0); +#endif +} + void atomic_store_int32(int *variable, int value) { #ifdef MXS_USE_ATOMIC_BUILTINS @@ -106,3 +115,12 @@ void atomic_store_uint64(uint64_t *variable, uint64_t value) __sync_lock_test_and_set(variable, value); #endif } + +void atomic_store_ptr(void **variable, void *value) +{ +#ifdef MXS_USE_ATOMIC_BUILTINS + __atomic_store_n(variable, value, __ATOMIC_SEQ_CST); +#else + __sync_lock_test_and_set(variable, value); +#endif +} diff --git a/server/core/config.cc b/server/core/config.cc index 1bed3e9c6..9326f1f72 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -12,38 +12,9 @@ */ /** - * @file config.c - Read the gateway.cnf configuration file - * - * @verbatim - * Revision History - * - * Date Who Description - * 21/06/13 Mark Riddoch Initial implementation - * 08/07/13 Mark Riddoch Addition on monitor module support - * 23/07/13 Mark Riddoch Addition on default monitor password - * 06/02/14 Massimiliano Pinto Added support for enable/disable root user in services - * 14/02/14 Massimiliano Pinto Added enable_root_user in the service_params list - * 11/03/14 Massimiliano Pinto Added Unix socket support - * 11/05/14 Massimiliano Pinto Added version_string support to service - * 19/05/14 Mark Riddoch Added unique names from section headers - * 29/05/14 Mark Riddoch Addition of filter definition - * 23/05/14 Massimiliano Pinto Added automatic set of maxscale-id: first listening ipv4_raw + port + pid - * 28/05/14 Massimiliano Pinto Added detect_replication_lag parameter - * 28/08/14 Massimiliano Pinto Added detect_stale_master parameter - * 09/09/14 Massimiliano Pinto Added localhost_match_wildcard_host parameter - * 12/09/14 Mark Riddoch Addition of checks on servers list and - * internal router suppression of messages - * 30/10/14 Massimiliano Pinto Added disable_master_failback parameter - * 07/11/14 Massimiliano Pinto Addition of monitor timeouts for connect/read/write - * 20/02/15 Markus Mäkelä Added connection_timeout parameter for services - * 05/03/15 Massimiliano Pinto Added notification_feedback support - * 20/04/15 Guillaume Lefranc Added available_when_donor parameter - * 22/04/15 Martin Brampton Added disable_master_role_setting parameter - * 26/01/16 Martin Brampton Transfer SSL processing to listener - * 31/05/16 Martin Brampton Implement connection throttling, initially no queue - * - * @endverbatim + * @file config.c Configuration file processing */ + #include #include @@ -56,7 +27,10 @@ #include #include #include +#include +#include +#include #include #include #include @@ -74,6 +48,81 @@ #include "maxscale/modules.h" #include "maxscale/router.h" +using std::set; +using std::string; + +const char CN_ADDRESS[] = "address"; +const char CN_ADMIN_AUTH[] = "admin_auth"; +const char CN_ADMIN_HOST[] = "admin_host"; +const char CN_ADMIN_PASSWORD[] = "admin_password"; +const char CN_ADMIN_PORT[] = "admin_port"; +const char CN_ADMIN_USER[] = "admin_user"; +const char CN_ADMIN_SSL_KEY[] = "admin_ssl_key"; +const char CN_ADMIN_SSL_CERT[] = "admin_ssl_cert"; +const char CN_ADMIN_SSL_CA_CERT[] = "admin_ssl_ca_cert"; +const char CN_AUTHENTICATOR[] = "authenticator"; +const char CN_AUTHENTICATOR_OPTIONS[] = "authenticator_options"; +const char CN_AUTH_ALL_SERVERS[] = "auth_all_servers"; +const char CN_AUTH_CONNECT_TIMEOUT[] = "auth_connect_timeout"; +const char CN_AUTH_READ_TIMEOUT[] = "auth_read_timeout"; +const char CN_AUTH_WRITE_TIMEOUT[] = "auth_write_timeout"; +const char CN_AUTO[] = "auto"; +const char CN_CONNECTION_TIMEOUT[] = "connection_timeout"; +const char CN_DEFAULT[] = "default"; +const char CN_ENABLE_ROOT_USER[] = "enable_root_user"; +const char CN_FEEDBACK[] = "feedback"; +const char CN_FILTERS[] = "filters"; +const char CN_FILTER[] = "filter"; +const char CN_GATEWAY[] = "gateway"; +const char CN_LISTENER[] = "listener"; +const char CN_LISTENERS[] = "listeners"; +const char CN_LOCALHOST_MATCH_WILDCARD_HOST[] = "localhost_match_wildcard_host"; +const char CN_LOG_AUTH_WARNINGS[] = "log_auth_warnings"; +const char CN_LOG_THROTTLING[] = "log_throttling"; +const char CN_MAXSCALE[] = "maxscale"; +const char CN_MAX_CONNECTIONS[] = "max_connections"; +const char CN_MAX_RETRY_INTERVAL[] = "max_retry_interval"; +const char CN_MODULE[] = "module"; +const char CN_MONITORS[] = "monitors"; +const char CN_MONITOR[] = "monitor"; +const char CN_MS_TIMESTAMP[] = "ms_timestamp"; +const char CN_NAME[] = "name"; +const char CN_NON_BLOCKING_POLLS[] = "non_blocking_polls"; +const char CN_OPTIONS[] = "options"; +const char CN_PARAMETERS[] = "parameters"; +const char CN_PASSWORD[] = "password"; +const char CN_POLL_SLEEP[] = "poll_sleep"; +const char CN_PORT[] = "port"; +const char CN_PROTOCOL[] = "protocol"; +const char CN_QUERY_CLASSIFIER[] = "query_classifier"; +const char CN_QUERY_CLASSIFIER_ARGS[] = "query_classifier_args"; +const char CN_RELATIONSHIPS[] = "relationships"; +const char CN_REQUIRED[] = "required"; +const char CN_RETRY_ON_FAILURE[] = "retry_on_failure"; +const char CN_ROUTER[] = "router"; +const char CN_ROUTER_OPTIONS[] = "router_options"; +const char CN_SELF[] = "self"; +const char CN_SERVERS[] = "servers"; +const char CN_SERVER[] = "server"; +const char CN_SERVICES[] = "services"; +const char CN_SERVICE[] = "service"; +const char CN_SKIP_PERMISSION_CHECKS[] = "skip_permission_checks"; +const char CN_SOCKET[] = "socket"; +const char CN_STATE[] = "state"; +const char CN_STATUS[] = "status"; +const char CN_SSL[] = "ssl"; +const char CN_SSL_CA_CERT[] = "ssl_ca_cert"; +const char CN_SSL_CERT[] = "ssl_cert"; +const char CN_SSL_CERT_VERIFY_DEPTH[] = "ssl_cert_verify_depth"; +const char CN_SSL_KEY[] = "ssl_key"; +const char CN_SSL_VERSION[] = "ssl_version"; +const char CN_STRIP_DB_ESC[] = "strip_db_esc"; +const char CN_THREADS[] = "threads"; +const char CN_TYPE[] = "type"; +const char CN_USER[] = "user"; +const char CN_VERSION_STRING[] = "version_string"; +const char CN_WEIGHTBY[] = "weightby"; + typedef struct duplicate_context { HASHTABLE *hash; @@ -106,6 +155,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* int create_new_listener(CONFIG_CONTEXT *obj); int create_new_filter(CONFIG_CONTEXT *obj); int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj); +void config_fix_param(const MXS_MODULE_PARAM *params, MXS_CONFIG_PARAMETER *p); static const char *config_file = NULL; static MXS_CONFIG gateway; @@ -113,96 +163,96 @@ static FEEDBACK_CONF feedback; char *version_string = NULL; static bool is_persisted_config = false; /**< True if a persisted configuration file is being parsed */ -static const char *service_params[] = +const char *config_service_params[] = { - "type", - "router", - "router_options", - "servers", - "monitor", - "user", + CN_TYPE, + CN_ROUTER, + CN_ROUTER_OPTIONS, + CN_SERVERS, + CN_MONITOR, + CN_USER, "passwd", // DEPRECATE: See config_get_password. - "password", - "enable_root_user", - "max_retry_interval", - "max_connections", - "max_queued_connections", - "queued_connection_timeout", - "connection_timeout", - "auth_all_servers", - "strip_db_esc", - "localhost_match_wildcard_host", - "version_string", - "filters", - "weightby", - "log_auth_warnings", - "retry_on_failure", + CN_PASSWORD, + CN_ENABLE_ROOT_USER, + CN_MAX_RETRY_INTERVAL, + CN_MAX_CONNECTIONS, + "max_queued_connections", //TODO: Fix this + "queued_connection_timeout", // TODO: Fix this + CN_CONNECTION_TIMEOUT, + CN_AUTH_ALL_SERVERS, + CN_STRIP_DB_ESC, + CN_LOCALHOST_MATCH_WILDCARD_HOST, + CN_VERSION_STRING, + CN_FILTERS, + CN_WEIGHTBY, + CN_LOG_AUTH_WARNINGS, + CN_RETRY_ON_FAILURE, NULL }; -static const char *listener_params[] = +const char *config_listener_params[] = { - "authenticator_options", - "type", - "service", - "protocol", - "port", - "address", - "socket", - "authenticator", - "ssl_cert", - "ssl_ca_cert", - "ssl", - "ssl_key", - "ssl_version", - "ssl_cert_verify_depth", + CN_AUTHENTICATOR_OPTIONS, + CN_TYPE, + CN_SERVICE, + CN_PROTOCOL, + CN_PORT, + CN_ADDRESS, + CN_SOCKET, + CN_AUTHENTICATOR, + CN_SSL_CERT, + CN_SSL_CA_CERT, + CN_SSL, + CN_SSL_KEY, + CN_SSL_VERSION, + CN_SSL_CERT_VERIFY_DEPTH, NULL }; -static const char *monitor_params[] = +const char *config_monitor_params[] = { - "type", - "module", - "servers", - "user", + CN_TYPE, + CN_MODULE, + CN_SERVERS, + CN_USER, "passwd", // DEPRECATE: See config_get_password. - "password", - "script", - "events", - "monitor_interval", - "backend_connect_timeout", - "backend_read_timeout", - "backend_write_timeout", - BACKEND_CONNECT_ATTEMPTS, + CN_PASSWORD, + CN_SCRIPT, + CN_EVENTS, + CN_MONITOR_INTERVAL, + CN_BACKEND_CONNECT_TIMEOUT, + CN_BACKEND_READ_TIMEOUT, + CN_BACKEND_WRITE_TIMEOUT, + CN_BACKEND_CONNECT_ATTEMPTS, NULL }; -static const char *filter_params[] = +const char *config_filter_params[] = { - "type", - "module", + CN_TYPE, + CN_MODULE, NULL }; -static const char *server_params[] = +const char *server_params[] = { - "type", - "protocol", - "port", - "address", - "authenticator", - "authenticator_options", - "monitoruser", - "monitorpw", - "persistpoolmax", - "persistmaxtime", - "ssl_cert", - "ssl_ca_cert", - "ssl", - "ssl_key", - "ssl_version", - "ssl_cert_verify_depth", - USE_PROXY_PROTOCOL, + CN_TYPE, + CN_PROTOCOL, + CN_PORT, + CN_ADDRESS, + CN_AUTHENTICATOR, + CN_AUTHENTICATOR_OPTIONS, + CN_MONITORUSER, + CN_MONITORPW, + CN_PERSISTPOOLMAX, + CN_PERSISTMAXTIME, + CN_SSL_CERT, + CN_SSL_CA_CERT, + CN_SSL, + CN_SSL_KEY, + CN_SSL_VERSION, + CN_SSL_CERT_VERIFY_DEPTH, + CN_USE_PROXY_PROTOCOL, NULL }; @@ -340,6 +390,39 @@ CONFIG_CONTEXT* config_context_create(const char *section) return ctx; } +/** A set that holds all the section names that contain whitespace */ +static std::set warned_whitespace; + +/** + * @brief Fix section names + * + * Check that section names contain no whitespace. If the name contains + * whitespace, trim it, squeeze it and replace the remainig whitespace with + * hyphens. If a replacement was made, a warning is logged. + * + * @param section Section name + */ +void fix_section_name(char *section) +{ + for (char* s = section; *s; s++) + { + if (isspace(*s)) + { + if (warned_whitespace.find(section) == warned_whitespace.end()) + { + warned_whitespace.insert(section); + MXS_WARNING("Whitespace in object names is deprecated, " + "converting to hyphens: %s", section); + } + break; + } + } + + squeeze_whitespace(section); + trim(section); + replace_whitespace(section); +} + /** * Config item handler for the ini file reader * @@ -355,11 +438,11 @@ ini_handler(void *userdata, const char *section, const char *name, const char *v CONFIG_CONTEXT *cntxt = (CONFIG_CONTEXT *)userdata; CONFIG_CONTEXT *ptr = cntxt; - if (strcmp(section, "gateway") == 0 || strcasecmp(section, "MaxScale") == 0) + if (strcmp(section, CN_GATEWAY) == 0 || strcasecmp(section, CN_MAXSCALE) == 0) { return handle_global_item(name, value); } - else if (strcasecmp(section, "feedback") == 0) + else if (strcasecmp(section, CN_FEEDBACK) == 0) { return handle_feedback_item(name, value); } @@ -369,19 +452,23 @@ ini_handler(void *userdata, const char *section, const char *name, const char *v return 0; } + char fixed_section[strlen(section) + 1]; + strcpy(fixed_section, section); + fix_section_name(fixed_section); + /* * If we already have some parameters for the object * add the parameters to that object. If not create * a new object. */ - while (ptr && strcmp(ptr->object, section) != 0) + while (ptr && strcmp(ptr->object, fixed_section) != 0) { ptr = ptr->next; } if (!ptr) { - if ((ptr = config_context_create(section)) == NULL) + if ((ptr = config_context_create(fixed_section)) == NULL) { return 0; } @@ -392,7 +479,17 @@ ini_handler(void *userdata, const char *section, const char *name, const char *v if (config_get_param(ptr->parameters, name)) { - if (!config_append_param(ptr, name, value)) + /** The values in the persisted configurations are updated versions of + * the ones in the main configuration file. */ + if (is_persisted_config) + { + if (!config_replace_param(ptr, name, value)) + { + return 0; + } + } + /** Multi-line parameter */ + else if (!config_append_param(ptr, name, value)) { return 0; } @@ -781,10 +878,10 @@ process_config_context(CONFIG_CONTEXT *context) obj = context; while (obj) { - char *type = config_get_value(obj->parameters, "type"); + char *type = config_get_value(obj->parameters, CN_TYPE); if (type) { - if (!strcmp(type, "service")) + if (!strcmp(type, CN_SERVICE)) { error_count += create_new_service(obj); } @@ -815,22 +912,22 @@ process_config_context(CONFIG_CONTEXT *context) obj = context; while (obj) { - char *type = config_get_value(obj->parameters, "type"); + char *type = config_get_value(obj->parameters, CN_TYPE); if (type) { - if (!strcmp(type, "service")) + if (!strcmp(type, CN_SERVICE)) { error_count += configure_new_service(context, obj); } - else if (!strcmp(type, "listener")) + else if (!strcmp(type, CN_LISTENER)) { error_count += create_new_listener(obj); } - else if (!strcmp(type, "monitor")) + else if (!strcmp(type, CN_MONITOR)) { error_count += create_new_monitor(context, obj, monitorhash); } - else if (strcmp(type, "server") != 0 && strcmp(type, "filter") != 0) + else if (strcmp(type, CN_SERVER) != 0 && strcmp(type, CN_FILTER) != 0) { MXS_ERROR("Configuration object '%s' has an invalid type specified.", obj->object); @@ -898,7 +995,7 @@ config_get_value(MXS_CONFIG_PARAMETER *params, const char *name) static char * config_get_password(MXS_CONFIG_PARAMETER *params) { - char *password = config_get_value(params, "password"); + char *password = config_get_value(params, CN_PASSWORD); char *passwd = config_get_value(params, "passwd"); if (password && passwd) @@ -1236,9 +1333,9 @@ static int handle_global_item(const char *name, const char *value) { int i; - if (strcmp(name, "threads") == 0) + if (strcmp(name, CN_THREADS) == 0) { - if (strcmp(value, "auto") == 0) + if (strcmp(value, CN_AUTO) == 0) { if ((gateway.n_threads = get_processor_count()) > 1) { @@ -1275,23 +1372,23 @@ handle_global_item(const char *name, const char *value) gateway.n_threads = MXS_MAX_THREADS; } } - else if (strcmp(name, "non_blocking_polls") == 0) + else if (strcmp(name, CN_NON_BLOCKING_POLLS) == 0) { gateway.n_nbpoll = atoi(value); } - else if (strcmp(name, "poll_sleep") == 0) + else if (strcmp(name, CN_POLL_SLEEP) == 0) { gateway.pollsleep = atoi(value); } - else if (strcmp(name, "ms_timestamp") == 0) + else if (strcmp(name, CN_MS_TIMESTAMP) == 0) { mxs_log_set_highprecision_enabled(config_truth_value((char*)value)); } - else if (strcmp(name, "skip_permission_checks") == 0) + else if (strcmp(name, CN_SKIP_PERMISSION_CHECKS) == 0) { gateway.skip_permission_checks = config_truth_value((char*)value); } - else if (strcmp(name, "auth_connect_timeout") == 0) + else if (strcmp(name, CN_AUTH_CONNECT_TIMEOUT) == 0) { char* endptr; int intval = strtol(value, &endptr, 0); @@ -1304,7 +1401,7 @@ handle_global_item(const char *name, const char *value) MXS_WARNING("Invalid timeout value for 'auth_connect_timeout': %s", value); } } - else if (strcmp(name, "auth_read_timeout") == 0) + else if (strcmp(name, CN_AUTH_READ_TIMEOUT) == 0) { char* endptr; int intval = strtol(value, &endptr, 0); @@ -1317,7 +1414,7 @@ handle_global_item(const char *name, const char *value) MXS_ERROR("Invalid timeout value for 'auth_read_timeout': %s", value); } } - else if (strcmp(name, "auth_write_timeout") == 0) + else if (strcmp(name, CN_AUTH_WRITE_TIMEOUT) == 0) { char* endptr; int intval = strtol(value, &endptr, 0); @@ -1330,7 +1427,7 @@ handle_global_item(const char *name, const char *value) MXS_ERROR("Invalid timeout value for 'auth_write_timeout': %s", value); } } - else if (strcmp(name, "query_classifier") == 0) + else if (strcmp(name, CN_QUERY_CLASSIFIER) == 0) { int len = strlen(value); int max_len = sizeof(gateway.qc_name) - 1; @@ -1346,11 +1443,11 @@ handle_global_item(const char *name, const char *value) return 0; } } - else if (strcmp(name, "query_classifier_args") == 0) + else if (strcmp(name, CN_QUERY_CLASSIFIER_ARGS) == 0) { gateway.qc_args = MXS_STRDUP_A(value); } - else if (strcmp(name, "log_throttling") == 0) + else if (strcmp(name, CN_LOG_THROTTLING) == 0) { if (*value == 0) { @@ -1416,6 +1513,38 @@ handle_global_item(const char *name, const char *value) MXS_FREE(v); } } + else if (strcmp(name, CN_ADMIN_USER) == 0) + { + strcpy(gateway.admin_user, value); + } + else if (strcmp(name, CN_ADMIN_PASSWORD) == 0) + { + strcpy(gateway.admin_password, value); + } + else if (strcmp(name, CN_ADMIN_PORT) == 0) + { + gateway.admin_port = atoi(value); + } + else if (strcmp(name, CN_ADMIN_HOST) == 0) + { + strcpy(gateway.admin_host, value); + } + else if (strcmp(name, CN_ADMIN_SSL_KEY) == 0) + { + strcpy(gateway.admin_ssl_key, value); + } + else if (strcmp(name, CN_ADMIN_SSL_CERT) == 0) + { + strcpy(gateway.admin_ssl_cert, value); + } + else if (strcmp(name, CN_ADMIN_SSL_CA_CERT) == 0) + { + strcpy(gateway.admin_ssl_ca_cert, value); + } + else if (strcmp(name, CN_ADMIN_AUTH) == 0) + { + gateway.admin_auth = config_truth_value(value); + } else { for (i = 0; lognames[i].name; i++) @@ -1468,22 +1597,22 @@ SSL_LISTENER* make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *e int local_errors = 0; SSL_LISTENER *new_ssl; - ssl = config_get_value(obj->parameters, "ssl"); + ssl = config_get_value(obj->parameters, CN_SSL); if (ssl) { - if (!strcmp(ssl, "required")) + if (!strcmp(ssl, CN_REQUIRED)) { if ((new_ssl = (SSL_LISTENER*)MXS_CALLOC(1, sizeof(SSL_LISTENER))) == NULL) { return NULL; } new_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX; - ssl_cert = config_get_value(obj->parameters, "ssl_cert"); - ssl_key = config_get_value(obj->parameters, "ssl_key"); - ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert"); - ssl_version = config_get_value(obj->parameters, "ssl_version"); - ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth"); + ssl_cert = config_get_value(obj->parameters, CN_SSL_CERT); + ssl_key = config_get_value(obj->parameters, CN_SSL_KEY); + ssl_ca_cert = config_get_value(obj->parameters, CN_SSL_CA_CERT); + ssl_version = config_get_value(obj->parameters, CN_SSL_VERSION); + ssl_cert_verify_depth = config_get_value(obj->parameters, CN_SSL_CERT_VERIFY_DEPTH); new_ssl->ssl_init_done = false; if (ssl_version) @@ -1636,6 +1765,15 @@ global_defaults() gateway.auth_read_timeout = DEFAULT_AUTH_READ_TIMEOUT; gateway.auth_write_timeout = DEFAULT_AUTH_WRITE_TIMEOUT; gateway.skip_permission_checks = false; + gateway.admin_port = DEFAULT_ADMIN_HTTP_PORT; + gateway.admin_auth = false; + strcpy(gateway.admin_host, DEFAULT_ADMIN_HOST); + strcpy(gateway.admin_user, INET_DEFAULT_USERNAME); + strcpy(gateway.admin_password, INET_DEFAULT_PASSWORD); + gateway.admin_ssl_key[0] = '\0'; + gateway.admin_ssl_cert[0] = '\0'; + gateway.admin_ssl_ca_cert[0] = '\0'; + if (version_string != NULL) { gateway.version_string = MXS_STRDUP_A(version_string); @@ -1715,14 +1853,14 @@ process_config_update(CONFIG_CONTEXT *context) obj = context; while (obj) { - char *type = config_get_value(obj->parameters, "type"); + char *type = config_get_value(obj->parameters, CN_TYPE); if (type == NULL) { MXS_ERROR("Configuration object %s has no type.", obj->object); } - else if (!strcmp(type, "service")) + else if (!strcmp(type, CN_SERVICE)) { - char *router = config_get_value(obj->parameters, "router"); + char *router = config_get_value(obj->parameters, CN_ROUTER); if (router) { if ((service = service_find(obj->object)) != NULL) @@ -1743,22 +1881,22 @@ process_config_update(CONFIG_CONTEXT *context) char *version_string; char *allow_localhost_match_wildcard_host; - enable_root_user = config_get_value(obj->parameters, "enable_root_user"); + enable_root_user = config_get_value(obj->parameters, CN_ENABLE_ROOT_USER); - connection_timeout = config_get_value(obj->parameters, "connection_timeout"); - max_connections = config_get_value_string(obj->parameters, "max_connections"); + connection_timeout = config_get_value(obj->parameters, CN_CONNECTION_TIMEOUT); + max_connections = config_get_value_string(obj->parameters, CN_MAX_CONNECTIONS); max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections"); queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout"); - user = config_get_value(obj->parameters, "user"); + user = config_get_value(obj->parameters, CN_USER); auth = config_get_password(obj->parameters); - auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); - strip_db_esc = config_get_value(obj->parameters, "strip_db_esc"); - version_string = config_get_value(obj->parameters, "version_string"); + auth_all_servers = config_get_value(obj->parameters, CN_AUTH_ALL_SERVERS); + strip_db_esc = config_get_value(obj->parameters, CN_STRIP_DB_ESC); + version_string = config_get_value(obj->parameters, CN_VERSION_STRING); allow_localhost_match_wildcard_host = - config_get_value(obj->parameters, "localhost_match_wildcard_host"); + config_get_value(obj->parameters, CN_LOCALHOST_MATCH_WILDCARD_HOST); - char *log_auth_warnings = config_get_value(obj->parameters, "log_auth_warnings"); + char *log_auth_warnings = config_get_value(obj->parameters, CN_LOG_AUTH_WARNINGS); int truthval; if (log_auth_warnings && (truthval = config_truth_value(log_auth_warnings)) != -1) { @@ -1767,11 +1905,7 @@ process_config_update(CONFIG_CONTEXT *context) if (version_string) { - if (service->version_string) - { - MXS_FREE(service->version_string); - } - service->version_string = MXS_STRDUP_A(version_string); + serviceSetVersionString(service, version_string); } if (user && auth) @@ -1825,14 +1959,14 @@ process_config_update(CONFIG_CONTEXT *context) } else if (!strcmp(type, "server")) { - char *address = config_get_value(obj->parameters, "address"); - char *port = config_get_value(obj->parameters, "port"); + char *address = config_get_value(obj->parameters, CN_ADDRESS); + char *port = config_get_value(obj->parameters, CN_PORT); if (address && port && (server = server_find(address, atoi(port))) != NULL) { - char *monuser = config_get_value(obj->parameters, "monuser"); - char *monpw = config_get_value(obj->parameters, "monpw"); + char *monuser = config_get_value(obj->parameters, CN_MONITORUSER); + char *monpw = config_get_value(obj->parameters, CN_MONITORPW); server_update_credentials(server, monuser, monpw); obj->element = server; } @@ -1930,28 +2064,28 @@ check_config_objects(CONFIG_CONTEXT *context) const char *type; const char *module_type = NULL; - if (obj->parameters && (type = config_get_value(obj->parameters, "type"))) + if (obj->parameters && (type = config_get_value(obj->parameters, CN_TYPE))) { - if (!strcmp(type, "service")) + if (!strcmp(type, CN_SERVICE)) { - param_set = service_params; - module = config_get_value(obj->parameters, "router"); + param_set = config_service_params; + module = config_get_value(obj->parameters, CN_ROUTER); module_type = MODULE_ROUTER; } - else if (!strcmp(type, "listener")) + else if (!strcmp(type, CN_LISTENER)) { - param_set = listener_params; + param_set = config_listener_params; } - else if (!strcmp(type, "monitor")) + else if (!strcmp(type, CN_MONITOR)) { - param_set = monitor_params; - module = config_get_value(obj->parameters, "module"); + param_set = config_monitor_params; + module = config_get_value(obj->parameters, CN_MODULE); module_type = MODULE_MONITOR; } - else if (!strcmp(type, "filter")) + else if (!strcmp(type, CN_FILTER)) { - param_set = filter_params; - module = config_get_value(obj->parameters, "module"); + param_set = config_filter_params; + module = config_get_value(obj->parameters, CN_MODULE); module_type = MODULE_FILTER; } } @@ -1986,6 +2120,11 @@ check_config_objects(CONFIG_CONTEXT *context) { process_path_parameter(params); } + else + { + /** Fix old-style object names */ + config_fix_param(mod->parameters, params); + } } params = params->next; } @@ -2325,6 +2464,23 @@ bool config_append_param(CONFIG_CONTEXT* obj, const char* key, const char* value return rval; } +bool config_replace_param(CONFIG_CONTEXT* obj, const char* key, const char* value) +{ + MXS_CONFIG_PARAMETER *param = config_get_param(obj->parameters, key); + ss_dassert(param); + char *new_value = MXS_STRDUP(value); + bool rval; + + if (new_value) + { + MXS_FREE(param->value); + param->value = new_value; + rval = true; + } + + return rval; +} + MXS_CONFIG* config_get_global_options() { return &gateway; @@ -2531,6 +2687,55 @@ void config_add_defaults(CONFIG_CONTEXT *ctx, const MXS_MODULE_PARAM *params) } } +static json_t* param_value_json(const MXS_CONFIG_PARAMETER* param, + const MXS_MODULE* mod) +{ + json_t* rval = NULL; + + for (int i = 0; mod->parameters[i].name; i++) + { + if (strcmp(mod->parameters[i].name, param->name) == 0) + { + switch (mod->parameters[i].type) + { + case MXS_MODULE_PARAM_COUNT: + case MXS_MODULE_PARAM_INT: + rval = json_integer(strtol(param->value, NULL, 10)); + break; + + case MXS_MODULE_PARAM_BOOL: + rval = json_boolean(config_truth_value(param->value)); + break; + + default: + rval = json_string(param->value); + break; + } + } + } + + return rval; +} + +void config_add_module_params_json(const MXS_MODULE* mod, MXS_CONFIG_PARAMETER* parameters, + const char** type_params, json_t* output) +{ + set param_set; + + for (int i = 0; type_params[i]; i++) + { + param_set.insert(type_params[i]); + } + + for (MXS_CONFIG_PARAMETER* p = parameters; p; p = p->next) + { + if (param_set.find(p->name) == param_set.end()) + { + json_object_set_new(output, p->name, param_value_json(p, mod)); + } + } +} + /** * Create a new router for a service * @param obj Service configuration context @@ -2538,7 +2743,7 @@ void config_add_defaults(CONFIG_CONTEXT *ctx, const MXS_MODULE_PARAM *params) */ int create_new_service(CONFIG_CONTEXT *obj) { - char *router = config_get_value(obj->parameters, "router"); + char *router = config_get_value(obj->parameters, CN_ROUTER); if (router == NULL) { obj->element = NULL; @@ -2555,19 +2760,19 @@ int create_new_service(CONFIG_CONTEXT *obj) int error_count = 0; MXS_CONFIG_PARAMETER* param; - char *retry = config_get_value(obj->parameters, "retry_on_failure"); + char *retry = config_get_value(obj->parameters, CN_RETRY_ON_FAILURE); if (retry) { serviceSetRetryOnFailure(service, retry); } - char *enable_root_user = config_get_value(obj->parameters, "enable_root_user"); + char *enable_root_user = config_get_value(obj->parameters, CN_ENABLE_ROOT_USER); if (enable_root_user) { serviceEnableRootUser(service, config_truth_value(enable_root_user)); } - char *max_retry_interval = config_get_value(obj->parameters, "max_retry_interval"); + char *max_retry_interval = config_get_value(obj->parameters, CN_MAX_RETRY_INTERVAL); if (max_retry_interval) { @@ -2585,13 +2790,13 @@ int create_new_service(CONFIG_CONTEXT *obj) } } - char *connection_timeout = config_get_value(obj->parameters, "connection_timeout"); + char *connection_timeout = config_get_value(obj->parameters, CN_CONNECTION_TIMEOUT); if (connection_timeout) { serviceSetTimeout(service, atoi(connection_timeout)); } - const char *max_connections = config_get_value_string(obj->parameters, "max_connections"); + const char *max_connections = config_get_value_string(obj->parameters, CN_MAX_CONNECTIONS); const char *max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections"); const char *queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout"); if (strlen(max_connections)) @@ -2600,31 +2805,31 @@ int create_new_service(CONFIG_CONTEXT *obj) atoi(max_queued_connections), atoi(queued_connection_timeout)); } - char *auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); + char *auth_all_servers = config_get_value(obj->parameters, CN_AUTH_ALL_SERVERS); if (auth_all_servers) { serviceAuthAllServers(service, config_truth_value(auth_all_servers)); } - char *strip_db_esc = config_get_value(obj->parameters, "strip_db_esc"); + char *strip_db_esc = config_get_value(obj->parameters, CN_STRIP_DB_ESC); if (strip_db_esc) { serviceStripDbEsc(service, config_truth_value(strip_db_esc)); } - char *weightby = config_get_value(obj->parameters, "weightby"); + char *weightby = config_get_value(obj->parameters, CN_WEIGHTBY); if (weightby) { serviceWeightBy(service, weightby); } - char *wildcard = config_get_value(obj->parameters, "localhost_match_wildcard_host"); + char *wildcard = config_get_value(obj->parameters, CN_LOCALHOST_MATCH_WILDCARD_HOST); if (wildcard) { serviceEnableLocalhostMatchWildcardHost(service, config_truth_value(wildcard)); } - char *user = config_get_value(obj->parameters, "user"); + char *user = config_get_value(obj->parameters, CN_USER); char *auth = config_get_password(obj->parameters); if (user && auth) @@ -2641,7 +2846,7 @@ int create_new_service(CONFIG_CONTEXT *obj) auth ? "" : "the 'password' or 'passwd' parameter"); } - char *log_auth_warnings = config_get_value(obj->parameters, "log_auth_warnings"); + char *log_auth_warnings = config_get_value(obj->parameters, CN_LOG_AUTH_WARNINGS); if (log_auth_warnings) { int truthval = config_truth_value(log_auth_warnings); @@ -2655,7 +2860,7 @@ int create_new_service(CONFIG_CONTEXT *obj) } } - char *version_string = config_get_value(obj->parameters, "version_string"); + char *version_string = config_get_value(obj->parameters, CN_VERSION_STRING); if (version_string) { /** Add the 5.5.5- string to the start of the version string if @@ -2664,22 +2869,18 @@ int create_new_service(CONFIG_CONTEXT *obj) if (version_string[0] != '5') { size_t len = strlen(version_string) + strlen("5.5.5-") + 1; - service->version_string = (char*)MXS_MALLOC(len); - MXS_ABORT_IF_NULL(service->version_string); - strcpy(service->version_string, "5.5.5-"); - strcat(service->version_string, version_string); + char ver[len]; + snprintf(ver, sizeof(ver), "5.5.5-%s", version_string); + serviceSetVersionString(service, ver); } else { - service->version_string = MXS_STRDUP_A(version_string); + serviceSetVersionString(service, version_string); } } - else + else if (gateway.version_string) { - if (gateway.version_string) - { - service->version_string = MXS_STRDUP_A(gateway.version_string); - } + serviceSetVersionString(service, gateway.version_string); } @@ -2724,13 +2925,13 @@ bool is_normal_server_parameter(const char *param) int create_new_server(CONFIG_CONTEXT *obj) { int error_count = 0; - char *address = config_get_value(obj->parameters, "address"); - char *port = config_get_value(obj->parameters, "port"); - char *protocol = config_get_value(obj->parameters, "protocol"); - char *monuser = config_get_value(obj->parameters, "monitoruser"); - char *monpw = config_get_value(obj->parameters, "monitorpw"); - char *auth = config_get_value(obj->parameters, "authenticator"); - char *auth_opts = config_get_value(obj->parameters, "authenticator_options"); + char *address = config_get_value(obj->parameters, CN_ADDRESS); + char *port = config_get_value(obj->parameters, CN_PORT); + char *protocol = config_get_value(obj->parameters, CN_PROTOCOL); + char *monuser = config_get_value(obj->parameters, CN_MONITORUSER); + char *monpw = config_get_value(obj->parameters, CN_MONITORPW); + char *auth = config_get_value(obj->parameters, CN_AUTHENTICATOR); + char *auth_opts = config_get_value(obj->parameters, CN_AUTHENTICATOR_OPTIONS); if (address && port && protocol) { @@ -2764,7 +2965,7 @@ int create_new_server(CONFIG_CONTEXT *obj) } char *endptr; - const char *poolmax = config_get_value_string(obj->parameters, "persistpoolmax"); + const char *poolmax = config_get_value_string(obj->parameters, CN_PERSISTPOOLMAX); if (poolmax) { long int persistpoolmax = strtol(poolmax, &endptr, 0); @@ -2780,7 +2981,7 @@ int create_new_server(CONFIG_CONTEXT *obj) } } - const char *persistmax = config_get_value_string(obj->parameters, "persistmaxtime"); + const char *persistmax = config_get_value_string(obj->parameters, CN_PERSISTMAXTIME); if (persistmax) { long int persistmaxtime = strtol(persistmax, &endptr, 0); @@ -2796,7 +2997,7 @@ int create_new_server(CONFIG_CONTEXT *obj) } } - server->use_proxy_protocol = config_get_bool(obj->parameters, USE_PROXY_PROTOCOL); + server->use_proxy_protocol = config_get_bool(obj->parameters, CN_USE_PROXY_PROTOCOL); MXS_CONFIG_PARAMETER *params = obj->parameters; @@ -2829,10 +3030,10 @@ int create_new_server(CONFIG_CONTEXT *obj) int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj) { int error_count = 0; - char *filters = config_get_value(obj->parameters, "filters"); - char *servers = config_get_value(obj->parameters, "servers"); - char *monitor = config_get_value(obj->parameters, "monitor"); - char *roptions = config_get_value(obj->parameters, "router_options"); + char *filters = config_get_value(obj->parameters, CN_FILTERS); + char *servers = config_get_value(obj->parameters, CN_SERVERS); + char *monitor = config_get_value(obj->parameters, CN_MONITOR); + char *roptions = config_get_value(obj->parameters, CN_ROUTER_OPTIONS); SERVICE *service = (SERVICE*)obj->element; if (service) @@ -2852,7 +3053,7 @@ int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj) { if (strcmp(ctx->object, monitor) == 0) { - servers = config_get_value(ctx->parameters, "servers"); + servers = config_get_value(ctx->parameters, CN_SERVERS); break; } } @@ -2929,7 +3130,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* { int error_count = 0; - char *module = config_get_value(obj->parameters, "module"); + char *module = config_get_value(obj->parameters, CN_MODULE); if (module) { if ((obj->element = monitor_alloc(obj->object, module)) == NULL) @@ -2952,7 +3153,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* error_count++; } - char *servers = config_get_value(obj->parameters, "servers"); + char *servers = config_get_value(obj->parameters, CN_SERVERS); if (error_count == 0) { @@ -2969,7 +3170,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* error_count++; } - char *interval_str = config_get_value(obj->parameters, "monitor_interval"); + char *interval_str = config_get_value(obj->parameters, CN_MONITOR_INTERVAL); if (interval_str) { char *endptr; @@ -2982,54 +3183,54 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* } else { - MXS_NOTICE("Invalid 'monitor_interval' parameter for monitor '%s', " + MXS_NOTICE("Invalid '%s' parameter for monitor '%s', " "using default value of %d milliseconds.", - obj->object, MONITOR_DEFAULT_INTERVAL); + CN_MONITOR_INTERVAL, obj->object, MONITOR_DEFAULT_INTERVAL); } } else { - MXS_NOTICE("Monitor '%s' is missing the 'monitor_interval' parameter, " + MXS_NOTICE("Monitor '%s' is missing the '%s' parameter, " "using default value of %d milliseconds.", - obj->object, MONITOR_DEFAULT_INTERVAL); + CN_MONITOR_INTERVAL, obj->object, MONITOR_DEFAULT_INTERVAL); } - char *connect_timeout = config_get_value(obj->parameters, "backend_connect_timeout"); + char *connect_timeout = config_get_value(obj->parameters, CN_BACKEND_CONNECT_TIMEOUT); if (connect_timeout) { if (!monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, atoi(connect_timeout))) { - MXS_ERROR("Failed to set backend_connect_timeout"); + MXS_ERROR("Failed to set '%s'", CN_BACKEND_CONNECT_TIMEOUT); error_count++; } } - char *read_timeout = config_get_value(obj->parameters, "backend_read_timeout"); + char *read_timeout = config_get_value(obj->parameters, CN_BACKEND_READ_TIMEOUT); if (read_timeout) { if (!monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, atoi(read_timeout))) { - MXS_ERROR("Failed to set backend_read_timeout"); + MXS_ERROR("Failed to set '%s'", CN_BACKEND_READ_TIMEOUT); error_count++; } } - char *write_timeout = config_get_value(obj->parameters, "backend_write_timeout"); + char *write_timeout = config_get_value(obj->parameters, CN_BACKEND_WRITE_TIMEOUT); if (write_timeout) { if (!monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, atoi(write_timeout))) { - MXS_ERROR("Failed to set backend_write_timeout"); + MXS_ERROR("Failed to set '%s'", CN_BACKEND_WRITE_TIMEOUT); error_count++; } } - char *connect_attempts = config_get_value(obj->parameters, BACKEND_CONNECT_ATTEMPTS); + char *connect_attempts = config_get_value(obj->parameters, CN_BACKEND_CONNECT_ATTEMPTS); if (connect_attempts) { if (!monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_ATTEMPTS, atoi(connect_attempts))) { - MXS_ERROR("Failed to set '%s'.", BACKEND_CONNECT_ATTEMPTS); + MXS_ERROR("Failed to set '%s'", CN_BACKEND_CONNECT_ATTEMPTS); error_count++; } } @@ -3069,7 +3270,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* } } - char *user = config_get_value(obj->parameters, "user"); + char *user = config_get_value(obj->parameters, CN_USER); char *passwd = config_get_password(obj->parameters); if (user && passwd) { @@ -3095,16 +3296,20 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* int create_new_listener(CONFIG_CONTEXT *obj) { int error_count = 0; - char *service_name = config_get_value(obj->parameters, "service"); - char *port = config_get_value(obj->parameters, "port"); - char *address = config_get_value(obj->parameters, "address"); - char *protocol = config_get_value(obj->parameters, "protocol"); - char *socket = config_get_value(obj->parameters, "socket"); - char *authenticator = config_get_value(obj->parameters, "authenticator"); - char *authenticator_options = config_get_value(obj->parameters, "authenticator_options"); + char *raw_service_name = config_get_value(obj->parameters, CN_SERVICE); + char *port = config_get_value(obj->parameters, CN_PORT); + char *address = config_get_value(obj->parameters, CN_ADDRESS); + char *protocol = config_get_value(obj->parameters, CN_PROTOCOL); + char *socket = config_get_value(obj->parameters, CN_SOCKET); + char *authenticator = config_get_value(obj->parameters, CN_AUTHENTICATOR); + char *authenticator_options = config_get_value(obj->parameters, CN_AUTHENTICATOR_OPTIONS); - if (service_name && protocol && (socket || port)) + if (raw_service_name && protocol && (socket || port)) { + char service_name[strlen(raw_service_name) + 1]; + strcpy(service_name, raw_service_name); + fix_section_name(service_name); + SERVICE *service = service_find(service_name); if (service) { @@ -3171,14 +3376,14 @@ int create_new_listener(CONFIG_CONTEXT *obj) int create_new_filter(CONFIG_CONTEXT *obj) { int error_count = 0; - char *module = config_get_value(obj->parameters, "module"); + char *module = config_get_value(obj->parameters, CN_MODULE); if (module) { if ((obj->element = filter_alloc(obj->object, module))) { MXS_FILTER_DEF* filter_def = (MXS_FILTER_DEF*)obj->element; - char *options = config_get_value(obj->parameters, "options"); + char *options = config_get_value(obj->parameters, CN_OPTIONS); if (options) { char *lasts; @@ -3226,23 +3431,23 @@ bool config_have_required_ssl_params(CONFIG_CONTEXT *obj) { MXS_CONFIG_PARAMETER *param = obj->parameters; - return config_get_param(param, "ssl") && - config_get_param(param, "ssl_key") && - config_get_param(param, "ssl_cert") && - config_get_param(param, "ssl_ca_cert") && - strcmp(config_get_value_string(param, "ssl"), "required") == 0; + return config_get_param(param, CN_SSL) && + config_get_param(param, CN_SSL_KEY) && + config_get_param(param, CN_SSL_CERT) && + config_get_param(param, CN_SSL_CA_CERT) && + strcmp(config_get_value_string(param, CN_SSL), CN_REQUIRED) == 0; } bool config_is_ssl_parameter(const char *key) { const char *ssl_params[] = { - "ssl_cert", - "ssl_ca_cert", - "ssl", - "ssl_key", - "ssl_version", - "ssl_cert_verify_depth", + CN_SSL_CERT, + CN_SSL_CA_CERT, + CN_SSL, + CN_SSL_KEY, + CN_SSL_VERSION, + CN_SSL_CERT_VERIFY_DEPTH, NULL }; @@ -3342,7 +3547,7 @@ static bool config_contains_type(const CONFIG_CONTEXT *ctx, const char *name, co while (ctx) { if (strcmp(ctx->object, name) == 0 && - strcmp(type, config_get_value_string(ctx->parameters, "type")) == 0) + strcmp(type, config_get_value_string(ctx->parameters, CN_TYPE)) == 0) { return true; } @@ -3353,10 +3558,58 @@ static bool config_contains_type(const CONFIG_CONTEXT *ctx, const char *name, co return false; } +void fix_serverlist(char* value) +{ + string dest; + char* end; + char* start = strtok_r(value, ",", &end); + const char* sep = ""; + + while (start) + { + fix_section_name(start); + dest += sep; + dest += start; + sep = ","; + } + + /** The value will always be smaller than the original one or of equal size */ + strcpy(value, dest.c_str()); +} + +void config_fix_param(const MXS_MODULE_PARAM *params, MXS_CONFIG_PARAMETER *p) +{ + for (int i = 0; params[i].name; i++) + { + if (strcmp(params[i].name, p->name) == 0) + { + switch (params[i].type) + { + case MXS_MODULE_PARAM_SERVER: + case MXS_MODULE_PARAM_SERVICE: + fix_section_name(p->value); + break; + + case MXS_MODULE_PARAM_SERVERLIST: + fix_serverlist(p->value); + break; + + default: + break; + } + + break; + } + } +} + bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key, const char *value, const CONFIG_CONTEXT *context) { bool valid = false; + char fixed_value[strlen(value) + 1]; + strcpy(fixed_value, value); + fix_section_name(fixed_value); for (int i = 0; params[i].name && !valid; i++) { @@ -3473,14 +3726,14 @@ bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key, break; case MXS_MODULE_PARAM_SERVICE: - if (context && config_contains_type(context, value, "service")) + if (context && config_contains_type(context, fixed_value, CN_SERVICE)) { valid = true; } break; case MXS_MODULE_PARAM_SERVER: - if (context && config_contains_type(context, value, "server")) + if (context && config_contains_type(context, fixed_value, CN_SERVER)) { valid = true; } @@ -3498,7 +3751,7 @@ bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key, for (int i = 0; i < n_serv; i++) { if (valid && - !config_contains_type(context, server_names[i], "server")) + !config_contains_type(context, server_names[i], CN_SERVER)) { valid = false; } @@ -3558,7 +3811,8 @@ int config_parse_server_list(const char *servers, char ***output_array) { char srv_name_tmp[strlen(s) + 1]; strcpy(srv_name_tmp, s); - trim(srv_name_tmp); + fix_section_name(srv_name_tmp); + if (strlen(srv_name_tmp) > 0) { results[output_ind] = MXS_STRDUP(srv_name_tmp); @@ -3593,3 +3847,23 @@ int config_parse_server_list(const char *servers, char ***output_array) } return output_ind; } + +json_t* config_paths_to_json(const char* host) +{ + json_t* obj = json_object(); + + json_object_set_new(obj, "libdir", json_string(get_libdir())); + json_object_set_new(obj, "datadir", json_string(get_datadir())); + json_object_set_new(obj, "process_datadir", json_string(get_process_datadir())); + json_object_set_new(obj, "cachedir", json_string(get_cachedir())); + json_object_set_new(obj, "configdir", json_string(get_configdir())); + json_object_set_new(obj, "config_persistdir", json_string(get_config_persistdir())); + json_object_set_new(obj, "module_configdir", json_string(get_module_configdir())); + json_object_set_new(obj, "piddir", json_string(get_piddir())); + json_object_set_new(obj, "logdir", json_string(get_logdir())); + json_object_set_new(obj, "langdir", json_string(get_langdir())); + json_object_set_new(obj, "execdir", json_string(get_execdir())); + json_object_set_new(obj, "connector_plugindir", json_string(get_connector_plugindir())); + + return obj; +} diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index ad683b1ba..e72adc204 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -10,19 +10,31 @@ * of this software will be governed by version 2 or later of the General * Public License. */ + +#include + #include "maxscale/config_runtime.h" #include +#include +#include +#include +#include #include #include #include +#include #include "maxscale/config.h" #include "maxscale/monitor.h" #include "maxscale/modules.h" #include "maxscale/service.h" +using std::string; +using std::set; +using mxs::Closer; + static SPINLOCK crt_lock = SPINLOCK_INIT; bool runtime_link_server(SERVER *server, const char *target) @@ -37,7 +49,7 @@ bool runtime_link_server(SERVER *server, const char *target) { if (serviceAddBackend(service, server)) { - service_serialize_servers(service); + service_serialize(service); rval = true; } } @@ -45,7 +57,7 @@ bool runtime_link_server(SERVER *server, const char *target) { if (monitorAddServer(monitor, server)) { - monitor_serialize_servers(monitor); + monitor_serialize(monitor); rval = true; } } @@ -75,12 +87,12 @@ bool runtime_unlink_server(SERVER *server, const char *target) if (service) { serviceRemoveBackend(service, server); - service_serialize_servers(service); + service_serialize(service); } else if (monitor) { monitorRemoveServer(monitor, server); - monitor_serialize_servers(monitor); + monitor_serialize(monitor); } const char *type = service ? "service" : "monitor"; @@ -91,7 +103,6 @@ bool runtime_unlink_server(SERVER *server, const char *target) 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) @@ -212,12 +223,12 @@ static SSL_LISTENER* create_ssl(const char *name, const char *key, const char *c 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))) + if (config_add_param(obj, CN_SSL, CN_REQUIRED) && + config_add_param(obj, CN_SSL_KEY, key) && + config_add_param(obj, CN_SSL_CERT, cert) && + config_add_param(obj, CN_SSL_CA_CERT, ca) && + (!version || config_add_param(obj, CN_SSL_VERSION, version)) && + (!depth || config_add_param(obj, CN_SSL_CERT_VERIFY_DEPTH, depth))) { int err = 0; SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err); @@ -266,24 +277,24 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert return rval; } -bool runtime_alter_server(SERVER *server, char *key, char *value) +bool runtime_alter_server(SERVER *server, const char *key, const char *value) { spinlock_acquire(&crt_lock); bool valid = true; - if (strcmp(key, "address") == 0) + if (strcmp(key, CN_ADDRESS) == 0) { server_update_address(server, value); } - else if (strcmp(key, "port") == 0) + else if (strcmp(key, CN_PORT) == 0) { server_update_port(server, atoi(value)); } - else if (strcmp(key, "monuser") == 0) + else if (strcmp(key, CN_MONITORUSER) == 0) { server_update_credentials(server, value, server->monpw); } - else if (strcmp(key, "monpw") == 0) + else if (strcmp(key, CN_MONITORPW) == 0) { server_update_credentials(server, server->monuser, value); } @@ -365,22 +376,22 @@ static void add_monitor_defaults(MXS_MONITOR *monitor) } } -bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) +bool runtime_alter_monitor(MXS_MONITOR *monitor, const char *key, const char *value) { spinlock_acquire(&crt_lock); bool valid = false; - if (strcmp(key, "user") == 0) + if (strcmp(key, CN_USER) == 0) { valid = true; monitorAddUser(monitor, value, monitor->password); } - else if (strcmp(key, "password") == 0) + else if (strcmp(key, CN_PASSWORD) == 0) { valid = true; monitorAddUser(monitor, monitor->user, value); } - else if (strcmp(key, "monitor_interval") == 0) + else if (strcmp(key, CN_MONITOR_INTERVAL) == 0) { long ival = get_positive_int(value); if (ival) @@ -389,7 +400,7 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) monitorSetInterval(monitor, ival); } } - else if (strcmp(key, "backend_connect_timeout") == 0) + else if (strcmp(key, CN_BACKEND_CONNECT_TIMEOUT) == 0) { long ival = get_positive_int(value); if (ival) @@ -398,7 +409,7 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); } } - else if (strcmp(key, "backend_write_timeout") == 0) + else if (strcmp(key, CN_BACKEND_WRITE_TIMEOUT) == 0) { long ival = get_positive_int(value); if (ival) @@ -407,7 +418,7 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); } } - else if (strcmp(key, "backend_read_timeout") == 0) + else if (strcmp(key, CN_BACKEND_READ_TIMEOUT) == 0) { long ival = get_positive_int(value); if (ival) @@ -416,7 +427,7 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); } } - else if (strcmp(key, BACKEND_CONNECT_ATTEMPTS) == 0) + else if (strcmp(key, CN_BACKEND_CONNECT_ATTEMPTS) == 0) { long ival = get_positive_int(value); if (ival) @@ -438,8 +449,8 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) if (value[0]) { MXS_CONFIG_PARAMETER p = {}; - p.name = key; - p.value = value; + p.name = const_cast(key); + p.value = const_cast(value); monitorAddParameters(monitor, &p); } @@ -463,6 +474,86 @@ bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value) return valid; } +bool runtime_alter_service(SERVICE *service, const char* zKey, const char* zValue) +{ + string key(zKey); + string value(zValue); + bool valid = true; + + spinlock_acquire(&crt_lock); + + if (key == CN_USER) + { + serviceSetUser(service, value.c_str(), service->credentials.authdata); + } + else if (key == CN_PASSWORD) + { + serviceSetUser(service, service->credentials.name, value.c_str()); + } + else if (key == CN_ENABLE_ROOT_USER) + { + serviceEnableRootUser(service, config_truth_value(value.c_str())); + } + else if (key == CN_MAX_RETRY_INTERVAL) + { + service_set_retry_interval(service, strtol(value.c_str(), NULL, 10)); + } + else if (key == CN_MAX_CONNECTIONS) + { + // TODO: Once connection queues are implemented, use correct values + serviceSetConnectionLimits(service, strtol(value.c_str(), NULL, 10), 0, 0); + } + else if (key == CN_CONNECTION_TIMEOUT) + { + serviceSetTimeout(service, strtol(value.c_str(), NULL, 10)); + } + else if (key == CN_AUTH_ALL_SERVERS) + { + serviceAuthAllServers(service, config_truth_value(value.c_str())); + } + else if (key == CN_STRIP_DB_ESC) + { + serviceStripDbEsc(service, config_truth_value(value.c_str())); + } + else if (key == CN_LOCALHOST_MATCH_WILDCARD_HOST) + { + serviceEnableLocalhostMatchWildcardHost(service, config_truth_value(value.c_str())); + } + else if (key == CN_VERSION_STRING) + { + serviceSetVersionString(service, value.c_str()); + } + else if (key == CN_WEIGHTBY) + { + serviceWeightBy(service, value.c_str()); + } + else if (key == CN_LOG_AUTH_WARNINGS) + { + // TODO: Move this inside the service source + service->log_auth_warnings = config_truth_value(value.c_str()); + } + else if (key == CN_RETRY_ON_FAILURE) + { + serviceSetRetryOnFailure(service, value.c_str()); + } + else + { + MXS_ERROR("Unknown parameter for service '%s': %s=%s", + service->name, key.c_str(), value.c_str()); + valid = false; + } + + if (valid) + { + service_serialize(service); + MXS_NOTICE("Updated service '%s': %s=%s", service->name, key.c_str(), value.c_str()); + } + + 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, @@ -470,26 +561,26 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add const char *ssl_version, const char *ssl_depth) { - if (addr == NULL || strcasecmp(addr, "default") == 0) + if (addr == NULL || strcasecmp(addr, CN_DEFAULT) == 0) { addr = "::"; } - if (port == NULL || strcasecmp(port, "default") == 0) + if (port == NULL || strcasecmp(port, CN_DEFAULT) == 0) { port = "3306"; } - if (proto == NULL || strcasecmp(proto, "default") == 0) + if (proto == NULL || strcasecmp(proto, CN_DEFAULT) == 0) { proto = "MySQLClient"; } - if (auth && strcasecmp(auth, "default") == 0) + if (auth && strcasecmp(auth, CN_DEFAULT) == 0) { /** Set auth to NULL so the protocol default authenticator is used */ auth = NULL; } - if (auth_opt && strcasecmp(auth_opt, "default") == 0) + if (auth_opt && strcasecmp(auth_opt, CN_DEFAULT) == 0) { /** Don't pass options to the authenticator */ auth_opt = NULL; @@ -659,3 +750,490 @@ bool runtime_destroy_monitor(MXS_MONITOR *monitor) spinlock_release(&crt_lock); return rval; } + +static bool extract_relations(json_t* json, set& relations, + const char** relation_types, + bool (*relation_check)(const string&, const string&)) +{ + bool rval = true; + json_t* rel; + + if ((rel = json_object_get(json, CN_RELATIONSHIPS))) + { + for (int i = 0; relation_types[i]; i++) + { + json_t* arr = json_object_get(rel, relation_types[i]); + + if (arr) + { + size_t size = json_array_size(arr); + + for (size_t j = 0; j < size; j++) + { + json_t* t = json_array_get(arr, j); + + if (json_is_string(t)) + { + string value = json_string_value(t); + + // Remove the link part + size_t pos = value.find_last_of("/"); + if (pos != string::npos) + { + value.erase(0, pos + 1); + } + + if (relation_check(relation_types[i], value)) + { + relations.insert(value); + } + else + { + rval = false; + } + } + else + { + rval = false; + } + } + } + } + } + + return rval; +} + +static inline const char* string_or_null(json_t* json, const char* name) +{ + const char* rval = NULL; + json_t* value = json_object_get(json, name); + + if (value && json_is_string(value)) + { + rval = json_string_value(value); + } + + return rval; +} + +static bool server_contains_required_fields(json_t* json) +{ + bool rval = false; + json_t* value; + + if ((value = json_object_get(json, CN_NAME)) && json_is_string(value)) + { + /** Object has a name field */ + json_t* param = json_object_get(json, CN_PARAMETERS); + + if (param && + (value = json_object_get(param, CN_ADDRESS)) && json_is_string(value) && + (value = json_object_get(param, CN_PORT)) && json_is_integer(value)) + { + rval = true; + } + } + + return rval; +} + +const char* server_relation_types[] = +{ + CN_SERVICES, + CN_MONITORS, + NULL +}; + +static bool server_relation_is_valid(const string& type, const string& value) +{ + return (type == CN_SERVICES && service_find(value.c_str())) || + (type == CN_MONITORS && monitor_find(value.c_str())); +} + +static bool unlink_server_from_objects(SERVER* server, set& relations) +{ + bool rval = true; + + for (set::iterator it = relations.begin(); it != relations.end(); it++) + { + if (!runtime_unlink_server(server, it->c_str())) + { + rval = false; + } + } + + return rval; +} + +static bool link_server_to_objects(SERVER* server, set& relations) +{ + bool rval = true; + + for (set::iterator it = relations.begin(); it != relations.end(); it++) + { + if (!runtime_link_server(server, it->c_str())) + { + unlink_server_from_objects(server, relations); + rval = false; + break; + } + } + + return rval; +} + +SERVER* runtime_create_server_from_json(json_t* json) +{ + SERVER* rval = NULL; + + if (server_contains_required_fields(json)) + { + const char* name = json_string_value(json_object_get(json, CN_NAME)); + json_t* params = json_object_get(json, CN_PARAMETERS); + const char* address = json_string_value(json_object_get(params, CN_ADDRESS)); + + /** The port needs to be in string format */ + char port[200]; // Enough to store any port value + int i = json_integer_value(json_object_get(params, CN_PORT)); + snprintf(port, sizeof(port), "%d", i); + + /** Optional parameters */ + const char* protocol = string_or_null(params, CN_PROTOCOL); + const char* authenticator = string_or_null(params, CN_AUTHENTICATOR); + const char* authenticator_options = string_or_null(params, CN_AUTHENTICATOR_OPTIONS); + + + set relations; + + if (extract_relations(json, relations, server_relation_types, server_relation_is_valid) && + runtime_create_server(name, address, port, protocol, authenticator, authenticator_options)) + { + rval = server_find_by_unique_name(name); + ss_dassert(rval); + + if (!link_server_to_objects(rval, relations)) + { + runtime_destroy_server(rval); + rval = NULL; + } + } + } + + return rval; +} + +bool server_to_object_relations(SERVER* server, json_t* old_json, json_t* new_json) +{ + bool rval = false; + set old_relations; + set new_relations; + + if (extract_relations(old_json, old_relations, server_relation_types, server_relation_is_valid) && + extract_relations(new_json, new_relations, server_relation_types, server_relation_is_valid)) + { + set removed_relations; + set added_relations; + + std::set_difference(old_relations.begin(), old_relations.end(), + new_relations.begin(), new_relations.end(), + std::inserter(removed_relations, removed_relations.begin())); + + std::set_difference(new_relations.begin(), new_relations.end(), + old_relations.begin(), old_relations.end(), + std::inserter(added_relations, added_relations.begin())); + + if (unlink_server_from_objects(server, removed_relations) && + link_server_to_objects(server, added_relations)) + { + rval = true; + } + } + + return rval; +} + +bool runtime_alter_server_from_json(SERVER* server, json_t* new_json) +{ + bool rval = false; + Closer old_json(server_to_json(server, "")); + ss_dassert(old_json.get()); + + if (server_to_object_relations(server, old_json.get(), new_json)) + { + json_t* parameters = json_object_get(new_json, CN_PARAMETERS); + json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS); + + ss_dassert(old_parameters); + + if (parameters) + { + rval = true; + const char* key; + json_t* value; + + json_object_foreach(parameters, key, value) + { + json_t* new_val = json_object_get(parameters, key); + json_t* old_val = json_object_get(old_parameters, key); + + if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val)) + { + /** No change in values */ + } + else if (!runtime_alter_server(server, key, mxs::json_to_string(value).c_str())) + { + rval = false; + } + } + } + } + + return rval; +} + +const char* object_relation_types[] = +{ + CN_SERVERS, + NULL +}; + +static bool object_relation_is_valid(const string& type, const string& value) +{ + return type == CN_SERVERS && server_find_by_unique_name(value.c_str()); +} + +/** + * @brief Do a coarse validation of the monitor JSON + * + * @param json JSON to validate + * + * @return True of the JSON is valid + */ +static bool validate_monitor_json(json_t* json) +{ + bool rval = false; + json_t* value; + + if ((value = json_object_get(json, CN_NAME)) && json_is_string(value) && + (value = json_object_get(json, CN_MODULE)) && json_is_string(value)) + { + set relations; + if (extract_relations(json, relations, object_relation_types, object_relation_is_valid)) + { + rval = true; + } + } + + return rval; +} + +static bool unlink_object_from_servers(const char* target, set& relations) +{ + bool rval = true; + + for (set::iterator it = relations.begin(); it != relations.end(); it++) + { + SERVER* server = server_find_by_unique_name(it->c_str()); + + if (!server || !runtime_unlink_server(server, target)) + { + rval = false; + break; + } + } + + return rval; +} + +static bool link_object_to_servers(const char* target, set& relations) +{ + bool rval = true; + + for (set::iterator it = relations.begin(); it != relations.end(); it++) + { + SERVER* server = server_find_by_unique_name(it->c_str()); + + if (!server || !runtime_link_server(server, target)) + { + unlink_server_from_objects(server, relations); + rval = false; + break; + } + } + + return rval; +} + +MXS_MONITOR* runtime_create_monitor_from_json(json_t* json) +{ + MXS_MONITOR* rval = NULL; + + if (validate_monitor_json(json)) + { + const char* name = json_string_value(json_object_get(json, CN_NAME)); + const char* module = json_string_value(json_object_get(json, CN_MODULE)); + + if (runtime_create_monitor(name, module)) + { + rval = monitor_find(name); + ss_dassert(rval); + + if (!runtime_alter_monitor_from_json(rval, json)) + { + runtime_destroy_monitor(rval); + rval = NULL; + } + } + } + + return rval; +} + +bool object_to_server_relations(const char* target, json_t* old_json, json_t* new_json) +{ + bool rval = false; + set old_relations; + set new_relations; + + if (extract_relations(old_json, old_relations, object_relation_types, object_relation_is_valid) && + extract_relations(new_json, new_relations, object_relation_types, object_relation_is_valid)) + { + set removed_relations; + set added_relations; + + std::set_difference(old_relations.begin(), old_relations.end(), + new_relations.begin(), new_relations.end(), + std::inserter(removed_relations, removed_relations.begin())); + + std::set_difference(new_relations.begin(), new_relations.end(), + old_relations.begin(), old_relations.end(), + std::inserter(added_relations, added_relations.begin())); + + if (unlink_object_from_servers(target, removed_relations) && + link_object_to_servers(target, added_relations)) + { + rval = true; + } + } + + return rval; +} + +bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json) +{ + bool rval = false; + Closer old_json(monitor_to_json(monitor, "")); + ss_dassert(old_json.get()); + + if (object_to_server_relations(monitor->name, old_json.get(), new_json)) + { + bool changed = false; + json_t* parameters = json_object_get(new_json, CN_PARAMETERS); + json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS); + + ss_dassert(old_parameters); + + if (parameters) + { + rval = true; + const char* key; + json_t* value; + + json_object_foreach(parameters, key, value) + { + json_t* new_val = json_object_get(parameters, key); + json_t* old_val = json_object_get(old_parameters, key); + + if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val)) + { + /** No change in values */ + } + else if (runtime_alter_monitor(monitor, key, mxs::json_to_string(value).c_str())) + { + changed = true; + } + else + { + rval = false; + } + } + } + + if (changed) + { + /** A configuration change was made, restart the monitor */ + monitorStop(monitor); + monitorStart(monitor, monitor->parameters); + } + } + + return rval; +} + +/** + * @brief Check if the service parameter can be altered at runtime + * + * @param key Parameter name + * @return True if the parameter can be altered + */ +static bool is_dynamic_param(const string& key) +{ + return key != CN_TYPE && + key != CN_ROUTER && + key != CN_ROUTER_OPTIONS && + key != CN_SERVERS; +} + +bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json) +{ + bool rval = false; + Closer old_json(service_to_json(service, "")); + ss_dassert(old_json.get()); + + if (object_to_server_relations(service->name, old_json.get(), new_json)) + { + bool changed = false; + json_t* parameters = json_object_get(new_json, CN_PARAMETERS); + json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS); + + ss_dassert(old_parameters); + + if (parameters) + { + /** Create a set of accepted service parameters */ + set paramset; + for (int i = 0; config_service_params[i]; i++) + { + if (is_dynamic_param(config_service_params[i])) + { + paramset.insert(config_service_params[i]); + } + } + + rval = true; + const char* key; + json_t* value; + + json_object_foreach(parameters, key, value) + { + json_t* new_val = json_object_get(parameters, key); + json_t* old_val = json_object_get(old_parameters, key); + + if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val)) + { + /** No change in values */ + } + else if (paramset.find(key) != paramset.end()) + { + if (!runtime_alter_service(service, key, mxs::json_to_string(value).c_str())) + { + rval = false; + } + } + } + } + } + + return rval; +} diff --git a/server/core/filter.cc b/server/core/filter.cc index 853b26168..05180a64c 100644 --- a/server/core/filter.cc +++ b/server/core/filter.cc @@ -14,21 +14,29 @@ /** * @file filter.c - A representation of a filter within MaxScale. */ -#include -#include + +#include "maxscale/filter.h" + #include #include #include #include +#include +#include + #include #include #include #include -#include "maxscale/filter.h" +#include +#include #include "maxscale/config.h" #include "maxscale/modules.h" +using std::string; +using std::set; + static SPINLOCK filter_spin = SPINLOCK_INIT; /**< Protects the list of all filters */ static MXS_FILTER_DEF *allFilters = NULL; /**< The list of all filters */ @@ -466,8 +474,91 @@ filter_upstream(MXS_FILTER_DEF *filter, MXS_FILTER_SESSION *fsession, MXS_UPSTRE } return me; } +json_t* filter_parameters_to_json(const MXS_FILTER_DEF* filter) +{ + json_t* rval = json_object(); + if (filter->options) + { + json_t* arr = json_array(); + for (int i = 0; filter->options && filter->options[i]; i++) + { + json_array_append_new(arr, json_string(filter->options[i])); + } + + json_object_set_new(rval, "options", arr); + } + + /** Add custom module parameters */ + const MXS_MODULE* mod = get_module(filter->module, MODULE_FILTER); + config_add_module_params_json(mod, filter->parameters, config_filter_params, rval); + + return rval; +} + +json_t* filter_to_json(const MXS_FILTER_DEF* filter, const char* host) +{ + json_t* rval = json_object(); + + json_object_set_new(rval, CN_NAME, json_string(filter->name)); + json_object_set_new(rval, CN_MODULE, json_string(filter->module)); + json_object_set_new(rval, CN_PARAMETERS, filter_parameters_to_json(filter)); + + if (filter->obj && filter->filter) + { + json_t* diag = filter->obj->diagnostics_json(filter->filter, NULL); + + if (diag) + { + json_object_set_new(rval, "filter_diagnostics", diag); + } + } + + /** Store relationships to other objects */ + json_t* rel = json_object(); + + string self = host; + self += "/filters/"; + self += filter->name; + json_object_set_new(rel, CN_SELF, json_string(self.c_str())); + + json_t* arr = service_relations_to_filter(filter, host); + + if (json_array_size(arr) > 0) + { + json_object_set_new(rel, "services", arr); + } + else + { + json_decref(arr); + } + + json_object_set_new(rval, "relationships", rel); + + return rval; +} + +json_t* filter_list_to_json(const char* host) +{ + json_t* rval = json_array(); + + spinlock_acquire(&filter_spin); + + for (MXS_FILTER_DEF* f = allFilters; f; f = f->next) + { + json_t* json = filter_to_json(f, host); + + if (json) + { + json_array_append_new(rval, json); + } + } + + spinlock_release(&filter_spin); + + return rval; +} namespace maxscale { @@ -509,8 +600,13 @@ int FilterSession::clientReply(GWBUF* pPacket) return m_up.clientReply(pPacket); } -void FilterSession::diagnostics(DCB* pDcb) +void FilterSession::diagnostics(DCB *pDcb) { } +json_t* FilterSession::diagnostics_json() const +{ + return NULL; +} + } diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 9d53c128c..47425c8cd 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -51,14 +51,16 @@ #include "maxscale/config.h" #include "maxscale/dcb.h" #include "maxscale/maxscale.h" +#include "maxscale/messagequeue.hh" #include "maxscale/modules.h" #include "maxscale/monitor.h" #include "maxscale/poll.h" #include "maxscale/service.h" #include "maxscale/statistics.h" +#include "maxscale/admin.hh" #include "maxscale/worker.hh" -using maxscale::Worker; +using namespace maxscale; #define STRING_BUFFER_SIZE 1024 #define PIDFD_CLOSED -1 @@ -1890,6 +1892,13 @@ int main(int argc, char **argv) goto return_main; } + if (!MessageQueue::init()) + { + MXS_ERROR("Failed to initialize message queue."); + rc = MAXSCALE_INTERNALERROR; + goto return_main; + } + if (!Worker::init()) { MXS_ERROR("Failed to initialize workers."); @@ -1973,6 +1982,18 @@ int main(int argc, char **argv) } } + if (mxs_admin_init()) + { + MXS_NOTICE("Started REST API on [%s]:%u", cnf->admin_host, cnf->admin_port); + } + else + { + const char* logerr = "Failed to initialize admin interface"; + print_log_n_stderr(true, true, logerr, logerr, 0); + rc = MAXSCALE_INTERNALERROR; + goto return_main; + } + MXS_NOTICE("MaxScale started with %d server threads.", config_threadcount()); /** * Successful start, notify the parent process that it can exit. @@ -1990,6 +2011,9 @@ int main(int argc, char **argv) ss_dassert(worker); worker->run(); + /** Stop administrative interface */ + mxs_admin_shutdown(); + /*< * Wait for the housekeeper to finish. */ @@ -2007,6 +2031,7 @@ int main(int argc, char **argv) } Worker::finish(); + MessageQueue::finish(); /*< * Destroy the router and filter instances of all services. diff --git a/server/core/housekeeper.cc b/server/core/housekeeper.cc index cd65c0afd..d8425e333 100644 --- a/server/core/housekeeper.cc +++ b/server/core/housekeeper.cc @@ -267,7 +267,7 @@ hkthread(void *data) for (i = 0; i < 10; i++) { thread_millisleep(100); - hkheartbeat++; + atomic_add_int64(&hkheartbeat, 1); } now = time(0); spinlock_acquire(&tasklock); diff --git a/server/core/httprequest.cc b/server/core/httprequest.cc new file mode 100644 index 000000000..12301c0f8 --- /dev/null +++ b/server/core/httprequest.cc @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 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 "maxscale/httprequest.hh" +#include "maxscale/admin.hh" + +#include +#include + +using std::string; +using std::deque; + +#define HTTP_HOST_HEADER "Host" + +const std::string HttpRequest::HTTP_PREFIX = "http://"; +const std::string HttpRequest::HTTPS_PREFIX = "https://"; + +/** TODO: Move this to a C++ string utility header */ +namespace maxscale +{ +static inline string& trim(string& str) +{ + if (str.length()) + { + if (isspace(*str.begin())) + { + string::iterator it = str.begin(); + + while (it != str.end() && isspace(*it)) + { + it++; + } + str.erase(str.begin(), it); + } + + if (isspace(*str.rbegin())) + { + string::reverse_iterator it = str.rbegin(); + while (it != str.rend() && isspace(*it)) + { + it++; + } + + str.erase(it.base(), str.end()); + } + } + + return str; +} +} + +static void process_uri(string& uri, std::deque& uri_parts) +{ + /** Clean up trailing slashes in requested resource */ + while (uri.length() > 1 && *uri.rbegin() == '/') + { + uri.erase(uri.find_last_of("/")); + } + + string my_uri = uri; + + while (my_uri.length() && *my_uri.begin() == '/') + { + my_uri.erase(my_uri.begin()); + } + + if (my_uri.length() == 0) + { + /** Special handling for the / resource */ + uri_parts.push_back(""); + } + else + { + while (my_uri.length() > 0) + { + size_t pos = my_uri.find("/"); + string part = pos == string::npos ? my_uri : my_uri.substr(0, pos); + my_uri.erase(0, pos == string::npos ? pos : pos + 1); + uri_parts.push_back(part); + } + } +} + +HttpRequest::HttpRequest(struct MHD_Connection *connection, string url, string method, json_t* data): + m_json(data), + m_json_string(data ? mxs::json_dump(data, 0) : ""), + m_resource(url), + m_verb(method), + m_connection(connection) +{ + process_uri(url, m_resource_parts); + + m_hostname = mxs_admin_https_enabled() ? HttpRequest::HTTPS_PREFIX : HttpRequest::HTTP_PREFIX; + m_hostname += get_header(HTTP_HOST_HEADER); +} + +HttpRequest::~HttpRequest() +{ + +} diff --git a/server/core/httpresponse.cc b/server/core/httpresponse.cc new file mode 100644 index 000000000..e0c6e4982 --- /dev/null +++ b/server/core/httpresponse.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 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 "maxscale/httpresponse.hh" + +#include +#include + +#include +#include + +#include "maxscale/admin.hh" + +using std::string; +using std::stringstream; + +HttpResponse::HttpResponse(int code, json_t* response): + m_body(response), + m_code(code) +{ + string http_date = http_get_date(); + add_header(HTTP_RESPONSE_HEADER_DATE, http_date); + add_header(HTTP_RESPONSE_HEADER_LAST_MODIFIED, http_date); + // This ETag is the base64 encoding of `not-yet-implemented` + add_header(HTTP_RESPONSE_HEADER_ETAG, "bm90LXlldC1pbXBsZW1lbnRlZAo"); +} + +HttpResponse::HttpResponse(const HttpResponse& response): + m_body(json_incref(response.m_body)), + m_code(response.m_code), + m_headers(response.m_headers) +{ +} + +HttpResponse& HttpResponse::operator=(const HttpResponse& response) +{ + json_t* body = m_body; + m_body = json_incref(response.m_body); + m_code = response.m_code; + m_headers = response.m_headers; + json_decref(body); + return *this; +} + +HttpResponse::~HttpResponse() +{ + if (m_body) + { + json_decref(m_body); + } +} + +json_t* HttpResponse::get_response() const +{ + return m_body; +} + +void HttpResponse::drop_response() +{ + json_decref(m_body); + m_body = NULL; +} + +int HttpResponse::get_code() const +{ + return m_code; +} + +void HttpResponse::add_header(const string& key, const string& value) +{ + m_headers[key] = value; +} + +const Headers& HttpResponse::get_headers() const +{ + return m_headers; +} diff --git a/server/core/listener.cc b/server/core/listener.cc index 7d0d1969c..4006ffb23 100644 --- a/server/core/listener.cc +++ b/server/core/listener.cc @@ -511,3 +511,29 @@ bool listener_serialize(const SERV_LISTENER *listener) return rval; } + +json_t* listener_to_json(const SERV_LISTENER* listener) +{ + json_t* rval = json_object(); + json_object_set_new(rval, "name", json_string(listener->name)); + json_object_set_new(rval, "address", json_string(listener->address)); + json_object_set_new(rval, "port", json_integer(listener->port)); + json_object_set_new(rval, "protocol", json_string(listener->protocol)); + json_object_set_new(rval, "authenticator", json_string(listener->authenticator)); + json_object_set_new(rval, "auth_options", json_string(listener->auth_options)); + + if (listener->ssl) + { + json_t* ssl = json_object(); + + const char* ssl_method = ssl_method_type_to_string(listener->ssl->ssl_method_type); + json_object_set_new(ssl, "ssl_version", json_string(ssl_method)); + json_object_set_new(ssl, "ssl_cert", json_string(listener->ssl->ssl_cert)); + json_object_set_new(ssl, "ssl_ca_cert", json_string(listener->ssl->ssl_ca_cert)); + json_object_set_new(ssl, "ssl_key", json_string(listener->ssl->ssl_key)); + + json_object_set_new(rval, "ssl", ssl); + } + + return rval; +} diff --git a/server/core/load_utils.cc b/server/core/load_utils.cc index f254dc1fe..39d088b6b 100644 --- a/server/core/load_utils.cc +++ b/server/core/load_utils.cc @@ -28,12 +28,16 @@ * * @endverbatim */ + +#include "maxscale/modules.h" + #include #include #include #include #include #include + #include #include #include @@ -45,6 +49,7 @@ #include #include "maxscale/modules.h" +#include "maxscale/config.h" typedef struct loaded_module { @@ -401,6 +406,64 @@ void dprintAllModules(DCB *dcb) dcb_printf(dcb, "----------------+-----------------+---------+-------+-------------------------\n\n"); } +static json_t* module_to_json(const LOADED_MODULE *mod, const char* host) +{ + json_t* obj = json_object(); + + json_object_set_new(obj, "name", json_string(mod->module)); + json_object_set_new(obj, "type", json_string(mod->type)); + json_object_set_new(obj, "version", json_string(mod->info->version)); + json_object_set_new(obj, "description", json_string(mod->info->description)); + json_object_set_new(obj, "api", json_string(mxs_module_api_to_string(mod->info->modapi))); + json_object_set_new(obj, "status", json_string(mxs_module_status_to_string(mod->info->status))); + + json_t* params = json_array(); + + for (int i = 0; mod->info->parameters[i].name; i++) + { + json_t* p = json_object(); + + json_object_set_new(p, CN_NAME, json_string(mod->info->parameters[i].name)); + json_object_set_new(p, CN_TYPE, json_string(mxs_module_param_type_to_string(mod->info->parameters[i].type))); + + if (mod->info->parameters[i].default_value) + { + json_object_set(p, "default_value", json_string(mod->info->parameters[i].default_value)); + } + + if (mod->info->parameters[i].type == MXS_MODULE_PARAM_ENUM && + mod->info->parameters[i].accepted_values) + { + json_t* arr = json_array(); + + for (int x = 0; mod->info->parameters[i].accepted_values[x].name; x++) + { + json_array_append_new(arr, json_string(mod->info->parameters[i].accepted_values[x].name)); + } + + json_object_set_new(p, "enum_values", arr); + } + + json_array_append_new(params, p); + } + + json_object_set_new(obj, CN_PARAMETERS, params); + + return obj; +} + +json_t* module_list_to_json(const char* host) +{ + json_t* arr = json_array(); + + for (LOADED_MODULE *ptr = registered; ptr; ptr = ptr->next) + { + json_array_append_new(arr, module_to_json(ptr, host)); + } + + return arr; +} + void moduleShowFeedbackReport(DCB *dcb) { GWBUF *buffer; @@ -843,7 +906,7 @@ const MXS_MODULE *get_module(const char *name, const char *type) { LOADED_MODULE *mod = find_module(name); - if (mod == NULL && load_module(name, type)) + if (mod == NULL && type && load_module(name, type)) { mod = find_module(name); } diff --git a/server/core/maxscale/admin.hh b/server/core/maxscale/admin.hh new file mode 100644 index 000000000..46959145a --- /dev/null +++ b/server/core/maxscale/admin.hh @@ -0,0 +1,77 @@ +#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/bsl11. + * + * 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 + +class Client +{ +public: + + /** + * @brief Create a new client + * + * @param connection The connection handle for this client + */ + Client(MHD_Connection *connection): + m_connection(connection) + { + } + + ~Client() + { + } + + /** + * @brief Process a client request + * + * This function can be called multiple times if a PUT/POST/PATCH + * uploads a large amount of data. + * + * @param url Requested URL + * @param method Request method + * @param data Pointer to request data + * @param size Size of request data + * + * @return MHD_YES on success, MHD_NO on error + */ + int process(std::string url, std::string method, const char* data, size_t *size); + +private: + MHD_Connection* m_connection; /**< Connection handle */ + std::string m_data; /**< Uploaded data */ +}; + +/** + * @brief Start the administrative interface + * + * @return True if the interface was successfully started + */ +bool mxs_admin_init(); + +/** + * @brief Shutdown the administrative interface + */ +void mxs_admin_shutdown(); + +/** + * @brief Check if admin interface uses HTTPS protocol + * + * @return True if HTTPS is enabled + */ +bool mxs_admin_https_enabled(); diff --git a/server/core/maxscale/config.h b/server/core/maxscale/config.h index d3004d4cc..eb32e0430 100644 --- a/server/core/maxscale/config.h +++ b/server/core/maxscale/config.h @@ -19,6 +19,7 @@ #include #include +#include MXS_BEGIN_DECLS @@ -34,6 +35,13 @@ enum MAX_PARAM_LEN = 256 }; +/** Object type specific parameter name lists */ +extern const char *config_service_params[]; +extern const char *config_listener_params[]; +extern const char *config_monitor_params[]; +extern const char *config_filter_params[]; +extern const char *config_server_params[]; + /** * @brief Generate default module parameters * @@ -84,6 +92,16 @@ bool config_add_param(CONFIG_CONTEXT* obj, const char* key, const char* value); */ bool config_append_param(CONFIG_CONTEXT* obj, const char* key, const char* value); +/** + * @brief Replace an existing parameter + * + * @param obj Configuration context + * @param key Parameter name + * @param value Parameter value + * @return True on success, false on memory allocation error + */ +bool config_replace_param(CONFIG_CONTEXT* obj, const char* key, const char* value); + /** * @brief Construct an SSL structure * @@ -111,4 +129,24 @@ SSL_LISTENER *make_ssl_structure(CONFIG_CONTEXT *obj, bool require_cert, int *er */ bool config_have_required_ssl_params(CONFIG_CONTEXT *obj); +/** + * @brief Add non-standard module type parameters to a JSON object + * + * @param mod Module whose parameters are inspected + * @param parameters List of configuration parameters for the module + * @param type_params NULL terminated list of default module type parameters + * @param output Output JSON object where the parameters are added + */ +void config_add_module_params_json(const MXS_MODULE* mod, + MXS_CONFIG_PARAMETER* parameters, + const char** type_params, + json_t* output); + +/** + * @brief Convert section names to new format + * + * @param section Section name to fix + */ +void fix_section_name(char *section); + MXS_END_DECLS diff --git a/server/core/maxscale/config_runtime.h b/server/core/maxscale/config_runtime.h index b24b6d3cb..d5473269d 100644 --- a/server/core/maxscale/config_runtime.h +++ b/server/core/maxscale/config_runtime.h @@ -89,7 +89,7 @@ bool runtime_unlink_server(SERVER *server, const char *target); * @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); +bool runtime_alter_server(SERVER *server, const char *key, const char *value); /** * @brief Enable SSL for a server @@ -118,7 +118,18 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert * @param value New value * @return True if @c key was one of the supported parameters */ -bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value); +bool runtime_alter_monitor(MXS_MONITOR *monitor, const char *key, const char *value); + +/** + * @brief Alter service parameters + * + * @param monitor Service 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_service(SERVICE *service, const char* zKey, const char* zValue); /** * @brief Create a new listener for a service @@ -179,4 +190,52 @@ bool runtime_create_monitor(const char *name, const char *module); */ bool runtime_destroy_monitor(MXS_MONITOR *monitor); +/** + * @brief Create a new server from JSON + * + * @param json JSON defining the server + * + * @return Created server or NULL on error + */ +SERVER* runtime_create_server_from_json(json_t* json); + +/** + * @brief Alter a server using JSON + * + * @param server Server to alter + * @param new_json JSON definition of the updated server + * + * @return True if the server was successfully modified to represent @c new_json + */ +bool runtime_alter_server_from_json(SERVER* server, json_t* new_json); + +/** + * @brief Create a new monitor from JSON + * + * @param json JSON defining the monitor + * + * @return Created monitor or NULL on error + */ +MXS_MONITOR* runtime_create_monitor_from_json(json_t* json); + +/** + * @brief Alter a monitor using JSON + * + * @param monitor Monitor to alter + * @param new_json JSON definition of the updated monitor + * + * @return True if the monitor was successfully modified to represent @c new_json + */ +bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json); + +/** + * @brief Alter a service using JSON + * + * @param service Service to alter + * @param new_json JSON definition of the updated service + * + * @return True if the service was successfully modified to represent @c new_json + */ +bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json); + MXS_END_DECLS diff --git a/server/core/maxscale/http.hh b/server/core/maxscale/http.hh new file mode 100644 index 000000000..6a37e8664 --- /dev/null +++ b/server/core/maxscale/http.hh @@ -0,0 +1,36 @@ +#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/bsl11. + * + * 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 + +/** + * @brief Return the current HTTP-date + * + * @return The RFC 1123 compliant date + */ +static inline std::string http_get_date() +{ + time_t now = time(NULL); + struct tm tm; + char buf[200]; // Enough to store all dates + + gmtime_r(&now, &tm); + strftime(buf, sizeof(buf), "%a, %d %b %y %T GMT", &tm); + + return std::string(buf); +} diff --git a/server/core/maxscale/httprequest.hh b/server/core/maxscale/httprequest.hh new file mode 100644 index 000000000..6986d676a --- /dev/null +++ b/server/core/maxscale/httprequest.hh @@ -0,0 +1,177 @@ +#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/bsl11. + * + * 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 +#include + +#include +#include + +#include "http.hh" + +static int value_iterator(void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) +{ + std::pair* cmp = (std::pair*)cls; + + if (cmp->first == key) + { + cmp->second = value; + return MHD_NO; + } + + return MHD_YES; +} + +class HttpRequest +{ + HttpRequest(const HttpRequest&); + HttpRequest& operator = (const HttpRequest); +public: + /** + * @brief Parse a request + * + * @param request Request to parse + * + * @return Parsed statement or NULL if request is not valid + */ + HttpRequest(struct MHD_Connection *connection, std::string url, std::string method, json_t* data); + + ~HttpRequest(); + + /** + * @brief Return request verb type + * + * @return One of the HTTP verb values + */ + const std::string& get_verb() const + { + return m_verb; + } + + /** + * @brief Get header value + * + * @param header Header to get + * + * @return Header value or empty string if the header was not found + */ + std::string get_header(const std::string& header) const + { + std::pair p; + p.first = header; + + MHD_get_connection_values(m_connection, MHD_HEADER_KIND, + value_iterator, &p); + + return p.second; + } + + /** + * @brief Get option value + * + * @param header Option to get + * + * @return Option value or empty string if the option was not found + */ + std::string get_option(const std::string& option) const + { + std::pair p; + p.first = option; + + MHD_get_connection_values(m_connection, MHD_GET_ARGUMENT_KIND, + value_iterator, &p); + + return p.second; + } + + /** + * @brief Return request body + * + * @return Request body or empty string if no body is defined + */ + const std::string& get_json_str() const + { + return m_json_string; + } + + /** + * @brief Return raw JSON body + * + * @return Raw JSON body or NULL if no body is defined + */ + json_t* get_json() const + { + return m_json.get(); + } + + /** + * @brief Get complete request URI + * + * @return The complete request URI + */ + const std::string& get_uri() const + { + return m_resource; + } + + /** + * @brief Get URI part + * + * @param idx Zero indexed part number in URI + * + * @return The request URI part or empty string if no part was found + */ + const std::string uri_part(uint32_t idx) const + { + return m_resource_parts.size() > idx ? m_resource_parts[idx] : ""; + } + + /** + * @brief Return how many parts are in the URI + * + * @return Number of URI parts + */ + size_t uri_part_count() const + { + return m_resource_parts.size(); + } + + const char* host() const + { + return m_hostname.c_str(); + } +private: + + /** Constants */ + static const std::string HTTP_PREFIX; + static const std::string HTTPS_PREFIX; + + std::map m_options; /**< Request options */ + mxs::Closer m_json; /**< Request body */ + std::string m_json_string; /**< String version of @c m_json */ + std::string m_resource; /**< Requested resource */ + std::deque m_resource_parts; /**< @c m_resource split into parts */ + std::string m_verb; /**< Request method */ + std::string m_hostname; /**< The value of the Host header */ + struct MHD_Connection* m_connection; +}; diff --git a/server/core/maxscale/httpresponse.hh b/server/core/maxscale/httpresponse.hh new file mode 100644 index 000000000..3bf96672d --- /dev/null +++ b/server/core/maxscale/httpresponse.hh @@ -0,0 +1,91 @@ +#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/bsl11. + * + * 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 + +#include "http.hh" + +/** + * A list of default headers that are generated with each response + */ +#define HTTP_RESPONSE_HEADER_DATE "Date" +#define HTTP_RESPONSE_HEADER_LAST_MODIFIED "Last-Modified" +#define HTTP_RESPONSE_HEADER_ETAG "ETag" +#define HTTP_RESPONSE_HEADER_ACCEPT "Accept" + +typedef std::map Headers; + +class HttpResponse +{ +public: + /** + * @brief Create new HTTP response + * + * @param response Response body + * @param code HTTP return code + */ + HttpResponse(int code = MHD_HTTP_OK, json_t* response = NULL); + HttpResponse(const HttpResponse& response); + HttpResponse& operator = (const HttpResponse& response); + + ~HttpResponse(); + + /** + * @brief Get the response body + * + * @return The response body + */ + json_t* get_response() const; + + /** + * @brief Drop response body + * + * This discards the message body. + */ + void drop_response(); + + /** + * @brief Get the HTTP response code + * + * @return The HTTP response code + */ + int get_code() const; + + /** + * @brief Add an extra header to this response + * + * @param key Header name + * @param value Header value + */ + void add_header(const std::string& key, const std::string& value); + + /** + * @brief Get request headers + * + * @return Headers of this request + */ + const Headers& get_headers() const; + +private: + json_t* m_body; /**< Message body */ + int m_code; /**< The HTTP code for the response */ + Headers m_headers; /**< Extra headers */ +}; diff --git a/server/core/maxscale/messagequeue.hh b/server/core/maxscale/messagequeue.hh index 32a938358..7aa2cd9ad 100644 --- a/server/core/maxscale/messagequeue.hh +++ b/server/core/maxscale/messagequeue.hh @@ -39,7 +39,7 @@ public: * @param arg1 First argument. * @param arg2 Second argument. */ - explicit MessageQueueMessage(uint32_t id = 0, intptr_t arg1 = 0, intptr_t arg2 = 0) + explicit MessageQueueMessage(uint64_t id = 0, intptr_t arg1 = 0, intptr_t arg2 = 0) : m_id(id) , m_arg1(arg1) , m_arg2(arg2) @@ -61,7 +61,7 @@ public: return m_arg2; } - MessageQueueMessage& set_id(uint32_t id) + MessageQueueMessage& set_id(uint64_t id) { m_id = id; return *this; @@ -80,7 +80,7 @@ public: } private: - uint32_t m_id; + uint64_t m_id; intptr_t m_arg1; intptr_t m_arg2; }; @@ -116,6 +116,21 @@ public: typedef MessageQueueHandler Handler; typedef MessageQueueMessage Message; + /** + * Initializes the message queue mechanism. To be called once at + * process startup. + * + * @return True if the initialization succeeded, false otherwise. + */ + static bool init(); + + + /** + * Finalizes the message queue mechanism. To be called once at + * process shutdown, if the initialization succeeded. + */ + static void finish(); + /** * Creates a @c MessageQueue with the provided handler. * diff --git a/server/core/maxscale/modules.h b/server/core/maxscale/modules.h index 95cfd8923..1eecff5fa 100644 --- a/server/core/maxscale/modules.h +++ b/server/core/maxscale/modules.h @@ -63,7 +63,7 @@ void *load_module(const char *module, const char *type); * @brief Get a module * * @param name Name of the module - * @param type The module type + * @param type The module type or NULL for any type * @return The loaded module or NULL if the module is not loaded */ const MXS_MODULE *get_module(const char *name, const char *type); @@ -161,4 +161,12 @@ bool mxs_module_iterator_has_next(const MXS_MODULE_ITERATOR* iterator); */ MXS_MODULE* mxs_module_iterator_get_next(MXS_MODULE_ITERATOR* iterator); +/** + * @brief Convert all modules to JSON + * + * @param host The hostname of this server + * @return Array of modules in JSON format + */ +json_t* module_list_to_json(const char* host); + MXS_END_DECLS diff --git a/server/core/maxscale/monitor.h b/server/core/maxscale/monitor.h index d71f88411..938c03d5d 100644 --- a/server/core/maxscale/monitor.h +++ b/server/core/maxscale/monitor.h @@ -58,29 +58,13 @@ RESULTSET *monitorGetList(); bool monitorAddServer(MXS_MONITOR *mon, SERVER *server); void monitorRemoveServer(MXS_MONITOR *mon, SERVER *server); -void monitorAddUser(MXS_MONITOR *, char *, char *); +void monitorAddUser(MXS_MONITOR *, const char *, const char *); void monitorAddParameters(MXS_MONITOR *monitor, MXS_CONFIG_PARAMETER *params); bool monitorRemoveParameter(MXS_MONITOR *monitor, const char *key); void monitorSetInterval (MXS_MONITOR *, unsigned long); bool monitorSetNetworkTimeout(MXS_MONITOR *, int, int); -/** - * @brief Serialize the servers of a monitor to a file - * - * This partially converts @c monitor into an INI format file. Only the servers - * of the monitor are serialized. This allows the monitor to keep monitoring - * the servers that were added at runtime even after a restart. - * - * NOTE: This does not persist the complete monitor configuration and requires - * that an existing monitor configuration is in the main configuration file. - * Changes to monitor parameters are not persisted. - * - * @param monitor Monitor to serialize - * @return False if the serialization of the monitor fails, true if it was successful - */ -bool monitor_serialize_servers(const MXS_MONITOR *monitor); - /** * @brief Serialize a monitor to a file * diff --git a/server/core/maxscale/poll.h b/server/core/maxscale/poll.h index 7b1dcae7b..f5e3527cc 100644 --- a/server/core/maxscale/poll.h +++ b/server/core/maxscale/poll.h @@ -61,6 +61,4 @@ void dShowEventStats(DCB *dcb); int poll_get_stat(POLL_STAT stat); RESULTSET *eventTimesGetList(); -void poll_send_message(enum poll_message msg, void *data); - MXS_END_DECLS diff --git a/server/core/maxscale/resource.hh b/server/core/maxscale/resource.hh new file mode 100644 index 000000000..b3e80498d --- /dev/null +++ b/server/core/maxscale/resource.hh @@ -0,0 +1,76 @@ +#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/bsl11. + * + * 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 REST API resources */ + +#include + +#include +#include + +#include + +#include "http.hh" +#include "httprequest.hh" +#include "httpresponse.hh" +#include "monitor.h" +#include "service.h" +#include "filter.h" +#include "session.h" + +typedef HttpResponse (*ResourceCallback)(const HttpRequest& request); + +class Resource +{ + Resource(const Resource&); + Resource& operator = (const Resource&); +public: + + Resource(ResourceCallback cb, int components, ...); + ~Resource(); + + /** + * @brief Check if a request matches this resource + * + * @param request Request to match + * + * @return True if this request matches this resource + */ + bool match(const HttpRequest& request) const; + + /** + * @brief Handle a HTTP request + * + * @param request Request to handle + * + * @return Response to the request + */ + HttpResponse call(const HttpRequest& request) const; + +private: + + bool matching_variable_path(const std::string& path, const std::string& target) const; + + ResourceCallback m_cb; /**< Resource handler callback */ + std::deque m_path; /**< Path components */ +}; + +/** + * @brief Handle a HTTP request + * + * @param request Request to handle + * + * @return Response to request + */ +HttpResponse resource_handle_request(const HttpRequest& request); diff --git a/server/core/maxscale/service.h b/server/core/maxscale/service.h index f8288e2bb..b8347fb1b 100644 --- a/server/core/maxscale/service.h +++ b/server/core/maxscale/service.h @@ -82,18 +82,15 @@ void serviceRemoveBackend(SERVICE *service, 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. + * This converts @c service into an INI format file. * * 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); +bool service_serialize(const SERVICE *service); /** * Internal utility functions diff --git a/server/core/maxscale/worker.hh b/server/core/maxscale/worker.hh index 35056713c..e1822d498 100644 --- a/server/core/maxscale/worker.hh +++ b/server/core/maxscale/worker.hh @@ -80,6 +80,12 @@ public: ZPROCESSING }; + enum execute_mode_t + { + EXECUTE_AUTO, /**< Execute tasks immediately */ + EXECUTE_QUEUED /**< Only queue tasks for execution */ + }; + /** * Initialize the worker mechanism. * @@ -258,6 +264,7 @@ public: * * @param pTask The task to be executed. * @param pSem If non-NULL, will be posted once the task's `execute` return. + * @param mode Execution mode * * @return True if the task could be posted (i.e. not executed), false otherwise. * @@ -277,18 +284,19 @@ public: * MyResult& result = task.result(); * @endcode */ - bool post(Task* pTask, Semaphore* pSem = NULL); + bool post(Task* pTask, Semaphore* pSem = NULL, enum execute_mode_t mode = EXECUTE_AUTO); /** * Posts a task to a worker for execution. * * @param pTask The task to be executed. + * @param mode Execution mode * * @return True if the task could be posted (i.e. not executed), false otherwise. * * @attention Once the task has been executed, it will be deleted. */ - bool post(std::auto_ptr sTask); + bool post(std::auto_ptr sTask, enum execute_mode_t mode = EXECUTE_AUTO); /** * Posts a task to all workers for execution. @@ -442,7 +450,7 @@ private: static Worker* create(int id, int epoll_listener_fd); - bool post_disposable(DisposableTask* pTask); + bool post_disposable(DisposableTask* pTask, enum execute_mode_t mode = EXECUTE_AUTO); void handle_message(MessageQueue& queue, const MessageQueue::Message& msg); // override diff --git a/server/core/maxscale/workertask.hh b/server/core/maxscale/workertask.hh index 483903470..ee05aa620 100644 --- a/server/core/maxscale/workertask.hh +++ b/server/core/maxscale/workertask.hh @@ -47,11 +47,29 @@ public: * When the task has been executed, the instance will automatically be * deleted. */ -class WorkerDisposableTask : public WorkerTask +class WorkerDisposableTask { protected: + /** + * Constructor + */ WorkerDisposableTask(); + /** + * Destructor + */ + virtual ~WorkerDisposableTask(); + + /** + * @brief Called in the context of a specific worker. + * + * @param worker The worker in whose context `execute` is called. + * + * @attention As the function is called by a worker, the body of `execute` + * should execute quickly and not perform any blocking operations. + */ + virtual void execute(Worker& worker) = 0; + private: friend class Worker; diff --git a/server/core/messagequeue.cc b/server/core/messagequeue.cc index 1b9db0996..c6680c72a 100644 --- a/server/core/messagequeue.cc +++ b/server/core/messagequeue.cc @@ -12,14 +12,31 @@ */ #include "maxscale/messagequeue.hh" +#include #include #include #include #include +#include #include #include #include "maxscale/worker.hh" +namespace +{ + +struct +{ + bool initialized; + int pipe_flags; +} this_unit = +{ + false, + O_NONBLOCK | O_CLOEXEC +}; + +} + namespace maxscale { @@ -46,17 +63,89 @@ MessageQueue::~MessageQueue() close(m_write_fd); } +//static +bool MessageQueue::init() +{ + ss_dassert(!this_unit.initialized); + + /* From "man 7 pipe" + * ---- + * + * O_NONBLOCK enabled, n <= PIPE_BUF + * If there is room to write n bytes to the pipe, then write(2) + * succeeds immediately, writing all n bytes; otherwise write(2) + * fails, with errno set to EAGAIN. + * + * ... (On Linux, PIPE_BUF is 4096 bytes.) + * + * ---- + * + * As O_NONBLOCK is set and the messages are less than 4096 bytes, + * O_DIRECT should not be needed and we should be safe without it. + * + * However, to be in the safe side, if we run on kernel version >= 3.4 + * we use it. + */ + + utsname u; + + if (uname(&u) == 0) + { + char* p; + char* zMajor = strtok_r(u.release, ".", &p); + char* zMinor = strtok_r(NULL, ".", &p); + + if (zMajor && zMinor) + { + int major = atoi(zMajor); + int minor = atoi(zMinor); + + if (major >= 3 && minor >= 4) + { + // O_DIRECT for pipes is supported from kernel 3.4 onwards. + this_unit.pipe_flags |= O_DIRECT; + } + else + { + MXS_NOTICE("O_DIRECT is not supported for pipes on Linux kernel %s " + "(supported from version 3.4 onwards), NOT using it.", + u.release); + } + } + else + { + MXS_WARNING("Syntax used in utsname.release seems to have changed, " + "not able to figure out current kernel version. Assuming " + "O_DIRECT is not supported for pipes."); + } + } + else + { + MXS_WARNING("uname() failed, assuming O_DIRECT is not supported for pipes: %s", + mxs_strerror(errno)); + } + + this_unit.initialized = true; + + return this_unit.initialized; +} + +//static +void MessageQueue::finish() +{ + ss_dassert(this_unit.initialized); + this_unit.initialized = false; +} + //static MessageQueue* MessageQueue::create(Handler* pHandler) { + ss_dassert(this_unit.initialized); + MessageQueue* pThis = NULL; - // We create the pipe in message mode (O_DIRECT), so that we do - // not need to deal with partial messages and as non blocking so - // that the descriptor can be added to an epoll instance. - int fds[2]; - if (pipe2(fds, O_DIRECT | O_NONBLOCK | O_CLOEXEC) == 0) + if (pipe2(fds, this_unit.pipe_flags) == 0) { int read_fd = fds[0]; int write_fd = fds[1]; diff --git a/server/core/monitor.cc b/server/core/monitor.cc index 03ec9cf8d..9f2299ff1 100644 --- a/server/core/monitor.cc +++ b/server/core/monitor.cc @@ -13,19 +13,6 @@ /** * @file monitor.c - The monitor module management routines - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/07/13 Mark Riddoch Initial implementation - * 23/05/14 Massimiliano Pinto Addition of monitor_interval parameter - * and monitor id - * 30/10/14 Massimiliano Pinto Addition of disable_master_failback parameter - * 07/11/14 Massimiliano Pinto Addition of monitor network timeouts - * 08/05/15 Markus Makela Moved common monitor variables to MONITOR struct - * - * @endverbatim */ #include @@ -33,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -48,6 +37,17 @@ #include "maxscale/monitor.h" #include "maxscale/modules.h" +using std::string; +using std::set; + +const char CN_BACKEND_CONNECT_ATTEMPTS[] = "backend_connect_attempts"; +const char CN_BACKEND_READ_TIMEOUT[] = "backend_read_timeout"; +const char CN_BACKEND_WRITE_TIMEOUT[] = "backend_write_timeout"; +const char CN_BACKEND_CONNECT_TIMEOUT[] = "backend_connect_timeout"; +const char CN_MONITOR_INTERVAL[] = "monitor_interval"; +const char CN_SCRIPT[] = "script"; +const char CN_EVENTS[] = "events"; + static MXS_MONITOR *allMonitors = NULL; static SPINLOCK monLock = SPINLOCK_INIT; @@ -164,18 +164,21 @@ monitor_free(MXS_MONITOR *mon) void monitorStart(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params) { - spinlock_acquire(&monitor->lock); - - if ((monitor->handle = (*monitor->module->startMonitor)(monitor, params))) + if (monitor) { - monitor->state = MONITOR_STATE_RUNNING; - } - else - { - MXS_ERROR("Failed to start monitor '%s'.", monitor->name); - } + spinlock_acquire(&monitor->lock); - spinlock_release(&monitor->lock); + if ((monitor->handle = (*monitor->module->startMonitor)(monitor, params))) + { + monitor->state = MONITOR_STATE_RUNNING; + } + else + { + MXS_ERROR("Failed to start monitor '%s'.", monitor->name); + } + + spinlock_release(&monitor->lock); + } } /** @@ -203,26 +206,29 @@ void monitorStartAll() void monitorStop(MXS_MONITOR *monitor) { - spinlock_acquire(&monitor->lock); - - /** Only stop the monitor if it is running */ - if (monitor->state == MONITOR_STATE_RUNNING) + if (monitor) { - monitor->state = MONITOR_STATE_STOPPING; - monitor->module->stopMonitor(monitor); - monitor->state = MONITOR_STATE_STOPPED; + spinlock_acquire(&monitor->lock); - MXS_MONITOR_SERVERS* db = monitor->databases; - while (db) + /** Only stop the monitor if it is running */ + if (monitor->state == MONITOR_STATE_RUNNING) { - // TODO: Create a generic entry point for this or move it inside stopMonitor - mysql_close(db->con); - db->con = NULL; - db = db->next; - } - } + monitor->state = MONITOR_STATE_STOPPING; + monitor->module->stopMonitor(monitor); + monitor->state = MONITOR_STATE_STOPPED; - spinlock_release(&monitor->lock); + MXS_MONITOR_SERVERS* db = monitor->databases; + while (db) + { + // TODO: Create a generic entry point for this or move it inside stopMonitor + mysql_close(db->con); + db->con = NULL; + db = db->next; + } + } + + spinlock_release(&monitor->lock); + } } /** @@ -393,7 +399,7 @@ void monitorRemoveServer(MXS_MONITOR *mon, SERVER *server) * @param passwd The default password associated to the default user. */ void -monitorAddUser(MXS_MONITOR *mon, char *user, char *passwd) +monitorAddUser(MXS_MONITOR *mon, const char *user, const char *passwd) { if (user != mon->user) { @@ -997,7 +1003,7 @@ bool mon_status_changed(MXS_MONITOR_SERVERS* mon_srv) bool rval = false; /* Previous status is -1 if not yet set */ - if (mon_srv->mon_prev_status != (uint32_t)-1) + if (mon_srv->mon_prev_status != static_cast(-1)) { unsigned int old_status = mon_srv->mon_prev_status & all_server_bits; @@ -1273,51 +1279,6 @@ MXS_MONITOR* monitor_server_in_use(const SERVER *server) return rval; } -/** - * Creates a monitor configuration at the location pointed by @c filename - * - * @param monitor Monitor to serialize into a configuration - * @param filename Filename where configuration is written - * @return True on success, false on error - */ -static bool create_monitor_server_config(const MXS_MONITOR *monitor, const char *filename) -{ - int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - if (file == -1) - { - MXS_ERROR("Failed to open file '%s' when serializing monitor '%s': %d, %s", - filename, monitor->name, errno, mxs_strerror(errno)); - return false; - } - - /** - * Only additional parameters are added to the configuration. This prevents - * duplication or addition of parameters that don't support it. - * - * TODO: Check for return values on all of the dprintf calls - */ - dprintf(file, "[%s]\n", monitor->name); - - if (monitor->databases) - { - dprintf(file, "servers="); - for (MXS_MONITOR_SERVERS *db = monitor->databases; db; db = db->next) - { - if (db != monitor->databases) - { - dprintf(file, ","); - } - dprintf(file, "%s", db->server->unique_name); - } - dprintf(file, "\n"); - } - - close(file); - - return true; -} - static bool create_monitor_config(const MXS_MONITOR *monitor, const char *filename) { int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); @@ -1336,56 +1297,35 @@ static bool create_monitor_config(const MXS_MONITOR *monitor, const char *filena * TODO: Check for return values on all of the dprintf calls */ dprintf(file, "[%s]\n", monitor->name); - dprintf(file, "type=monitor\n"); - dprintf(file, "module=%s\n", monitor->module_name); - dprintf(file, "user=%s\n", monitor->user); - dprintf(file, "password=%s\n", monitor->password); - dprintf(file, "monitor_interval=%lu\n", monitor->interval); - dprintf(file, "backend_connect_timeout=%d\n", monitor->connect_timeout); - dprintf(file, "backend_write_timeout=%d\n", monitor->write_timeout); - dprintf(file, "backend_read_timeout=%d\n", monitor->read_timeout); - dprintf(file, "%s=%d\n", BACKEND_CONNECT_ATTEMPTS, monitor->connect_attempts); + dprintf(file, "%s=monitor\n", CN_TYPE); + dprintf(file, "%s=%s\n", CN_MODULE, monitor->module_name); + dprintf(file, "%s=%s\n", CN_USER, monitor->user); + dprintf(file, "%s=%s\n", CN_PASSWORD, monitor->password); + dprintf(file, "%s=%lu\n", CN_MONITOR_INTERVAL, monitor->interval); + dprintf(file, "%s=%d\n", CN_BACKEND_CONNECT_TIMEOUT, monitor->connect_timeout); + dprintf(file, "%s=%d\n", CN_BACKEND_WRITE_TIMEOUT, monitor->write_timeout); + dprintf(file, "%s=%d\n", CN_BACKEND_READ_TIMEOUT, monitor->read_timeout); + dprintf(file, "%s=%d\n", CN_BACKEND_CONNECT_ATTEMPTS, monitor->connect_attempts); + + if (monitor->databases) + { + dprintf(file, "%s=", CN_SERVERS); + for (MXS_MONITOR_SERVERS *db = monitor->databases; db; db = db->next) + { + if (db != monitor->databases) + { + dprintf(file, ","); + } + dprintf(file, "%s", db->server->unique_name); + } + dprintf(file, "\n"); + } close(file); return true; } -bool monitor_serialize_servers(const MXS_MONITOR *monitor) -{ - bool rval = false; - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), - monitor->name); - - if (unlink(filename) == -1 && errno != ENOENT) - { - MXS_ERROR("Failed to remove temporary monitor configuration at '%s': %d, %s", - filename, errno, mxs_strerror(errno)); - } - else if (create_monitor_server_config(monitor, 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 - { - MXS_ERROR("Failed to rename temporary monitor configuration at '%s': %d, %s", - filename, errno, mxs_strerror(errno)); - } - } - - return rval; -} - bool monitor_serialize(const MXS_MONITOR *monitor) { bool rval = false; @@ -1508,3 +1448,149 @@ void mon_process_state_changes(MXS_MONITOR *monitor, const char *script, uint64_ } } } + +static const char* monitor_state_to_string(int state) +{ + switch (state) + { + case MONITOR_STATE_RUNNING: + return "Running"; + + case MONITOR_STATE_STOPPING: + return "Stopping"; + + case MONITOR_STATE_STOPPED: + return "Stopped"; + + case MONITOR_STATE_ALLOC: + return "Allocated"; + + default: + ss_dassert(false); + return "Unknown"; + } +} + +json_t* monitor_parameters_to_json(const MXS_MONITOR* monitor) +{ + json_t* rval = json_object(); + + json_object_set_new(rval, CN_USER, json_string(monitor->user)); + json_object_set_new(rval, CN_PASSWORD, json_string(monitor->password)); + json_object_set_new(rval, CN_MONITOR_INTERVAL, json_integer(monitor->interval)); + json_object_set_new(rval, CN_BACKEND_CONNECT_TIMEOUT, json_integer(monitor->connect_timeout)); + json_object_set_new(rval, CN_BACKEND_READ_TIMEOUT, json_integer(monitor->read_timeout)); + json_object_set_new(rval, CN_BACKEND_WRITE_TIMEOUT, json_integer(monitor->write_timeout)); + json_object_set_new(rval, CN_BACKEND_CONNECT_ATTEMPTS, json_integer(monitor->connect_attempts)); + + /** Add custom module parameters */ + const MXS_MODULE* mod = get_module(monitor->module_name, MODULE_MONITOR); + config_add_module_params_json(mod, monitor->parameters, config_monitor_params, rval); + + /** Don't show the default value for events if no script is defined */ + if (json_object_get(rval, CN_SCRIPT) == NULL) + { + json_object_del(rval, CN_EVENTS); + } + + return rval; +} + +json_t* monitor_to_json(const MXS_MONITOR* monitor, const char* host) +{ + json_t* rval = json_object(); + + json_object_set_new(rval, CN_NAME, json_string(monitor->name)); + json_object_set_new(rval, CN_MODULE, json_string(monitor->module_name)); + json_object_set_new(rval, CN_STATE, json_string(monitor_state_to_string(monitor->state))); + + /** Monitor parameters */ + json_object_set_new(rval, CN_PARAMETERS, monitor_parameters_to_json(monitor)); + + if (monitor->handle && monitor->module->diagnostics) + { + json_t* diag = monitor->module->diagnostics_json(monitor); + + if (diag) + { + json_object_set_new(rval, "monitor_diagnostics", diag); + } + } + + json_t* rel = json_object(); + + /** Store relationships to other objects */ + string self = host; + self += "/monitors/"; + self += monitor->name; + json_object_set_new(rel, CN_SELF, json_string(self.c_str())); + + if (monitor->databases) + { + json_t* arr = json_array(); + + for (MXS_MONITOR_SERVERS *db = monitor->databases; db; db = db->next) + { + string s = host; + s += "/servers/"; + s += db->server->unique_name; + json_array_append_new(arr, json_string(s.c_str())); + } + + json_object_set_new(rel, "servers", arr); + } + + json_object_set_new(rval, "relationships", rel); + + return rval; +} + +json_t* monitor_list_to_json(const char* host) +{ + json_t* rval = json_array(); + + spinlock_acquire(&monLock); + + for (MXS_MONITOR* mon = allMonitors; mon; mon = mon->next) + { + json_t *json = monitor_to_json(mon, host); + + if (json) + { + json_array_append_new(rval, json); + } + } + + spinlock_release(&monLock); + + return rval; +} + +json_t* monitor_relations_to_server(const SERVER* server, const char* host) +{ + json_t* arr = json_array(); + + spinlock_acquire(&monLock); + + for (MXS_MONITOR* mon = allMonitors; mon; mon = mon->next) + { + spinlock_acquire(&mon->lock); + + for (MXS_MONITOR_SERVERS* db = mon->databases; db; db = db->next) + { + if (db->server == server) + { + string m = host; + m += "/monitors/"; + m += mon->name; + json_array_append_new(arr, json_string(m.c_str())); + } + } + + spinlock_release(&mon->lock); + } + + spinlock_release(&monLock); + + return arr; +} diff --git a/server/core/poll.cc b/server/core/poll.cc index 93d017348..d1c20207d 100644 --- a/server/core/poll.cc +++ b/server/core/poll.cc @@ -43,12 +43,6 @@ using maxscale::Worker; static int next_epoll_fd = 0; /*< Which thread handles the next DCB */ static int n_threads; /*< Number of threads */ - -/** Poll cross-thread messaging variables */ -static volatile int *poll_msg; -static void *poll_msg_data = NULL; -static SPINLOCK poll_msg_lock = SPINLOCK_INIT; - /** * Initialise the polling system we are using for the gateway. * @@ -58,11 +52,6 @@ void poll_init() { n_threads = config_threadcount(); - - if ((poll_msg = (int*)MXS_CALLOC(n_threads, sizeof(int))) == NULL) - { - exit(-1); - } } static bool add_fd_to_worker(int wid, int fd, uint32_t events, MXS_POLL_DATA* data) @@ -422,45 +411,3 @@ eventTimesGetList() return set; } - -void poll_send_message(enum poll_message msg, void *data) -{ - spinlock_acquire(&poll_msg_lock); - int nthr = config_threadcount(); - poll_msg_data = data; - - for (int i = 0; i < nthr; i++) - { - poll_msg[i] |= msg; - } - - /** Handle this thread's message */ - poll_check_message(); - - for (int i = 0; i < nthr; i++) - { - if (i != Worker::get_current_id()) - { - while (poll_msg[i] & msg) - { - thread_millisleep(1); - } - } - } - - poll_msg_data = NULL; - spinlock_release(&poll_msg_lock); -} - -void poll_check_message() -{ - int thread_id = Worker::get_current_id(); - - if (poll_msg[thread_id] & POLL_MSG_CLEAN_PERSISTENT) - { - SERVER *server = (SERVER*)poll_msg_data; - dcb_persistent_clean_count(server->persistent[thread_id], thread_id, false); - atomic_synchronize(); - poll_msg[thread_id] &= ~POLL_MSG_CLEAN_PERSISTENT; - } -} diff --git a/server/core/resource.cc b/server/core/resource.cc new file mode 100644 index 000000000..8af160e1e --- /dev/null +++ b/server/core/resource.cc @@ -0,0 +1,577 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 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 "maxscale/resource.hh" + +#include +#include + +#include +#include +#include + +#include "maxscale/httprequest.hh" +#include "maxscale/httpresponse.hh" +#include "maxscale/session.h" +#include "maxscale/filter.h" +#include "maxscale/monitor.h" +#include "maxscale/service.h" +#include "maxscale/config_runtime.h" +#include "maxscale/modules.h" + +using std::list; +using std::string; +using std::stringstream; +using mxs::SpinLock; +using mxs::SpinLockGuard; + +Resource::Resource(ResourceCallback cb, int components, ...) : + m_cb(cb) +{ + va_list args; + va_start(args, components); + + for (int i = 0; i < components; i++) + { + string part = va_arg(args, const char*); + m_path.push_back(part); + } + va_end(args); +} + +Resource::~Resource() +{ +} + +bool Resource::match(const HttpRequest& request) const +{ + bool rval = false; + + if (request.uri_part_count() == m_path.size()) + { + rval = true; + + for (size_t i = 0; i < request.uri_part_count(); i++) + { + if (m_path[i] != request.uri_part(i) && + !matching_variable_path(m_path[i], request.uri_part(i))) + { + rval = false; + break; + } + } + } + + return rval; +} + +HttpResponse Resource::call(const HttpRequest& request) const +{ + return m_cb(request); +}; + +bool Resource::matching_variable_path(const string& path, const string& target) const +{ + bool rval = false; + + if (path[0] == ':') + { + if ((path == ":service" && service_find(target.c_str())) || + (path == ":server" && server_find_by_unique_name(target.c_str())) || + (path == ":filter" && filter_def_find(target.c_str())) || + (path == ":monitor" && monitor_find(target.c_str()))) + { + rval = true; + } + else if (path == ":session") + { + size_t id = atoi(target.c_str()); + MXS_SESSION* ses = session_get_by_id(id); + + if (ses) + { + session_put_ref(ses); + rval = true; + } + } + } + + return rval; +} + +HttpResponse cb_stop_monitor(const HttpRequest& request) +{ + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + monitorStop(monitor); + return HttpResponse(MHD_HTTP_NO_CONTENT); +} + +HttpResponse cb_start_monitor(const HttpRequest& request) +{ + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + monitorStart(monitor, monitor->parameters); + return HttpResponse(MHD_HTTP_NO_CONTENT); +} + +HttpResponse cb_stop_service(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + serviceStop(service); + return HttpResponse(MHD_HTTP_NO_CONTENT); +} + +HttpResponse cb_start_service(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + serviceStart(service); + return HttpResponse(MHD_HTTP_NO_CONTENT); +} + +HttpResponse cb_create_server(const HttpRequest& request) +{ + json_t* json = request.get_json(); + + if (json) + { + SERVER* server = runtime_create_server_from_json(json); + + if (server) + { + return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host())); + } + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_alter_server(const HttpRequest& request) +{ + json_t* json = request.get_json(); + + if (json) + { + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + + if (server && runtime_alter_server_from_json(server, json)) + { + return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host())); + } + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_create_monitor(const HttpRequest& request) +{ + json_t* json = request.get_json(); + + if (json) + { + MXS_MONITOR* monitor = runtime_create_monitor_from_json(json); + + if (monitor) + { + return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host())); + } + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_alter_monitor(const HttpRequest& request) +{ + json_t* json = request.get_json(); + + if (json) + { + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + + if (monitor && runtime_alter_monitor_from_json(monitor, json)) + { + return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host())); + } + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_alter_service(const HttpRequest& request) +{ + json_t* json = request.get_json(); + + if (json) + { + SERVICE* service = service_find(request.uri_part(1).c_str()); + + if (service && runtime_alter_service_from_json(service, json)) + { + return HttpResponse(MHD_HTTP_OK, service_to_json(service, request.host())); + } + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_delete_server(const HttpRequest& request) +{ + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + + if (server && runtime_destroy_server(server)) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_delete_monitor(const HttpRequest& request) +{ + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + + if (monitor && runtime_destroy_monitor(monitor)) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_BAD_REQUEST); +} + +HttpResponse cb_all_servers(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, server_list_to_json(request.host())); +} + +HttpResponse cb_get_server(const HttpRequest& request) +{ + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + + if (server) + { + return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host())); + } + + return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR); +} + +HttpResponse cb_all_services(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, service_list_to_json(request.host())); +} + +HttpResponse cb_get_service(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + + if (service) + { + return HttpResponse(MHD_HTTP_OK, service_to_json(service, request.host())); + } + + return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR); +} + +HttpResponse cb_get_service_listeners(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + json_t* json = service_to_json(service, request.host()); + + // The 'listeners' key is always defined + json_t* listeners = json_incref(json_object_get(json, CN_LISTENERS)); + ss_dassert(listeners); + + json_decref(json); + + return HttpResponse(MHD_HTTP_OK, listeners); +} + +HttpResponse cb_all_filters(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, filter_list_to_json(request.host())); +} + +HttpResponse cb_get_filter(const HttpRequest& request) +{ + MXS_FILTER_DEF* filter = filter_def_find(request.uri_part(1).c_str()); + + if (filter) + { + return HttpResponse(MHD_HTTP_OK, filter_to_json(filter, request.host())); + } + + return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR); +} + +HttpResponse cb_all_monitors(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, monitor_list_to_json(request.host())); +} + +HttpResponse cb_get_monitor(const HttpRequest& request) +{ + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + + if (monitor) + { + return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host())); + } + + return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR); +} + +HttpResponse cb_all_sessions(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host())); +} + +HttpResponse cb_get_session(const HttpRequest& request) +{ + int id = atoi(request.uri_part(1).c_str()); + MXS_SESSION* session = session_get_by_id(id); + + if (session) + { + json_t* json = session_to_json(session, request.host()); + session_put_ref(session); + return HttpResponse(MHD_HTTP_OK, json); + } + + return HttpResponse(MHD_HTTP_NOT_FOUND); +} + +HttpResponse cb_maxscale(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, config_paths_to_json(request.host())); +} + +HttpResponse cb_logs(const HttpRequest& request) +{ + // TODO: Show logs + return HttpResponse(MHD_HTTP_OK); +} + +HttpResponse cb_flush(const HttpRequest& request) +{ + // Flush logs + if (mxs_log_rotate() == 0) + { + return HttpResponse(MHD_HTTP_OK); + } + else + { + return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR); + } +} + +HttpResponse cb_threads(const HttpRequest& request) +{ + // TODO: Show thread status + return HttpResponse(MHD_HTTP_OK); +} + +HttpResponse cb_tasks(const HttpRequest& request) +{ + // TODO: Show housekeeper tasks + return HttpResponse(MHD_HTTP_OK); +} + +HttpResponse cb_all_modules(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK, module_list_to_json(request.host())); +} + +HttpResponse cb_send_ok(const HttpRequest& request) +{ + return HttpResponse(MHD_HTTP_OK); +} + +class RootResource +{ + RootResource(const RootResource&); + RootResource& operator=(const RootResource&); +public: + typedef std::shared_ptr SResource; + typedef list ResourceList; + + RootResource() + { + // Special resources required by OPTION etc. + m_get.push_back(SResource(new Resource(cb_send_ok, 1, "/"))); + m_get.push_back(SResource(new Resource(cb_send_ok, 1, "*"))); + + m_get.push_back(SResource(new Resource(cb_all_servers, 1, "servers"))); + m_get.push_back(SResource(new Resource(cb_get_server, 2, "servers", ":server"))); + + m_get.push_back(SResource(new Resource(cb_all_services, 1, "services"))); + m_get.push_back(SResource(new Resource(cb_get_service, 2, "services", ":service"))); + m_get.push_back(SResource(new Resource(cb_get_service_listeners, 3, + "services", ":service", "listeners"))); + + m_get.push_back(SResource(new Resource(cb_all_filters, 1, "filters"))); + m_get.push_back(SResource(new Resource(cb_get_filter, 2, "filters", ":filter"))); + + m_get.push_back(SResource(new Resource(cb_all_monitors, 1, "monitors"))); + m_get.push_back(SResource(new Resource(cb_get_monitor, 2, "monitors", ":monitor"))); + + m_get.push_back(SResource(new Resource(cb_all_sessions, 1, "sessions"))); + m_get.push_back(SResource(new Resource(cb_get_session, 2, "sessions", ":session"))); + + m_get.push_back(SResource(new Resource(cb_maxscale, 1, "maxscale"))); + m_get.push_back(SResource(new Resource(cb_threads, 2, "maxscale", "threads"))); + m_get.push_back(SResource(new Resource(cb_logs, 2, "maxscale", "logs"))); + m_get.push_back(SResource(new Resource(cb_tasks, 2, "maxscale", "tasks"))); + m_get.push_back(SResource(new Resource(cb_all_modules, 2, "maxscale", "modules"))); + + /** Create new resources */ + m_post.push_back(SResource(new Resource(cb_flush, 3, "maxscale", "logs", "flush"))); + m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers"))); + m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors"))); + + /** Update resources */ + m_put.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server"))); + m_put.push_back(SResource(new Resource(cb_alter_monitor, 2, "monitors", ":monitor"))); + m_put.push_back(SResource(new Resource(cb_alter_service, 2, "services", ":service"))); + + /** Change resource states */ + m_put.push_back(SResource(new Resource(cb_stop_monitor, 3, "monitors", ":monitor", "stop"))); + m_put.push_back(SResource(new Resource(cb_start_monitor, 3, "monitors", ":monitor", "start"))); + m_put.push_back(SResource(new Resource(cb_stop_service, 3, "services", ":service", "stop"))); + m_put.push_back(SResource(new Resource(cb_start_service, 3, "services", ":service", "start"))); + + m_delete.push_back(SResource(new Resource(cb_delete_server, 2, "servers", ":server"))); + m_delete.push_back(SResource(new Resource(cb_delete_monitor, 2, "monitors", ":monitor"))); + } + + ~RootResource() + { + } + + ResourceList::const_iterator find_resource(const ResourceList& list, const HttpRequest& request) const + { + for (ResourceList::const_iterator it = list.begin(); it != list.end(); it++) + { + Resource& r = *(*it); + + if (r.match(request)) + { + return it; + } + } + + return list.end(); + } + + HttpResponse process_request_type(const ResourceList& list, const HttpRequest& request) + { + ResourceList::const_iterator it = find_resource(list, request); + + if (it != list.end()) + { + Resource& r = *(*it); + return r.call(request); + } + + return HttpResponse(MHD_HTTP_NOT_FOUND); + } + + string get_supported_methods(const HttpRequest& request) + { + list l; + + if (find_resource(m_get, request) != m_get.end()) + { + l.push_back(MHD_HTTP_METHOD_GET); + } + if (find_resource(m_put, request) != m_put.end()) + { + l.push_back(MHD_HTTP_METHOD_PUT); + } + if (find_resource(m_post, request) != m_post.end()) + { + l.push_back(MHD_HTTP_METHOD_POST); + } + if (find_resource(m_delete, request) != m_delete.end()) + { + l.push_back(MHD_HTTP_METHOD_DELETE); + } + + stringstream rval; + + if (l.size() > 0) + { + rval << l.front(); + l.pop_front(); + } + + for (list::iterator it = l.begin(); it != l.end(); it++) + { + rval << ", " << *it; + } + + return rval.str(); + } + + HttpResponse process_request(const HttpRequest& request) + { + if (request.get_verb() == MHD_HTTP_METHOD_GET) + { + return process_request_type(m_get, request); + } + else if (request.get_verb() == MHD_HTTP_METHOD_PUT) + { + return process_request_type(m_put, request); + } + else if (request.get_verb() == MHD_HTTP_METHOD_POST) + { + return process_request_type(m_post, request); + } + else if (request.get_verb() == MHD_HTTP_METHOD_DELETE) + { + return process_request_type(m_delete, request); + } + else if (request.get_verb() == MHD_HTTP_METHOD_OPTIONS) + { + string methods = get_supported_methods(request); + + if (methods.size() > 0) + { + HttpResponse response(MHD_HTTP_OK); + response.add_header(HTTP_RESPONSE_HEADER_ACCEPT, methods); + return response; + } + } + else if (request.get_verb() == MHD_HTTP_METHOD_HEAD) + { + /** Do a GET and just drop the body of the response */ + HttpResponse response = process_request_type(m_get, request); + response.drop_response(); + return response; + } + + return HttpResponse(MHD_HTTP_METHOD_NOT_ALLOWED); + } + +private: + + ResourceList m_get; /**< GET request handlers */ + ResourceList m_put; /**< PUT request handlers */ + ResourceList m_post; /**< POST request handlers */ + ResourceList m_delete; /**< DELETE request handlers */ +}; + +static RootResource resources; /**< Core resource set */ +static SpinLock resource_lock; + +HttpResponse resource_handle_request(const HttpRequest& request) +{ + SpinLockGuard guard(resource_lock); + return resources.process_request(request); +} diff --git a/server/core/server.cc b/server/core/server.cc index 11a905af4..fdb0f2634 100644 --- a/server/core/server.cc +++ b/server/core/server.cc @@ -14,23 +14,6 @@ /** * @file server.c - A representation of a backend server within the gateway. * - * @verbatim - * Revision History - * - * Date Who Description - * 18/06/13 Mark Riddoch Initial implementation - * 17/05/14 Mark Riddoch Addition of unique_name - * 20/05/14 Massimiliano Pinto Addition of server_string - * 21/05/14 Massimiliano Pinto Addition of node_id - * 28/05/14 Massimiliano Pinto Addition of rlagd and node_ts fields - * 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields - * 26/06/14 Mark Riddoch Addition of server parameters - * 30/08/14 Massimiliano Pinto Addition of new service status description - * 30/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS description - * 01/06/15 Massimiliano Pinto Addition of server_update_address/port - * 19/06/15 Martin Brampton Extra code for persistent connections - * - * @endverbatim */ #include #include @@ -38,6 +21,9 @@ #include #include #include +#include + +#include #include #include #include @@ -48,14 +34,28 @@ #include #include #include +#include +#include #include "maxscale/monitor.h" #include "maxscale/poll.h" +#include "maxscale/workertask.hh" +#include "maxscale/worker.hh" + +using maxscale::Semaphore; +using maxscale::Worker; +using maxscale::WorkerTask; + +using std::string; /** The latin1 charset */ #define SERVER_DEFAULT_CHARSET 0x08 -const char USE_PROXY_PROTOCOL[] = "use_proxy_protocol"; +const char CN_MONITORPW[] = "monitorpw"; +const char CN_MONITORUSER[] = "monitoruser"; +const char CN_PERSISTMAXTIME[] = "persistmaxtime"; +const char CN_PERSISTPOOLMAX[] = "persistpoolmax"; +const char CN_USE_PROXY_PROTOCOL[] = "use_proxy_protocol"; static SPINLOCK server_spin = SPINLOCK_INIT; static SERVER *allServers = NULL; @@ -429,101 +429,50 @@ dprintAllServers(DCB *dcb) /** * Print all servers in Json format to a DCB - * - * Designed to be called within a debugger session in order - * to display all active servers within the gateway */ void dprintAllServersJson(DCB *dcb) { - char *stat; - int len = 0; - int el = 1; - - spinlock_acquire(&server_spin); - SERVER *server = next_active_server(allServers); - while (server) - { - server = next_active_server(server->next); - len++; - } - - server = next_active_server(allServers); - - dcb_printf(dcb, "[\n"); - while (server) - { - dcb_printf(dcb, " {\n \"server\": \"%s\",\n", - server->name); - stat = server_status(server); - dcb_printf(dcb, " \"status\": \"%s\",\n", - stat); - MXS_FREE(stat); - dcb_printf(dcb, " \"protocol\": \"%s\",\n", - server->protocol); - dcb_printf(dcb, " \"port\": \"%d\",\n", - server->port); - if (server->server_string) - { - dcb_printf(dcb, " \"version\": \"%s\",\n", - server->server_string); - } - dcb_printf(dcb, " \"nodeId\": \"%ld\",\n", - server->node_id); - dcb_printf(dcb, " \"masterId\": \"%ld\",\n", - server->master_id); - if (server->slaves) - { - int i; - dcb_printf(dcb, " \"slaveIds\": [ "); - for (i = 0; server->slaves[i]; i++) - { - if (i == 0) - { - dcb_printf(dcb, "%li", server->slaves[i]); - } - else - { - dcb_printf(dcb, ", %li ", server->slaves[i]); - } - } - dcb_printf(dcb, "],\n"); - } - dcb_printf(dcb, " \"replDepth\": \"%d\",\n", - server->depth); - if (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) - { - if (server->rlag >= 0) - { - dcb_printf(dcb, " \"slaveDelay\": \"%d\",\n", server->rlag); - } - } - if (server->node_ts > 0) - { - dcb_printf(dcb, " \"lastReplHeartbeat\": \"%lu\",\n", server->node_ts); - } - dcb_printf(dcb, " \"totalConnections\": \"%d\",\n", - server->stats.n_connections); - dcb_printf(dcb, " \"currentConnections\": \"%d\",\n", - server->stats.n_current); - dcb_printf(dcb, " \"currentOps\": \"%d\"\n", - server->stats.n_current_ops); - if (el < len) - { - dcb_printf(dcb, " },\n"); - } - else - { - dcb_printf(dcb, " }\n"); - } - server = next_active_server(server->next); - el++; - } - - dcb_printf(dcb, "]\n"); - spinlock_release(&server_spin); + json_t* all_servers = server_list_to_json(""); + char* dump = json_dumps(all_servers, JSON_INDENT(4)); + dcb_printf(dcb, "%s", dump); + MXS_FREE(dump); + json_decref(all_servers); } +/** + * A class for cleaning up persistent connections + */ +class CleanupTask : public WorkerTask +{ +public: + CleanupTask(const SERVER* server): + m_server(server) + { + } + + void execute(Worker& worker) + { + int thread_id = worker.get_current_id(); + dcb_persistent_clean_count(m_server->persistent[thread_id], thread_id, false); + } + +private: + const SERVER* m_server; /**< Server to clean up */ +}; + +/** + * @brief Clean up any stale persistent connections + * + * This function purges any stale persistent connections from @c server. + * + * @param server Server to clean up + */ +static void cleanup_persistent_connections(const SERVER* server) +{ + CleanupTask task(server); + Worker::execute_concurrently(task); +} /** * Print server details to a DCB @@ -604,7 +553,7 @@ dprintServer(DCB *dcb, const SERVER *server) if (server->persistpoolmax) { dcb_printf(dcb, "\tPersistent pool size: %d\n", server->stats.n_persistent); - poll_send_message(POLL_MSG_CLEAN_PERSISTENT, (void*)server); + cleanup_persistent_connections(server); dcb_printf(dcb, "\tPersistent measured pool size: %d\n", server->stats.n_persistent); dcb_printf(dcb, "\tPersistent actual size max: %d\n", server->persistmax); dcb_printf(dcb, "\tPersistent pool size limit: %ld\n", server->persistpoolmax); @@ -962,7 +911,7 @@ static void server_parameter_free(SERVER_PARAM *tofree) * @return The parameter value or NULL if not found */ const char * -server_get_parameter(const SERVER *server, char *name) +server_get_parameter(const SERVER *server, const char *name) { SERVER_PARAM *param = server->parameters; @@ -1174,36 +1123,36 @@ static bool create_server_config(const SERVER *server, const char *filename) // TODO: Check for return values on all of the dprintf calls dprintf(file, "[%s]\n", server->unique_name); - dprintf(file, "type=server\n"); - dprintf(file, "protocol=%s\n", server->protocol); - dprintf(file, "address=%s\n", server->name); - dprintf(file, "port=%u\n", server->port); - dprintf(file, "authenticator=%s\n", server->authenticator); + dprintf(file, "%s=server\n", CN_TYPE); + dprintf(file, "%s=%s\n", CN_PROTOCOL, server->protocol); + dprintf(file, "%s=%s\n", CN_ADDRESS, server->name); + dprintf(file, "%s=%u\n", CN_PORT, server->port); + dprintf(file, "%s=%s\n", CN_AUTHENTICATOR, server->authenticator); if (server->auth_options) { - dprintf(file, "authenticator_options=%s\n", server->auth_options); + dprintf(file, "%s=%s\n", CN_AUTHENTICATOR_OPTIONS, server->auth_options); } if (*server->monpw && *server->monuser) { - dprintf(file, "monitoruser=%s\n", server->monuser); - dprintf(file, "monitorpw=%s\n", server->monpw); + dprintf(file, "%s=%s\n", CN_MONITORUSER, server->monuser); + dprintf(file, "%s=%s\n", CN_MONITORPW, server->monpw); } if (server->persistpoolmax) { - dprintf(file, "persistpoolmax=%ld\n", server->persistpoolmax); + dprintf(file, "%s=%ld\n", CN_PERSISTPOOLMAX, server->persistpoolmax); } if (server->persistmaxtime) { - dprintf(file, "persistmaxtime=%ld\n", server->persistmaxtime); + dprintf(file, "%s=%ld\n", CN_PERSISTMAXTIME, server->persistmaxtime); } if (server->use_proxy_protocol) { - dprintf(file, "%s=yes\n", USE_PROXY_PROTOCOL); + dprintf(file, "%s=yes\n", CN_USE_PROXY_PROTOCOL); } for (SERVER_PARAM *p = server->parameters; p; p = p->next) @@ -1216,25 +1165,25 @@ static bool create_server_config(const SERVER *server, const char *filename) if (server->server_ssl) { - dprintf(file, "ssl=required\n"); + dprintf(file, "%s=required\n", CN_SSL); if (server->server_ssl->ssl_cert) { - dprintf(file, "ssl_cert=%s\n", server->server_ssl->ssl_cert); + dprintf(file, "%s=%s\n", CN_SSL_CERT, server->server_ssl->ssl_cert); } if (server->server_ssl->ssl_key) { - dprintf(file, "ssl_key=%s\n", server->server_ssl->ssl_key); + dprintf(file, "%s=%s\n", CN_SSL_KEY, server->server_ssl->ssl_key); } if (server->server_ssl->ssl_ca_cert) { - dprintf(file, "ssl_ca_cert=%s\n", server->server_ssl->ssl_ca_cert); + dprintf(file, "%s=%s\n", CN_SSL_CA_CERT, server->server_ssl->ssl_ca_cert); } if (server->server_ssl->ssl_cert_verify_depth) { - dprintf(file, "ssl_cert_verify_depth=%d\n", server->server_ssl->ssl_cert_verify_depth); + dprintf(file, "%s=%d\n", CN_SSL_CERT_VERIFY_DEPTH, server->server_ssl->ssl_cert_verify_depth); } const char *version = NULL; @@ -1264,7 +1213,7 @@ static bool create_server_config(const SERVER *server, const char *filename) if (version) { - dprintf(file, "ssl_version=%s\n", version); + dprintf(file, "%s=%s\n", CN_SSL_VERSION, version); } } @@ -1410,3 +1359,150 @@ bool server_is_mxs_service(const SERVER *server) return rval; } + +json_t* server_list_to_json(const char* host) +{ + json_t* rval = json_array(); + + if (rval) + { + spinlock_acquire(&server_spin); + + for (SERVER* server = allServers; server; server = server->next) + { + if (SERVER_IS_ACTIVE(server)) + { + json_t* srv_json = server_to_json(server, host); + + if (srv_json == NULL) + { + json_decref(rval); + rval = NULL; + break; + } + + json_array_append_new(rval, srv_json); + } + } + + spinlock_release(&server_spin); + } + + return rval; +} + +json_t* server_to_json(const SERVER* server, const char* host) +{ + json_t* rval = json_object(); + + json_object_set_new(rval, CN_NAME, json_string(server->unique_name)); + + /** Store server parameters */ + json_t* params = json_object(); + + json_object_set_new(params, CN_ADDRESS, json_string(server->name)); + json_object_set_new(params, CN_PORT, json_integer(server->port)); + json_object_set_new(params, CN_PROTOCOL, json_string(server->protocol)); + + if (*server->monuser) + { + json_object_set_new(params, CN_MONITORUSER, json_string(server->monuser)); + } + + if (*server->monpw) + { + json_object_set_new(params, CN_MONITORPW, json_string(server->monpw)); + } + + for (SERVER_PARAM* p = server->parameters; p; p = p->next) + { + json_object_set_new(params, p->name, json_string(p->value)); + } + + json_object_set_new(rval, CN_PARAMETERS, params); + + /** Store general information about the server state */ + char* stat = server_status(server); + json_object_set_new(rval, "status", json_string(stat)); + MXS_FREE(stat); + + if (server->server_string) + { + json_object_set_new(rval, "version", json_string(server->server_string)); + } + + json_object_set_new(rval, "node_id", json_integer(server->node_id)); + json_object_set_new(rval, "master_id", json_integer(server->master_id)); + json_object_set_new(rval, "replication_depth", json_integer(server->depth)); + + if (server->slaves) + { + json_t* slaves = json_array(); + + for (int i = 0; server->slaves[i]; i++) + { + json_array_append_new(slaves, json_integer(server->slaves[i])); + } + + json_object_set_new(rval, "slaves", slaves); + } + + if (server->rlag >= 0) + { + json_object_set_new(rval, "replication_lag", json_integer(server->rlag)); + } + + if (server->node_ts > 0) + { + struct tm result; + char timebuf[30]; + time_t tim = server->node_ts; + asctime_r(localtime_r(&tim, &result), timebuf); + trim(timebuf); + + json_object_set_new(rval, "last_heartbeat", json_string(timebuf)); + } + + /** Store statistics */ + json_t* stats = json_object(); + + json_object_set_new(stats, "connections", json_integer(server->stats.n_current)); + json_object_set_new(stats, "total_connections", json_integer(server->stats.n_connections)); + json_object_set_new(stats, "active_operations", json_integer(server->stats.n_current_ops)); + + json_object_set_new(rval, "statictics", stats); + + /** Store relationships to other objects */ + json_t* rel = json_object(); + + string self = host; + self += "/servers/"; + self += server->unique_name; + json_object_set_new(rel, CN_SELF, json_string(self.c_str())); + + json_t* arr = service_relations_to_server(server, host); + + if (json_array_size(arr) > 0) + { + json_object_set_new(rel, CN_SERVICES, arr); + } + else + { + json_decref(arr); + } + + arr = monitor_relations_to_server(server, host); + + if (json_array_size(arr) > 0) + { + json_object_set_new(rel, CN_MONITORS, arr); + } + else + { + json_decref(arr); + } + + json_object_set_new(rval, CN_RELATIONSHIPS, rel); + + return rval; +} diff --git a/server/core/service.cc b/server/core/service.cc index 76165a3a7..130e2a487 100644 --- a/server/core/service.cc +++ b/server/core/service.cc @@ -12,33 +12,11 @@ */ /** - * @file service.c - A representation of the service within the gateway. - * - * @verbatim - * Revision History - * - * Date Who Description - * 18/06/13 Mark Riddoch Initial implementation - * 24/06/13 Massimiliano Pinto Added: Loading users from mysql backend in serviceStart - * 06/02/14 Massimiliano Pinto Added: serviceEnableRootUser routine - * 25/02/14 Massimiliano Pinto Added: service refresh limit feature - * 28/02/14 Massimiliano Pinto users_alloc moved from service_alloc to - * serviceStartPort (generic hashable for services) - * 07/05/14 Massimiliano Pinto Added: version_string initialized to NULL - * 23/05/14 Mark Riddoch Addition of service validation call - * 29/05/14 Mark Riddoch Filter API implementation - * 09/09/14 Massimiliano Pinto Added service option for localhost authentication - * 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services) - * 06/02/15 Mark Riddoch Added caching of authentication data - * 18/02/15 Mark Riddoch Added result set management - * 03/03/15 Massimiliano Pinto Added config_enable_feedback_task() call in serviceStartAll - * 19/06/15 Martin Brampton More meaningful names for temp variables - * 31/05/16 Martin Brampton Implement connection throttling - * 08/11/16 Massimiliano Pinto Added: service_shutdown() calls destroyInstance() hoosk for routers - * - * @endverbatim + * @file service.c - A representation of a service within MaxScale */ -#include + +#include + #include #include #include @@ -48,6 +26,10 @@ #include #include #include +#include +#include + +#include #include #include #include @@ -65,6 +47,7 @@ #include #include #include +#include #include "maxscale/config.h" #include "maxscale/filter.h" @@ -72,6 +55,9 @@ #include "maxscale/queuemanager.h" #include "maxscale/service.h" +using std::string; +using std::set; + /** Base value for server weights */ #define SERVICE_BASE_SERVER_WEIGHT 1000 @@ -156,10 +142,6 @@ SERVICE* service_alloc(const char *name, const char *router) service->localhost_match_wildcard_host = SERVICE_PARAM_UNINIT; service->retry_start = true; service->conn_idle_timeout = SERVICE_NO_SESSION_TIMEOUT; - service->weightby = NULL; - service->credentials.authdata = NULL; - service->credentials.name = NULL; - service->version_string = NULL; service->svc_config_param = NULL; service->routerOptions = NULL; service->log_auth_warnings = true; @@ -645,23 +627,24 @@ int service_launch_all() bool serviceStop(SERVICE *service) { - SERV_LISTENER *port; int listeners = 0; - port = service->ports; - while (port) + if (service) { - if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER) + for (SERV_LISTENER * port = service->ports; port; port = port->next) { - if (poll_remove_dcb(port->listener) == 0) + if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER) { - port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; - listeners++; + if (poll_remove_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; + listeners++; + } } } - port = port->next; + + service->state = SERVICE_STATE_STOPPED; } - service->state = SERVICE_STATE_STOPPED; return listeners > 0; } @@ -676,23 +659,25 @@ bool serviceStop(SERVICE *service) */ bool serviceStart(SERVICE *service) { - SERV_LISTENER *port; int listeners = 0; - port = service->ports; - while (port) + if (service) { - if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER_STOPPED) + for (SERV_LISTENER* port = service->ports; port; port = port->next) { - if (poll_add_dcb(port->listener) == 0) + if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER_STOPPED) { - port->listener->session->state = SESSION_STATE_LISTENER; - listeners++; + if (poll_add_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER; + listeners++; + } } } - port = port->next; + + service->state = SERVICE_STATE_STARTED; } - service->state = SERVICE_STATE_STARTED; + return listeners > 0; } @@ -734,10 +719,6 @@ void service_free(SERVICE *service) MXS_FREE(service->name); MXS_FREE(service->routerModule); - MXS_FREE(service->weightby); - MXS_FREE(service->version_string); - MXS_FREE(service->credentials.name); - MXS_FREE(service->credentials.authdata); config_parameter_free(service->svc_config_param); serviceClearRouterOptions(service); @@ -1006,23 +987,19 @@ serviceClearRouterOptions(SERVICE *service) * @return 0 on failure */ int -serviceSetUser(SERVICE *service, char *user, char *auth) +serviceSetUser(SERVICE *service, const char *user, const char *auth) { - user = MXS_STRDUP(user); - auth = MXS_STRDUP(auth); - - if (!user || !auth) + if (service->credentials.name != user) { - MXS_FREE(user); - MXS_FREE(auth); - return 0; + snprintf(service->credentials.name, + sizeof(service->credentials.name), "%s", user); } - MXS_FREE(service->credentials.name); - MXS_FREE(service->credentials.authdata); - - service->credentials.name = user; - service->credentials.authdata = auth; + if (service->credentials.authdata != auth) + { + snprintf(service->credentials.authdata, + sizeof(service->credentials.authdata), "%s", auth); + } return 1; } @@ -1137,6 +1114,14 @@ serviceSetTimeout(SERVICE *service, int val) return 1; } +void serviceSetVersionString(SERVICE *service, const char* value) +{ + if (service->version_string != value) + { + snprintf(service->version_string, sizeof(service->version_string), "%s", value); + } +} + /** * Sets the connection limits, if any, for the service. * @param service Service to configure @@ -1199,7 +1184,7 @@ service_queue_check(void *data) * @param service Service to configure * @param value A string representation of a boolean value */ -void serviceSetRetryOnFailure(SERVICE *service, char* value) +void serviceSetRetryOnFailure(SERVICE *service, const char* value) { if (value) { @@ -1237,6 +1222,8 @@ serviceSetFilters(SERVICE *service, char *filters) ptr = strtok_r(filters, "|", &brkt); while (ptr) { + fix_section_name(ptr); + n++; MXS_FILTER_DEF **tmp; if ((tmp = (MXS_FILTER_DEF **) MXS_REALLOC(flist, @@ -1458,7 +1445,7 @@ void dprintService(DCB *dcb, SERVICE *service) } server = server->next; } - if (service->weightby) + if (*service->weightby) { dcb_printf(dcb, "\tRouting weight parameter: %s\n", service->weightby); @@ -1807,14 +1794,12 @@ service_get_name(SERVICE *svc) * @param service The service pointer * @param weightby The parameter name to weight the routing by */ -void -serviceWeightBy(SERVICE *service, char *weightby) +void serviceWeightBy(SERVICE *service, const char *weightby) { - if (service->weightby) + if (service->weightby != weightby) { - MXS_FREE(service->weightby); + snprintf(service->weightby, sizeof(service->weightby), "%s", weightby); } - service->weightby = MXS_STRDUP_A(weightby); } /** @@ -1822,8 +1807,7 @@ serviceWeightBy(SERVICE *service, char *weightby) * by * @param service The Service pointer */ -char * -serviceGetWeightingParameter(SERVICE *service) +const char* serviceGetWeightingParameter(SERVICE *service) { return service->weightby; } @@ -2115,8 +2099,9 @@ bool service_all_services_have_listeners() static void service_calculate_weights(SERVICE *service) { - char *weightby = serviceGetWeightingParameter(service); - if (weightby && service->dbref) + const char *weightby = serviceGetWeightingParameter(service); + + if (*weightby && service->dbref) { /** Service has a weighting parameter and at least one server */ int total = 0; @@ -2243,22 +2228,36 @@ static bool create_service_config(const SERVICE *service, const char *filename) } /** - * Only additional parameters are added to the configuration. This prevents - * duplication or addition of parameters that don't support it. - * * TODO: Check for return values on all of the dprintf calls */ dprintf(file, "[%s]\n", service->name); + dprintf(file, "%s=service\n", CN_TYPE); + dprintf(file, "%s=%s\n", CN_USER, service->credentials.name); + dprintf(file, "%s=%s\n", CN_PASSWORD, service->credentials.authdata); + dprintf(file, "%s=%s\n", CN_ENABLE_ROOT_USER, service->enable_root ? "true" : "false"); + dprintf(file, "%s=%d\n", CN_MAX_RETRY_INTERVAL, service->max_retry_interval); + dprintf(file, "%s=%d\n", CN_MAX_CONNECTIONS, service->max_connections); + dprintf(file, "%s=%ld\n", CN_CONNECTION_TIMEOUT, service->conn_idle_timeout); + dprintf(file, "%s=%s\n", CN_AUTH_ALL_SERVERS, service->users_from_all ? "true" : "false"); + dprintf(file, "%s=%s\n", CN_STRIP_DB_ESC, service->strip_db_esc ? "true" : "false"); + dprintf(file, "%s=%s\n", CN_LOCALHOST_MATCH_WILDCARD_HOST, service->localhost_match_wildcard_host ? "true" : "false"); + dprintf(file, "%s=%s\n", CN_VERSION_STRING, service->version_string); + dprintf(file, "%s=%s\n", CN_WEIGHTBY, service->weightby); + dprintf(file, "%s=%s\n", CN_LOG_AUTH_WARNINGS, service->log_auth_warnings ? "true" : "false"); + dprintf(file, "%s=%s\n", CN_RETRY_ON_FAILURE, service->retry_start ? "true" : "false"); + if (service->dbref) { - dprintf(file, "servers="); + dprintf(file, "%s=", CN_SERVERS); + const char *sep = ""; + for (SERVER_REF *db = service->dbref; db; db = db->next) { - if (db != service->dbref) + if (SERVER_REF_IS_ACTIVE(db)) { - dprintf(file, ","); + dprintf(file, "%s%s", sep, db->server->unique_name); + sep = ","; } - dprintf(file, "%s", db->server->unique_name); } dprintf(file, "\n"); } @@ -2268,6 +2267,41 @@ static bool create_service_config(const SERVICE *service, const char *filename) return true; } +bool service_serialize(const SERVICE *service) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), + service->name); + + if (unlink(filename) == -1 && errno != ENOENT) + { + MXS_ERROR("Failed to remove temporary service configuration at '%s': %d, %s", + filename, errno, mxs_strerror(errno)); + } + else if (create_service_config(service, 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 + { + MXS_ERROR("Failed to rename temporary service configuration at '%s': %d, %s", + filename, errno, mxs_strerror(errno)); + } + } + + return rval; +} + bool service_serialize_servers(const SERVICE *service) { bool rval = false; @@ -2339,3 +2373,258 @@ bool service_port_is_used(unsigned short port) return rval; } + +static const char* service_state_to_string(int state) +{ + switch (state) + { + case SERVICE_STATE_STARTED: + return "Started"; + + case SERVICE_STATE_STOPPED: + return "Stopped"; + + case SERVICE_STATE_FAILED: + return "Failed"; + + case SERVICE_STATE_ALLOC: + return "Allocated"; + + default: + ss_dassert(false); + return "Unknown"; + } +} + +json_t* service_parameters_to_json(const SERVICE* service) +{ + json_t* rval = json_object(); + json_t* arr = json_array(); + + string options; + + if (service->routerOptions && service->routerOptions[0]) + { + options += service->routerOptions[0]; + + for (int i = 1; service->routerOptions[i]; i++) + { + options += ","; + options += service->routerOptions[i]; + } + } + + json_object_set_new(rval, CN_ROUTER_OPTIONS, json_string(options.c_str())); + json_object_set_new(rval, CN_USER, json_string(service->credentials.name)); + json_object_set_new(rval, CN_PASSWORD, json_string(service->credentials.authdata)); + + json_object_set_new(rval, CN_ENABLE_ROOT_USER, json_boolean(service->enable_root)); + json_object_set_new(rval, CN_MAX_RETRY_INTERVAL, json_integer(service->max_retry_interval)); + json_object_set_new(rval, CN_MAX_CONNECTIONS, json_integer(service->max_connections)); + json_object_set_new(rval, CN_CONNECTION_TIMEOUT, json_integer(service->conn_idle_timeout)); + + json_object_set_new(rval, CN_AUTH_ALL_SERVERS, json_boolean(service->users_from_all)); + json_object_set_new(rval, CN_STRIP_DB_ESC, json_boolean(service->strip_db_esc)); + json_object_set_new(rval, CN_LOCALHOST_MATCH_WILDCARD_HOST, + json_boolean(service->localhost_match_wildcard_host)); + json_object_set_new(rval, CN_VERSION_STRING, json_string(service->version_string)); + + if (*service->weightby) + { + json_object_set_new(rval, CN_WEIGHTBY, json_string(service->weightby)); + } + + json_object_set_new(rval, CN_LOG_AUTH_WARNINGS, json_boolean(service->log_auth_warnings)); + json_object_set_new(rval, CN_RETRY_ON_FAILURE, json_boolean(service->retry_start)); + + /** Add custom module parameters */ + const MXS_MODULE* mod = get_module(service->routerModule, MODULE_ROUTER); + config_add_module_params_json(mod, service->svc_config_param, config_service_params, rval); + + return rval; +} + +json_t* service_to_json(const SERVICE* service, const char* host) +{ + spinlock_acquire(&service->spin); + + json_t* rval = json_object(); + + /** General service information */ + json_object_set_new(rval, CN_NAME, json_string(service->name)); + json_object_set_new(rval, CN_ROUTER, json_string(service->routerModule)); + json_object_set_new(rval, CN_STATE, json_string(service_state_to_string(service->state))); + + if (service->router && service->router_instance) + { + json_t* diag = service->router->diagnostics_json(service->router_instance); + + if (diag) + { + json_object_set_new(rval, "router_diagnostics", diag); + } + } + + struct tm result; + char timebuf[30]; + + asctime_r(localtime_r(&service->stats.started, &result), timebuf); + trim(timebuf); + + json_object_set_new(rval, "started", json_string(timebuf)); + json_object_set_new(rval, "total_connections", json_integer(service->stats.n_sessions)); + json_object_set_new(rval, "connections", json_integer(service->stats.n_current)); + + /** Add service parameters */ + json_object_set_new(rval, CN_PARAMETERS, service_parameters_to_json(service)); + + /** Add listeners */ + json_t* arr = json_array(); + + if (service->ports) + { + for (SERV_LISTENER* p = service->ports; p; p = p->next) + { + json_array_append_new(arr, listener_to_json(p)); + } + } + + json_object_set_new(rval, CN_LISTENERS, arr); + + /** Store relationships to other objects */ + json_t* rel = json_object(); + + string self = host; + self += "/services/"; + self += service->name; + json_object_set_new(rel, CN_SELF, json_string(self.c_str())); + + if (service->n_filters) + { + json_t* arr = json_array(); + + for (int i = 0; i < service->n_filters; i++) + { + string filter = host; + filter += "/filters/"; + filter += service->filters[i]->name; + json_array_append_new(arr, json_string(filter.c_str())); + } + + json_object_set_new(rel, "filters", arr); + } + + bool active_servers = false; + + for (SERVER_REF* ref = service->dbref; ref; ref = ref->next) + { + if (SERVER_REF_IS_ACTIVE(ref)) + { + active_servers = true; + break; + } + } + + if (active_servers) + { + json_t* arr = json_array(); + + for (SERVER_REF* ref = service->dbref; ref; ref = ref->next) + { + if (SERVER_REF_IS_ACTIVE(ref)) + { + string s = host; + s += "/servers/"; + s += ref->server->unique_name; + json_array_append_new(arr, json_string(s.c_str())); + } + } + + json_object_set_new(rel, CN_SERVERS, arr); + } + + json_object_set_new(rval, CN_RELATIONSHIPS, rel); + + spinlock_release(&service->spin); + + return rval; +} + +json_t* service_list_to_json(const char* host) +{ + json_t* rval = json_array(); + + spinlock_acquire(&service_spin); + + for (SERVICE *service = allServices; service; service = service->next) + { + json_t* svc = service_to_json(service, host); + + if (svc) + { + json_array_append_new(rval, svc); + } + } + + spinlock_release(&service_spin); + + return rval; +} + +static void add_service_relation(json_t* arr, const char* host, const SERVICE* service) +{ + string svc = host; + svc += "/services/"; + svc += service->name; + json_array_append_new(arr, json_string(svc.c_str())); +} + +json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* host) +{ + json_t* arr = json_array(); + spinlock_acquire(&service_spin); + + for (SERVICE *service = allServices; service; service = service->next) + { + spinlock_acquire(&service->spin); + + for (int i = 0; i < service->n_filters; i++) + { + if (service->filters[i] == filter) + { + add_service_relation(arr, host, service); + } + } + + spinlock_release(&service->spin); + } + + spinlock_release(&service_spin); + + return arr; +} + +json_t* service_relations_to_server(const SERVER* server, const char* host) +{ + json_t* arr = json_array(); + spinlock_acquire(&service_spin); + + for (SERVICE *service = allServices; service; service = service->next) + { + spinlock_acquire(&service->spin); + + for (SERVER_REF *ref = service->dbref; ref; ref = ref->next) + { + if (ref->server == server && SERVER_REF_IS_ACTIVE(ref)) + { + add_service_relation(arr, host, service); + } + } + + spinlock_release(&service->spin); + } + + spinlock_release(&service_spin); + + return arr; +} diff --git a/server/core/session.cc b/server/core/session.cc index 989151b4c..5793ce78a 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -45,10 +46,13 @@ #include #include #include +#include #include "maxscale/session.h" #include "maxscale/filter.h" +using std::string; + /** Global session id counter. Must be updated atomically. Value 0 is reserved for * dummy/unused sessions. */ @@ -993,4 +997,85 @@ void session_clear_stmt(MXS_SESSION *session) uint32_t session_get_next_id() { return atomic_add_uint32(&next_session_id, 1); -} \ No newline at end of file +} + +json_t* session_to_json(const MXS_SESSION *session, const char *host) +{ + json_t* rval = json_object(); + + json_object_set_new(rval, "id", json_integer(session->ses_id)); + json_object_set_new(rval, "state", json_string(session_state(session->state))); + + json_t* rel = json_object(); + json_t* arr = json_array(); + + string svc = host; + svc += "/services/"; + svc += session->service->name; + + json_array_append_new(arr, json_string(svc.c_str())); + json_object_set_new(rel, "services", arr); + json_object_set_new(rval, "relationships", rel); + + if (session->client_dcb->user) + { + json_object_set_new(rval, "user", json_string(session->client_dcb->user)); + } + + if (session->client_dcb->remote) + { + json_object_set_new(rval, "remote", json_string(session->client_dcb->remote)); + } + + struct tm result; + char buf[60]; + + asctime_r(localtime_r(&session->stats.connect, &result), buf); + trim(buf); + + json_object_set_new(rval, "connected", json_string(buf)); + + if (session->client_dcb->state == DCB_STATE_POLLING) + { + double idle = (hkheartbeat - session->client_dcb->last_read); + idle = idle > 0 ? idle / 10.f : 0; + json_object_set_new(rval, "idle", json_real(idle)); + } + + if (session->n_filters) + { + json_t* filters = json_array(); + + for (int i = 0; i < session->n_filters; i++) + { + string fil = host; + fil += "/filters/"; + fil += session->filters[i].filter->name; + json_array_append_new(filters, json_string(fil.c_str())); + } + + json_object_set_new(rval, "filters", filters); + } + + return rval; +} + +struct SessionListData +{ + json_t* json; + const char* host; +}; + +bool seslist_cb(DCB* dcb, void* data) +{ + SessionListData* d = (SessionListData*)data; + json_array_append_new(d->json, session_to_json(dcb->session, d->host)); + return true; +} + +json_t* session_list_to_json(const char* host) +{ + SessionListData data = {json_array(), host}; + dcb_foreach(seslist_cb, &data); + return data.json; +} diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 8e9629b39..89ca4cc0a 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -1,27 +1,27 @@ -add_executable(test_atomic testatomic.c) -add_executable(test_adminusers testadminusers.c) -add_executable(test_buffer testbuffer.c) -add_executable(test_dcb testdcb.c) -add_executable(test_filter testfilter.c) -add_executable(test_hash testhash.c) -add_executable(test_hint testhint.c) -add_executable(test_log testlog.c) -add_executable(test_logorder testlogorder.c) +add_executable(test_atomic testatomic.cc) +add_executable(test_adminusers testadminusers.cc) +add_executable(test_buffer testbuffer.cc) +add_executable(test_dcb testdcb.cc) +add_executable(test_filter testfilter.cc) +add_executable(test_hash testhash.cc) +add_executable(test_hint testhint.cc) +add_executable(test_log testlog.cc) +add_executable(test_logorder testlogorder.cc) add_executable(test_logthrottling testlogthrottling.cc) -add_executable(test_modutil testmodutil.c) -add_executable(test_poll testpoll.c) -add_executable(test_queuemanager testqueuemanager.c) +add_executable(test_modutil testmodutil.cc) +add_executable(test_poll testpoll.cc) +add_executable(test_queuemanager testqueuemanager.cc) add_executable(test_semaphore testsemaphore.cc) -add_executable(test_server testserver.c) -add_executable(test_service testservice.c) -add_executable(test_spinlock testspinlock.c) +add_executable(test_server testserver.cc) +add_executable(test_service testservice.cc) +add_executable(test_spinlock testspinlock.cc) add_executable(test_trxcompare testtrxcompare.cc ../../../query_classifier/test/testreader.cc) add_executable(test_trxtracking testtrxtracking.cc) -add_executable(test_users testusers.c) -add_executable(testfeedback testfeedback.c) -add_executable(testmaxscalepcre2 testmaxscalepcre2.c) -add_executable(testmodulecmd testmodulecmd.c) -add_executable(testconfig testconfig.c) +add_executable(test_users testusers.cc) +add_executable(testfeedback testfeedback.cc) +add_executable(testmaxscalepcre2 testmaxscalepcre2.cc) +add_executable(testmodulecmd testmodulecmd.cc) +add_executable(testconfig testconfig.cc) add_executable(trxboundaryparser_profile trxboundaryparser_profile.cc) target_link_libraries(test_atomic maxscale-common) target_link_libraries(test_adminusers maxscale-common) @@ -80,7 +80,6 @@ add_test(TestTrxCompare_Set test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../.. add_test(TestTrxCompare_Update test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/update.test) add_test(TestTrxCompare_MaxScale test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/maxscale.test) - # This test requires external dependencies and thus cannot be run # as a part of the core test set if(TEST_FEEDBACK) diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.cc similarity index 100% rename from server/core/test/testadminusers.c rename to server/core/test/testadminusers.cc diff --git a/server/core/test/testatomic.c b/server/core/test/testatomic.cc similarity index 100% rename from server/core/test/testatomic.c rename to server/core/test/testatomic.cc diff --git a/server/core/test/testbuffer.c b/server/core/test/testbuffer.cc similarity index 93% rename from server/core/test/testbuffer.c rename to server/core/test/testbuffer.cc index e1473b30c..6a5ee9f80 100644 --- a/server/core/test/testbuffer.c +++ b/server/core/test/testbuffer.cc @@ -45,7 +45,7 @@ */ uint8_t* generate_data(size_t count) { - uint8_t* data = MXS_MALLOC(count); + uint8_t* data = (uint8_t*)MXS_MALLOC(count); MXS_ABORT_IF_NULL(data); srand(0); @@ -79,7 +79,7 @@ GWBUF* create_test_buffer() uint8_t* data = generate_data(total); total = 0; - for (int i = 0; i < sizeof(buffers) / sizeof(size_t); i++) + for (size_t i = 0; i < sizeof(buffers) / sizeof(size_t); i++) { head = gwbuf_append(head, gwbuf_alloc_and_load(buffers[i], data + total)); total += buffers[i]; @@ -104,7 +104,7 @@ int get_length_at(int n) void split_buffer(int n, int offset) { - int cutoff = get_length_at(n) + offset; + size_t cutoff = get_length_at(n) + offset; GWBUF* buffer = create_test_buffer(); int len = gwbuf_length(buffer); GWBUF* newbuf = gwbuf_split(&buffer, cutoff); @@ -119,7 +119,7 @@ void split_buffer(int n, int offset) void consume_buffer(int n, int offset) { - int cutoff = get_length_at(n) + offset; + size_t cutoff = get_length_at(n) + offset; GWBUF* buffer = create_test_buffer(); int len = gwbuf_length(buffer); buffer = gwbuf_consume(buffer, cutoff); @@ -131,7 +131,7 @@ void consume_buffer(int n, int offset) void copy_buffer(int n, int offset) { - int cutoff = get_length_at(n) + offset; + size_t cutoff = get_length_at(n) + offset; uint8_t* data = generate_data(cutoff); GWBUF* buffer = create_test_buffer(); int len = gwbuf_length(buffer); @@ -450,30 +450,30 @@ test1() { GWBUF *buffer, *extra, *clone, *partclone; HINT *hint; - int size = 100; - int bite1 = 35; - int bite2 = 60; - int bite3 = 10; - int buflen; + size_t size = 100; + size_t bite1 = 35; + size_t bite2 = 60; + size_t bite3 = 10; + size_t buflen; /* Single buffer tests */ ss_dfprintf(stderr, - "testbuffer : creating buffer with data size %d bytes", + "testbuffer : creating buffer with data size %lu bytes", size); buffer = gwbuf_alloc(size); - ss_dfprintf(stderr, "\t..done\nAllocated buffer of size %d.", size); + ss_dfprintf(stderr, "\t..done\nAllocated buffer of size %lu.", size); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "\nBuffer length is now %d", buflen); + ss_dfprintf(stderr, "\nBuffer length is now %lu", buflen); ss_info_dassert(size == buflen, "Incorrect buffer size"); ss_info_dassert(0 == GWBUF_EMPTY(buffer), "Buffer should not be empty"); ss_info_dassert(GWBUF_IS_TYPE_UNDEFINED(buffer), "Buffer type should be undefined"); ss_dfprintf(stderr, "\t..done\nSet a hint for the buffer"); - hint = hint_create_parameter(NULL, "name", "value"); + hint = hint_create_parameter(NULL, (char*)"name", (char*)"value"); gwbuf_add_hint(buffer, hint); ss_info_dassert(hint == buffer->hint, "Buffer should point to first and only hint"); 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"); + gwbuf_add_property(buffer, (char*)"name", (char*)"value"); + ss_info_dassert(0 == strcmp("value", gwbuf_get_property(buffer, (char*)"name")), "Should now have correct property"); 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'"); @@ -485,7 +485,7 @@ test1() clone = gwbuf_clone(buffer); ss_dfprintf(stderr, "\t..done\nCloned buffer"); buflen = GWBUF_LENGTH(clone); - ss_dfprintf(stderr, "\nCloned buffer length is now %d", buflen); + ss_dfprintf(stderr, "\nCloned buffer length is now %lu", buflen); ss_info_dassert(size == buflen, "Incorrect buffer size"); ss_info_dassert(0 == GWBUF_EMPTY(clone), "Cloned buffer should not be empty"); ss_dfprintf(stderr, "\t..done\n"); @@ -495,45 +495,45 @@ test1() buffer = gwbuf_consume(buffer, bite1); ss_info_dassert(NULL != buffer, "Buffer should not be null"); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "Consumed %d bytes, now have %d, should have %d", bite1, buflen, size - bite1); + ss_dfprintf(stderr, "Consumed %lu bytes, now have %lu, should have %lu", bite1, buflen, size - bite1); ss_info_dassert((size - bite1) == buflen, "Incorrect buffer size"); ss_info_dassert(0 == GWBUF_EMPTY(buffer), "Buffer should not be empty"); ss_dfprintf(stderr, "\t..done\n"); buffer = gwbuf_consume(buffer, bite2); ss_info_dassert(NULL != buffer, "Buffer should not be null"); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "Consumed %d bytes, now have %d, should have %d", bite2, buflen, size - bite1 - bite2); + ss_dfprintf(stderr, "Consumed %lu bytes, now have %lu, should have %lu", bite2, buflen, size - bite1 - bite2); ss_info_dassert((size - bite1 - bite2) == buflen, "Incorrect buffer size"); ss_info_dassert(0 == GWBUF_EMPTY(buffer), "Buffer should not be empty"); ss_dfprintf(stderr, "\t..done\n"); buffer = gwbuf_consume(buffer, bite3); - ss_dfprintf(stderr, "Consumed %d bytes, should have null buffer", bite3); + ss_dfprintf(stderr, "Consumed %lu bytes, should have null buffer", bite3); ss_info_dassert(NULL == buffer, "Buffer should be null"); /* Buffer list tests */ size = 100000; buffer = gwbuf_alloc(size); - ss_dfprintf(stderr, "\t..done\nAllocated buffer of size %d.", size); + ss_dfprintf(stderr, "\t..done\nAllocated buffer of size %lu.", size); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "\nBuffer length is now %d", buflen); + ss_dfprintf(stderr, "\nBuffer length is now %lu", buflen); ss_info_dassert(size == buflen, "Incorrect buffer size"); ss_info_dassert(0 == GWBUF_EMPTY(buffer), "Buffer should not be empty"); ss_info_dassert(GWBUF_IS_TYPE_UNDEFINED(buffer), "Buffer type should be undefined"); extra = gwbuf_alloc(size); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "\t..done\nAllocated extra buffer of size %d.", size); + ss_dfprintf(stderr, "\t..done\nAllocated extra buffer of size %lu.", size); ss_info_dassert(size == buflen, "Incorrect buffer size"); buffer = gwbuf_append(buffer, extra); buflen = gwbuf_length(buffer); - ss_dfprintf(stderr, "\t..done\nAppended extra buffer to original buffer to create list of size %d", buflen); + ss_dfprintf(stderr, "\t..done\nAppended extra buffer to original buffer to create list of size %lu", buflen); ss_info_dassert((size * 2) == gwbuf_length(buffer), "Incorrect size for set of buffers"); buffer = gwbuf_rtrim(buffer, 60000); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "\t..done\nTrimmed 60 bytes from buffer, now size is %d.", buflen); + ss_dfprintf(stderr, "\t..done\nTrimmed 60 bytes from buffer, now size is %lu.", buflen); ss_info_dassert((size - 60000) == buflen, "Incorrect buffer size"); buffer = gwbuf_rtrim(buffer, 60000); buflen = GWBUF_LENGTH(buffer); - ss_dfprintf(stderr, "\t..done\nTrimmed another 60 bytes from buffer, now size is %d.", buflen); + ss_dfprintf(stderr, "\t..done\nTrimmed another 60 bytes from buffer, now size is %lu.", buflen); ss_info_dassert(100000 == buflen, "Incorrect buffer size"); ss_info_dassert(buffer == extra, "The buffer pointer should now point to the extra buffer"); ss_dfprintf(stderr, "\t..done\n"); diff --git a/server/core/test/testconfig.c b/server/core/test/testconfig.cc similarity index 95% rename from server/core/test/testconfig.c rename to server/core/test/testconfig.cc index caf6e0efb..0448ec024 100644 --- a/server/core/test/testconfig.c +++ b/server/core/test/testconfig.cc @@ -43,7 +43,7 @@ int test_validity() {MXS_END_MODULE_PARAMS} }; - CONFIG_CONTEXT ctx = {.object = ""}; + CONFIG_CONTEXT ctx = {.object = (char*)""}; /** Int parameter */ TEST(config_param_is_valid(params, "p1", "1", &ctx)); @@ -90,7 +90,7 @@ int test_validity() TEST(!config_param_is_valid(params, "p6", "This is not a valid path", &ctx)); /** Service parameter */ - CONFIG_CONTEXT svc = {.object = "test-service"}; + CONFIG_CONTEXT svc = {.object = (char*)"test-service"}; ctx.next = &svc; config_add_param(&svc, "type", "service"); TEST(config_param_is_valid(params, "p7", "test-service", &ctx)); @@ -135,9 +135,12 @@ int test_add_parameter() }; - CONFIG_CONTEXT svc1 = {.object = "my-service"}; - CONFIG_CONTEXT svc2 = {.object = "some-service", .next = &svc1}; - CONFIG_CONTEXT ctx = {.object = "", .next = &svc2}; + CONFIG_CONTEXT svc1, svc2, ctx; + svc1.object = (char*)"my-service"; + svc2.object = (char*)"some-service"; + svc2.next = &svc1; + ctx.object = (char*)""; + ctx.next = &svc2; config_add_param(&svc1, "type", "service"); config_add_param(&svc2, "type", "service"); @@ -190,7 +193,7 @@ int test_required_parameters() {MXS_END_MODULE_PARAMS} }; - CONFIG_CONTEXT ctx = {.object = ""}; + CONFIG_CONTEXT ctx = {.object = (char*)""}; TEST(missing_required_parameters(params, ctx.parameters)); config_add_defaults(&ctx, params); diff --git a/server/core/test/testdcb.c b/server/core/test/testdcb.cc similarity index 100% rename from server/core/test/testdcb.c rename to server/core/test/testdcb.cc diff --git a/server/core/test/testfeedback.c b/server/core/test/testfeedback.cc similarity index 87% rename from server/core/test/testfeedback.c rename to server/core/test/testfeedback.cc index 054a4c8ad..5fc17ee87 100644 --- a/server/core/test/testfeedback.c +++ b/server/core/test/testfeedback.cc @@ -45,9 +45,11 @@ #include #include +#include "../maxscale/config.h" + #include "../load_utils.cc" -static char* server_options[] = +static const char* server_options[] = { "MariaDB Corporation MaxScale", "--no-defaults", @@ -60,7 +62,7 @@ static char* server_options[] = const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1; -static char* server_groups[] = +static const char* server_groups[] = { "embedded", "server", @@ -71,10 +73,6 @@ static char* server_groups[] = NULL }; -int config_load(char *); -void config_enable_feedback_task(void); -int do_http_post(GWBUF *buffer, void *cfg); - int main(int argc, char** argv) { FEEDBACK_CONF* fc; @@ -85,14 +83,14 @@ int main(int argc, char** argv) hkinit(); - cnf = MXS_MALLOC(sizeof(char) * (strlen(TEST_DIR) + strlen("/maxscale.cnf") + 1)); + cnf = (char*)MXS_MALLOC(sizeof(char) * (strlen(TEST_DIR) + strlen("/maxscale.cnf") + 1)); MXS_ABORT_IF_NULL(cnf); sprintf(cnf, "%s/maxscale.cnf", TEST_DIR); printf("Config: %s\n", cnf); - if (mysql_library_init(num_elements, server_options, server_groups)) + if (mysql_library_init(num_elements, (char**)server_options, (char**)server_groups)) { FAILTEST("Failed to initialize embedded library."); } diff --git a/server/core/test/testfilter.c b/server/core/test/testfilter.cc similarity index 100% rename from server/core/test/testfilter.c rename to server/core/test/testfilter.cc diff --git a/server/core/test/testhash.c b/server/core/test/testhash.cc similarity index 100% rename from server/core/test/testhash.c rename to server/core/test/testhash.cc diff --git a/server/core/test/testhint.c b/server/core/test/testhint.cc similarity index 94% rename from server/core/test/testhint.c rename to server/core/test/testhint.cc index 90a7fe8d4..01b04a9ee 100644 --- a/server/core/test/testhint.c +++ b/server/core/test/testhint.cc @@ -54,7 +54,7 @@ test1() MXS_FREE(name); mxs_log_flush_sync(); ss_info_dassert(NULL != hint, "New hint list should not be null"); - ss_info_dassert(0 == strcmp("value", hint->value), "Hint value should be correct"); + ss_info_dassert(0 == strcmp("value", (char*)hint->value), "Hint value should be correct"); ss_info_dassert(0 != hint_exists(&hint, HINT_PARAMETER), "Hint of parameter type should exist"); ss_dfprintf(stderr, "\t..done\nFree hints."); if (NULL != hint) diff --git a/server/core/test/testlog.c b/server/core/test/testlog.cc similarity index 99% rename from server/core/test/testlog.c rename to server/core/test/testlog.cc index f4d713df7..61c171d1c 100644 --- a/server/core/test/testlog.c +++ b/server/core/test/testlog.cc @@ -35,7 +35,7 @@ static void skygw_log_disable(int priority) int main(int argc, char* argv[]) { int err = 0; - char* logstr; + const char* logstr; int i; bool succp; diff --git a/server/core/test/testlogorder.c b/server/core/test/testlogorder.cc similarity index 100% rename from server/core/test/testlogorder.c rename to server/core/test/testlogorder.cc diff --git a/server/core/test/testmaxscalepcre2.c b/server/core/test/testmaxscalepcre2.cc similarity index 96% rename from server/core/test/testmaxscalepcre2.c rename to server/core/test/testmaxscalepcre2.cc index 5e7ca18b0..4ea1c2d9b 100644 --- a/server/core/test/testmaxscalepcre2.c +++ b/server/core/test/testmaxscalepcre2.cc @@ -82,7 +82,7 @@ static int test2() pcre2_code *re2 = pcre2_compile((PCRE2_SPTR) pattern2, PCRE2_ZERO_TERMINATED, 0, &err, &erroff, NULL); size_t size = 1000; - char* dest = MXS_MALLOC(size); + char* dest = (char*)MXS_MALLOC(size); MXS_ABORT_IF_NULL(dest); mxs_pcre2_result_t result = mxs_pcre2_substitute(re, subject, good_replace, &dest, &size); @@ -90,12 +90,12 @@ static int test2() test_assert(strcmp(dest, expected) == 0, "Replaced text should match expected text"); size = 1000; - dest = MXS_REALLOC(dest, size); + dest = (char*)MXS_REALLOC(dest, size); result = mxs_pcre2_substitute(re2, subject, good_replace, &dest, &size); test_assert(result == MXS_PCRE2_NOMATCH, "Non-matching substitution should not substitute"); size = 1000; - dest = MXS_REALLOC(dest, size); + dest = (char*)MXS_REALLOC(dest, size); result = mxs_pcre2_substitute(re, subject, bad_replace, &dest, &size); test_assert(result == MXS_PCRE2_ERROR, "Bad substitution should return an error"); diff --git a/server/core/test/testmodulecmd.c b/server/core/test/testmodulecmd.cc similarity index 98% rename from server/core/test/testmodulecmd.c rename to server/core/test/testmodulecmd.cc index e82294ad4..c757e136c 100644 --- a/server/core/test/testmodulecmd.c +++ b/server/core/test/testmodulecmd.cc @@ -23,7 +23,7 @@ #include "../maxscale/monitor.h" -#define TEST(a, b) do{if (!(a)){printf("%s:%d "b"\n", __FILE__, __LINE__);return 1;}}while(false) +#define TEST(a, b) do{if (!(a)){printf("%s:%d " b "\n", __FILE__, __LINE__);return 1;}}while(false) static bool ok = false; @@ -154,8 +154,8 @@ int test_optional_arguments() const void *params3[] = {"Hello", NULL}; const void *params4[] = {NULL, NULL}; - const void *ns = "test_optional_arguments"; - const void *id = "test_optional_arguments"; + const char *ns = "test_optional_arguments"; + const char *id = "test_optional_arguments"; modulecmd_arg_type_t args1[] = { {MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, ""}, @@ -375,7 +375,7 @@ int test_domain_matching() /** Create a monitor */ char *libdir = MXS_STRDUP_A("../../modules/monitor/mysqlmon/"); set_libdir(libdir); - monitor_alloc((char*)ns, "mysqlmon"); + monitor_alloc((char*)ns, (char*)"mysqlmon"); const void* params[] = {ns}; diff --git a/server/core/test/testmodutil.c b/server/core/test/testmodutil.cc similarity index 97% rename from server/core/test/testmodutil.c rename to server/core/test/testmodutil.cc index 8f92dab00..f6b0a6b42 100644 --- a/server/core/test/testmodutil.c +++ b/server/core/test/testmodutil.cc @@ -62,7 +62,7 @@ test1() ss_dfprintf(stderr, "\t..done\nExtract SQL from buffer different way?"); ss_info_dassert(0 == modutil_MySQL_Query(buffer, &sql, &length, &residual), "Default buffer should fail"); ss_dfprintf(stderr, "\t..done\nReplace SQL in buffer"); - ss_info_dassert(0 == modutil_replace_SQL(buffer, "select * from some_table;"), "Default buffer should fail"); + ss_info_dassert(0 == modutil_replace_SQL(buffer, (char*)"select * from some_table;"), "Default buffer should fail"); ss_dfprintf(stderr, "\t..done\nTidy up."); gwbuf_free(buffer); ss_dfprintf(stderr, "\t..done\n"); @@ -89,7 +89,7 @@ test2() *((unsigned char*)buffer->start + 2) = 0; *((unsigned char*)buffer->start + 3) = 1; *((unsigned char*)buffer->start + 4) = 0x03; - memcpy(buffer->start + 5, query, strlen(query)); + memcpy((uint8_t*)buffer->start + 5, query, strlen(query)); char* result = modutil_get_SQL(buffer); ss_dassert(strcmp(result, query) == 0); gwbuf_free(buffer); @@ -109,7 +109,7 @@ static char ok[] = * CREATE OR REPLACE TABLE test.t1 (id int); * INSERT INTO test.t1 VALUES (3000); * SELECT * FROM test.t1; */ -static const char resultset[] = +static const uint8_t resultset[] = { /* Packet 1 */ 0x01, 0x00, 0x00, 0x01, 0x01, @@ -141,7 +141,7 @@ static const char resultset[] = struct packet { int index; - int length; + unsigned int length; } packets[] = { { PACKET_1_IDX, PACKET_1_LEN }, @@ -259,8 +259,8 @@ void test_multiple_sql_packets1() ss_info_dassert(gwbuf_length(complete) == sizeof(resultset), "Complete should be sizeof(resulset) bytes long"); - int headlen = gwbuf_length(head); - int completelen = complete ? gwbuf_length(complete) : 0; + unsigned int headlen = gwbuf_length(head); + unsigned int completelen = complete ? gwbuf_length(complete) : 0; uint8_t databuf[sizeof(resultset)]; ss_info_dassert(gwbuf_copy_data(complete, 0, completelen, databuf) == completelen, "Expected data should be readable"); @@ -344,7 +344,7 @@ void test_multiple_sql_packets2() buffer = gwbuf_alloc_and_load(sizeof(resultset), resultset); // Empty buffer packet by packet. - for (int i = 0; i < N_PACKETS; i++) + for (unsigned int i = 0; i < N_PACKETS; i++) { GWBUF* next = modutil_get_next_MySQL_packet(&buffer); ss_info_dassert(next, "Next packet buffer should not be NULL"); @@ -439,7 +439,7 @@ void test_multiple_sql_packets2() } while (total < sizeof(resultset)); - for (int i = 0; i < N_PACKETS; i++) + for (unsigned int i = 0; i < N_PACKETS; i++) { GWBUF* next = modutil_get_next_MySQL_packet(&buffer); ss_info_dassert(next, "Next packet buffer should not be NULL"); @@ -580,7 +580,7 @@ void test_large_packets() for (int i = 2; i < 8; i++) { GWBUF* buffer = gwbuf_append(create_buffer(0x00ffffff), create_buffer(i)); - ss_dassert(gwbuf_length(buffer) == 0xffffff + i + 8); + ss_dassert(gwbuf_length(buffer) == 0xffffffUL + i + 8); GWBUF_RTRIM(buffer->next, 1) GWBUF* complete = modutil_get_complete_packets(&buffer); ss_info_dassert(buffer, "Incomplete buffer is not NULL"); @@ -591,9 +591,9 @@ void test_large_packets() } } -char* bypass_whitespace(char* sql) +char* bypass_whitespace(const char* sql) { - return modutil_MySQL_bypass_whitespace(sql, strlen(sql)); + return modutil_MySQL_bypass_whitespace((char*)sql, strlen(sql)); } void test_bypass_whitespace() diff --git a/server/core/test/testpoll.c b/server/core/test/testpoll.cc similarity index 100% rename from server/core/test/testpoll.c rename to server/core/test/testpoll.cc diff --git a/server/core/test/testqueuemanager.c b/server/core/test/testqueuemanager.cc similarity index 99% rename from server/core/test/testqueuemanager.c rename to server/core/test/testqueuemanager.cc index d790bfb49..9f5e22f9e 100644 --- a/server/core/test/testqueuemanager.c +++ b/server/core/test/testqueuemanager.cc @@ -93,7 +93,7 @@ test1() ss_dfprintf(stderr, "Filled %d, emptied %d, expired %d\n", filled, emptied, expired); if (random_jkiss() % 2) { - int *entrynumber = MXS_MALLOC(sizeof(int)); + int *entrynumber = (int*)MXS_MALLOC(sizeof(int)); *entrynumber = input_counter; if (mxs_enqueue(queue, entrynumber)) { diff --git a/server/core/test/testserver.c b/server/core/test/testserver.cc similarity index 96% rename from server/core/test/testserver.c rename to server/core/test/testserver.cc index 1bceb20f8..ba95ce06d 100644 --- a/server/core/test/testserver.c +++ b/server/core/test/testserver.cc @@ -64,10 +64,10 @@ test1() //ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation"); ss_dfprintf(stderr, "\t..done\nTest Parameter for Server."); - ss_info_dassert(NULL == server_get_parameter(server, "name"), "Parameter should be null when not set"); + ss_info_dassert(NULL == server_get_parameter(server, (char*)"name"), "Parameter should be null when not set"); server_add_parameter(server, "name", "value"); mxs_log_flush_sync(); - ss_info_dassert(0 == strcmp("value", server_get_parameter(server, "name")), + ss_info_dassert(0 == strcmp("value", server_get_parameter(server, (char*)"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("non-existent"), @@ -115,7 +115,7 @@ bool test_load_config(const char *input, SERVER *server) if (duplicate_context_init(&dcontext)) { - CONFIG_CONTEXT ccontext = {.object = ""}; + CONFIG_CONTEXT ccontext = {.object = (char*)""}; if (config_load_single_file(input, &dcontext, &ccontext)) { diff --git a/server/core/test/testservice.c b/server/core/test/testservice.cc similarity index 100% rename from server/core/test/testservice.c rename to server/core/test/testservice.cc diff --git a/server/core/test/testsession.c b/server/core/test/testsession.cc similarity index 100% rename from server/core/test/testsession.c rename to server/core/test/testsession.cc diff --git a/server/core/test/testspinlock.c b/server/core/test/testspinlock.cc similarity index 100% rename from server/core/test/testspinlock.c rename to server/core/test/testspinlock.cc diff --git a/server/core/test/testusers.c b/server/core/test/testusers.cc similarity index 100% rename from server/core/test/testusers.c rename to server/core/test/testusers.cc diff --git a/server/core/users.cc b/server/core/users.cc index ae5f5d901..6ac253c21 100644 --- a/server/core/users.cc +++ b/server/core/users.cc @@ -137,6 +137,30 @@ void users_default_diagnostic(DCB *dcb, SERV_LISTENER *port) } } +json_t* users_default_diagnostic_json(const SERV_LISTENER *port) +{ + json_t* rval = json_array(); + + if (port->users && port->users->data) + { + HASHITERATOR *iter = hashtable_iterator(port->users->data); + + if (iter) + { + char* user; + + while ((user = (char*)hashtable_next(iter))) + { + json_array_append_new(rval, json_string(user)); + } + + hashtable_iterator_free(iter); + } + } + + return rval; +} + int users_default_loadusers(SERV_LISTENER *port) { users_free(port->users); diff --git a/server/core/utils.cc b/server/core/utils.cc index 6d7fe1e08..daa563345 100644 --- a/server/core/utils.cc +++ b/server/core/utils.cc @@ -122,6 +122,29 @@ int setnonblocking(int fd) return 0; } +int setblocking(int fd) +{ + int fl; + + if ((fl = fcntl(fd, F_GETFL, 0)) == -1) + { + MXS_ERROR("Can't GET fcntl for %i, errno = %d, %s.", + fd, + errno, + mxs_strerror(errno)); + return 1; + } + + if (fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1) + { + MXS_ERROR("Can't SET fcntl for %i, errno = %d, %s", + fd, + errno, + mxs_strerror(errno)); + return 1; + } + return 0; +} char *gw_strend(register const char *s) { @@ -467,6 +490,22 @@ char* trim(char *str) return str; } +/** + * @brief Replace whitespace with hyphens + * + * @param str String to replace + */ +void replace_whitespace(char* str) +{ + for (char* s = str; *s; s++) + { + if (isspace(*s)) + { + *s = '-'; + } + } +} + /** * Replace all whitespace with spaces and squeeze repeating whitespace characters * diff --git a/server/core/worker.cc b/server/core/worker.cc index 11967f480..7d8e6444f 100644 --- a/server/core/worker.cc +++ b/server/core/worker.cc @@ -548,34 +548,60 @@ void Worker::set_maxwait(unsigned int maxwait) this_unit.max_poll_sleep = maxwait; } -bool Worker::post(Task* pTask, Semaphore* pSem) +bool Worker::post(Task* pTask, Semaphore* pSem, enum execute_mode_t mode) { // No logging here, function must be signal safe. - intptr_t arg1 = reinterpret_cast(pTask); - intptr_t arg2 = reinterpret_cast(pSem); + bool rval = true; - return post_message(MXS_WORKER_MSG_TASK, arg1, arg2); + if (mode == Worker::EXECUTE_AUTO && Worker::get_current() == this) + { + pTask->execute(*this); + + if (pSem) + { + pSem->post(); + } + } + else + { + intptr_t arg1 = reinterpret_cast(pTask); + intptr_t arg2 = reinterpret_cast(pSem); + + rval = post_message(MXS_WORKER_MSG_TASK, arg1, arg2); + } + + return rval; } -bool Worker::post(std::auto_ptr sTask) +bool Worker::post(std::auto_ptr sTask, enum execute_mode_t mode) { // No logging here, function must be signal safe. - return post_disposable(sTask.release()); + return post_disposable(sTask.release(), mode); } // private -bool Worker::post_disposable(DisposableTask* pTask) +bool Worker::post_disposable(DisposableTask* pTask, enum execute_mode_t mode) { + bool posted = true; + pTask->inc_ref(); - intptr_t arg1 = reinterpret_cast(pTask); - - bool posted = post_message(MXS_WORKER_MSG_DISPOSABLE_TASK, arg1, 0); - - if (!posted) + if (mode == Worker::EXECUTE_AUTO && Worker::get_current() == this) { + pTask->execute(*this); pTask->dec_ref(); } + else + { + intptr_t arg1 = reinterpret_cast(pTask); + + posted = post_message(MXS_WORKER_MSG_DISPOSABLE_TASK, arg1, 0); + + if (!posted) + { + pTask->dec_ref(); + } + } return posted; } @@ -859,7 +885,7 @@ void Worker::handle_message(MessageQueue& queue, const MessageQueue::Message& ms case MXS_WORKER_MSG_CALL: { - void (*f)(int, void*) = (void (*)(int,void*))msg.arg1(); + void (*f)(int, void*) = (void (*)(int, void*))msg.arg1(); f(m_id, (void*)msg.arg2()); } @@ -1059,8 +1085,6 @@ void Worker::poll_waitevents() /** Process closed DCBs */ dcb_process_zombies(m_id); - poll_check_message(); - m_state = IDLE; } /*< while(1) */ diff --git a/server/core/workertask.cc b/server/core/workertask.cc index f4ab02329..2b264238c 100644 --- a/server/core/workertask.cc +++ b/server/core/workertask.cc @@ -33,6 +33,10 @@ WorkerDisposableTask::WorkerDisposableTask() { } +WorkerDisposableTask::~WorkerDisposableTask() +{ +} + void WorkerDisposableTask::inc_ref() { atomic_add(&m_count, 1); diff --git a/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c b/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c index e763d51b7..30d7e1dfd 100644 --- a/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c +++ b/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c @@ -167,6 +167,7 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* No destroy entry point */ cdc_replace_users, /* Load CDC users */ users_default_diagnostic, /* Default diagnostic */ + users_default_diagnostic_json, /* Default diagnostic */ NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c index a847f6997..d16141120 100644 --- a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c @@ -666,6 +666,7 @@ MXS_MODULE* MXS_CREATE_MODULE() gssapi_auth_free, /* Free authenticator data */ gssapi_auth_load_users, /* Load database users */ users_default_diagnostic, /* Default user diagnostic */ + users_default_diagnostic_json, /* Default user diagnostic */ NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c index 1f1252a0e..8396b1bde 100644 --- a/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c @@ -278,6 +278,7 @@ MXS_MODULE* MXS_CREATE_MODULE() gssapi_backend_auth_free, /* Free authenticator data */ NULL, /* Load users from backend databases */ NULL, /* No diagnostic */ + NULL, NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/HTTPAuth/http_auth.c b/server/modules/authenticator/HTTPAuth/http_auth.c index 0824e07fd..1475953a4 100644 --- a/server/modules/authenticator/HTTPAuth/http_auth.c +++ b/server/modules/authenticator/HTTPAuth/http_auth.c @@ -68,6 +68,7 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* No destroy entry point */ users_default_loadusers, /* Load generic users */ users_default_diagnostic, /* Default user diagnostic */ + users_default_diagnostic_json, /* Default user diagnostic */ NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c b/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c index 65736d972..d06447f8a 100644 --- a/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c +++ b/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c @@ -62,6 +62,7 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* No destroy entry point */ users_default_loadusers, /* Load generic users */ users_default_diagnostic, /* Default user diagnostic */ + users_default_diagnostic_json, /* Default user diagnostic */ NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 062662fb7..1515df5b6 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -59,6 +59,7 @@ static int mysql_auth_set_client_data( GWBUF *buffer); void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port); +json_t* mysql_auth_diagnostic_json(const SERV_LISTENER *port); int mysql_auth_reauthenticate(DCB *dcb, const char *user, uint8_t *token, size_t token_len, @@ -85,6 +86,7 @@ MXS_MODULE* MXS_CREATE_MODULE() mysql_auth_destroy, /* Destroy entry point */ mysql_auth_load_users, /* Load users from backend databases */ mysql_auth_diagnostic, + mysql_auth_diagnostic_json, mysql_auth_reauthenticate /* Handle COM_CHANGE_USER */ }; @@ -703,3 +705,31 @@ void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port) } dcb_printf(dcb, "\n"); } + +int diag_cb_json(void *data, int columns, char **row, char **field_names) +{ + json_t* obj = json_object(); + json_object_set_new(obj, "user", json_string(row[0])); + json_object_set_new(obj, "host", json_string(row[1])); + + json_t* arr = (json_t*)data; + json_array_append_new(arr, obj); + return 0; +} + +json_t* mysql_auth_diagnostic_json(const SERV_LISTENER *port) +{ + json_t* rval = json_array(); + + MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance; + char *err; + + if (sqlite3_exec(instance->handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME, + diag_cb, rval, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to print users: %s", err); + sqlite3_free(err); + } + + return rval; +} diff --git a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c index bd6d950be..e034d3c23 100644 --- a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c +++ b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c @@ -171,6 +171,7 @@ MXS_MODULE* MXS_CREATE_MODULE() auth_backend_destroy, /* Destroy authenticator */ NULL, /* We don't need to load users */ NULL, /* No diagnostic */ + NULL, NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/NullAuthAllow/null_auth_allow.c b/server/modules/authenticator/NullAuthAllow/null_auth_allow.c index f9d458b24..596b1a045 100644 --- a/server/modules/authenticator/NullAuthAllow/null_auth_allow.c +++ b/server/modules/authenticator/NullAuthAllow/null_auth_allow.c @@ -64,6 +64,7 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* No destroy entry point */ users_default_loadusers, /* Load generic users */ NULL, /* No diagnostic */ + NULL, NULL /* No user reauthentication */ }; diff --git a/server/modules/authenticator/NullAuthDeny/null_auth_deny.c b/server/modules/authenticator/NullAuthDeny/null_auth_deny.c index 23427724b..721702751 100644 --- a/server/modules/authenticator/NullAuthDeny/null_auth_deny.c +++ b/server/modules/authenticator/NullAuthDeny/null_auth_deny.c @@ -61,6 +61,7 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* No destroy entry point */ users_default_loadusers, /* Load generic users */ NULL, /* No diagnostic */ + NULL, NULL /* No user reauthentication */ }; diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc index f8b389fdb..2c24441d2 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cache.cc @@ -110,6 +110,11 @@ void Cache::show(DCB* pDcb) const } } +json_t* Cache::show_json() const +{ + return get_info(INFO_ALL); +} + cache_result_t Cache::get_key(const char* zDefault_db, const GWBUF* pQuery, CACHE_KEY* pKey) const diff --git a/server/modules/filter/cache/cache.hh b/server/modules/filter/cache/cache.hh index 39e852893..da84a2b9b 100644 --- a/server/modules/filter/cache/cache.hh +++ b/server/modules/filter/cache/cache.hh @@ -41,6 +41,7 @@ public: virtual ~Cache(); void show(DCB* pDcb) const; + json_t* show_json() const; const CACHE_CONFIG& config() const { diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 59de1c158..5417dec2b 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -18,6 +18,7 @@ #include #include "cachemt.hh" #include "cachept.hh" +#include "maxscale/jansson.hh" using std::auto_ptr; using std::string; @@ -302,6 +303,12 @@ void CacheFilter::diagnostics(DCB* pDcb) m_sCache->show(pDcb); } +// static +json_t* CacheFilter::diagnostics_json() const +{ + return m_sCache->show_json(); +} + uint64_t CacheFilter::getCapabilities() { return RCAP_TYPE_NONE; diff --git a/server/modules/filter/cache/cachefilter.hh b/server/modules/filter/cache/cachefilter.hh index 2fd494294..b62968c53 100644 --- a/server/modules/filter/cache/cachefilter.hh +++ b/server/modules/filter/cache/cachefilter.hh @@ -38,6 +38,7 @@ public: CacheFilterSession* newSession(MXS_SESSION* pSession); void diagnostics(DCB* pDcb); + json_t* diagnostics_json() const; uint64_t getCapabilities(); diff --git a/server/modules/filter/cache/cachefiltersession.cc b/server/modules/filter/cache/cachefiltersession.cc index f537b7abe..e63fb22d5 100644 --- a/server/modules/filter/cache/cachefiltersession.cc +++ b/server/modules/filter/cache/cachefiltersession.cc @@ -456,6 +456,15 @@ void CacheFilterSession::diagnostics(DCB* pDcb) dcb_printf(pDcb, "\n"); } +json_t* CacheFilterSession::diagnostics_json() const +{ + // Not printing anything. Session of the same instance share the same cache, in + // which case the same information would be printed once per session, or all + // threads (but not sessions) share the same cache, in which case the output + // would be nonsensical. + return NULL; +} + /** * Called when resultset field information is handled. */ diff --git a/server/modules/filter/cache/cachefiltersession.hh b/server/modules/filter/cache/cachefiltersession.hh index a2ea2e083..b488f61c0 100644 --- a/server/modules/filter/cache/cachefiltersession.hh +++ b/server/modules/filter/cache/cachefiltersession.hh @@ -85,6 +85,11 @@ public: */ void diagnostics(DCB *dcb); + /** + * Print diagnostics of the session cache. + */ + json_t* diagnostics_json() const; + private: int handle_expecting_fields(); int handle_expecting_nothing(); diff --git a/server/modules/filter/ccrfilter/ccrfilter.c b/server/modules/filter/ccrfilter/ccrfilter.c index dc95f18a0..c4bca0773 100644 --- a/server/modules/filter/ccrfilter/ccrfilter.c +++ b/server/modules/filter/ccrfilter/ccrfilter.c @@ -56,6 +56,7 @@ static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); #define CCR_DEFAULT_TIME "60" @@ -123,6 +124,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No clientReply diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -382,6 +384,42 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) dcb_printf(dcb, "\tNo. of hints added based on time: %d\n", my_instance->stats.n_add_time); } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * 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 json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance; + json_t* rval = json_object(); + + json_object_set_new(rval, "count", json_integer(my_instance->count)); + json_object_set_new(rval, "time", json_integer(my_instance->time)); + + if (my_instance->match) + { + json_object_set_new(rval, "match", json_string(my_instance->match)); + } + + if (my_instance->nomatch) + { + json_object_set_new(rval, "nomatch", json_string(my_instance->nomatch)); + } + + json_object_set_new(rval, "data_modifications", json_integer(my_instance->stats.n_modified)); + json_object_set_new(rval, "hints_added_count", json_integer(my_instance->stats.n_add_count)); + json_object_set_new(rval, "hints_added_time", json_integer(my_instance->stats.n_add_time)); + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index 6e71a6d55..e6c58c540 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -102,6 +102,7 @@ static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); /** @@ -289,6 +290,36 @@ static void print_rule(RULE *rules, char *dest) rules->times_matched); } +static json_t* rule_to_json(RULE *rule) +{ + int type = 0; + + if ((int)rule->type > 0 && (int)rule->type < rule_names_len) + { + type = (int)rule->type; + } + + json_t* rval = json_object(); + + json_object_set_new(rval, "name", json_string(rule->name)); + json_object_set_new(rval, "type", json_string(rule_names[type])); + json_object_set_new(rval, "times_matched", json_integer(rule->times_matched)); + + return rval; +} + +static json_t* rules_to_json(RULE *rules) +{ + json_t* rval = json_array(); + + for (RULE *rule = rules; rule; rule = rule->next) + { + json_array_append_new(rval, rule_to_json(rule)); + } + + return rval; +} + /** * Push a string onto a string stack * @param head Head of the stack @@ -816,6 +847,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No clientReply diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -2499,6 +2531,21 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * Prints the connection details and the names of the exchange, + * queue and the routing key. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + return rules_to_json(thr_rules); +} + /** * Capability routine. * diff --git a/server/modules/filter/hintfilter/hintfilter.c b/server/modules/filter/hintfilter/hintfilter.c index 9d5685f8b..7765cf719 100644 --- a/server/modules/filter/hintfilter/hintfilter.c +++ b/server/modules/filter/hintfilter/hintfilter.c @@ -33,6 +33,7 @@ static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); /** @@ -56,6 +57,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No clientReply diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -234,6 +236,18 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } +/** + * Diagnostics routine + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* +diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + return NULL; +} + /** * Capability routine. * diff --git a/server/modules/filter/insertstream/insertstream.c b/server/modules/filter/insertstream/insertstream.c index c93f3910d..3a0172bbc 100644 --- a/server/modules/filter/insertstream/insertstream.c +++ b/server/modules/filter/insertstream/insertstream.c @@ -38,6 +38,7 @@ static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MX static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *session, MXS_UPSTREAM *upstream); static int32_t routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER *instance); static int32_t clientReply(MXS_FILTER* instance, MXS_FILTER_SESSION *session, GWBUF *reply); static bool extract_insert_target(GWBUF *buffer, char* target, int len); @@ -99,6 +100,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, }; @@ -513,6 +515,35 @@ static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB * } } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + DS_INSTANCE *my_instance = (DS_INSTANCE*)instance; + + json_t* rval = json_object(); + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + + if (my_instance->user) + { + json_object_set_new(rval, "user", json_string(my_instance->user)); + } + + return rval; +} + /** * @brief Get filter capabilities * diff --git a/server/modules/filter/luafilter/luafilter.c b/server/modules/filter/luafilter/luafilter.c index b4a7122ff..721116bb5 100644 --- a/server/modules/filter/luafilter/luafilter.c +++ b/server/modules/filter/luafilter/luafilter.c @@ -64,6 +64,7 @@ static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS static int32_t routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static int32_t clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER *instance); /** @@ -87,6 +88,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -664,6 +666,56 @@ static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB * } } +/** + * Diagnostics routine. + * + * This will call the matching diagnostics entry point in the Lua script. If the + * Lua function returns a string, it will be printed to the client DCB. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + LUA_INSTANCE *my_instance = (LUA_INSTANCE *)instance; + json_t* rval = json_object(); + + if (my_instance) + { + if (my_instance->global_lua_state) + { + spinlock_acquire(&my_instance->lock); + + lua_getglobal(my_instance->global_lua_state, "diagnostic"); + + if (lua_pcall(my_instance->global_lua_state, 0, 1, 0) == 0) + { + lua_gettop(my_instance->global_lua_state); + if (lua_isstring(my_instance->global_lua_state, -1)) + { + json_object_set_new(rval, "script_output", + json_string(lua_tostring(my_instance->global_lua_state, -1))); + } + } + else + { + lua_pop(my_instance->global_lua_state, -1); + } + spinlock_release(&my_instance->lock); + } + if (my_instance->global_script) + { + json_object_set_new(rval, "global_script", json_string(my_instance->global_script)); + } + if (my_instance->session_script) + { + json_object_set_new(rval, "session_script", json_string(my_instance->session_script)); + } + } + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/masking/maskingfilter.cc b/server/modules/filter/masking/maskingfilter.cc index a966bc858..6ebb6c385 100644 --- a/server/modules/filter/masking/maskingfilter.cc +++ b/server/modules/filter/masking/maskingfilter.cc @@ -143,6 +143,12 @@ void MaskingFilter::diagnostics(DCB* pDcb) dcb_printf(pDcb, "Hello, World!\n"); } +// static +json_t* MaskingFilter::diagnostics_json() const +{ + return NULL; +} + // static uint64_t MaskingFilter::getCapabilities() { diff --git a/server/modules/filter/masking/maskingfilter.hh b/server/modules/filter/masking/maskingfilter.hh index 3ad7a8d3c..a540adcb9 100644 --- a/server/modules/filter/masking/maskingfilter.hh +++ b/server/modules/filter/masking/maskingfilter.hh @@ -34,6 +34,7 @@ public: MaskingFilterSession* newSession(MXS_SESSION* pSession); void diagnostics(DCB* pDcb); + json_t* diagnostics_json() const; uint64_t getCapabilities(); diff --git a/server/modules/filter/maxrows/maxrows.c b/server/modules/filter/maxrows/maxrows.c index c6ad17557..6021bc7ae 100644 --- a/server/modules/filter/maxrows/maxrows.c +++ b/server/modules/filter/maxrows/maxrows.c @@ -70,6 +70,8 @@ static int clientReply(MXS_FILTER *instance, static void diagnostics(MXS_FILTER *instance, MXS_FILTER_SESSION *sdata, DCB *dcb); +static json_t* diagnostics_json(const MXS_FILTER *instance, + const MXS_FILTER_SESSION *sdata); static uint64_t getCapabilities(MXS_FILTER *instance); enum maxrows_return_mode @@ -107,6 +109,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostics, + diagnostics_json, getCapabilities, NULL, // No destroyInstance }; @@ -496,6 +499,20 @@ static void diagnostics(MXS_FILTER *instance, MXS_FILTER_SESSION *sdata, DCB *dc dcb_printf(dcb, "Maxrows filter is working\n"); } +/** + * 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 json_t* diagnostics_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *sdata) +{ + return NULL; +} /** * Capability routine. diff --git a/server/modules/filter/mqfilter/mqfilter.c b/server/modules/filter/mqfilter/mqfilter.c index 3e9be5587..e560ba47c 100644 --- a/server/modules/filter/mqfilter/mqfilter.c +++ b/server/modules/filter/mqfilter/mqfilter.c @@ -95,6 +95,7 @@ static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_ static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static int clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER *instance); /** @@ -270,6 +271,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -1506,6 +1508,36 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * Prints the connection details and the names of the exchange, + * queue and the routing key. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* +diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + MQ_INSTANCE *my_instance = (MQ_INSTANCE*)instance; + json_t* rval = json_object(); + + json_object_set_new(rval, "host", json_string(my_instance->hostname)); + json_object_set_new(rval, "user", json_string(my_instance->username)); + json_object_set_new(rval, "vhost", json_string(my_instance->vhost)); + json_object_set_new(rval, "exchange", json_string(my_instance->exchange)); + json_object_set_new(rval, "key", json_string(my_instance->key)); + json_object_set_new(rval, "queue", json_string(my_instance->queue)); + + json_object_set_new(rval, "port", json_integer(my_instance->port)); + json_object_set_new(rval, "messages", json_integer(my_instance->stats.n_msg)); + json_object_set_new(rval, "queued", json_integer(my_instance->stats.n_queued)); + json_object_set_new(rval, "sent", json_integer(my_instance->stats.n_sent)); + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/namedserverfilter/namedserverfilter.cc b/server/modules/filter/namedserverfilter/namedserverfilter.cc index 53f8a49cc..a67f0c8de 100644 --- a/server/modules/filter/namedserverfilter/namedserverfilter.cc +++ b/server/modules/filter/namedserverfilter/namedserverfilter.cc @@ -321,6 +321,24 @@ void RegexHintFSession::diagnostics(DCB* dcb) m_n_undiverted); } +/** + * Diagnostics routine + * + * Print diagnostics on the filter instance as a whole + session-specific info. + * + * @param dcb The DCB for diagnostic output + */ +json_t* RegexHintFSession::diagnostics_json() const +{ + + json_t* rval = m_fil_inst.diagnostics_json(); /* Print overall diagnostics */ + + json_object_set_new(rval, "session_queries_diverted", json_integer(m_n_diverted)); + json_object_set_new(rval, "session_queries_undiverted", json_integer(m_n_undiverted)); + + return rval; +} + /** * Diagnostics routine * @@ -364,6 +382,54 @@ void RegexHintFilter::diagnostics(DCB* dcb) } } +/** + * Diagnostics routine + * + * Print diagnostics on the filter instance as a whole. + * + * @param dcb The DCB for diagnostic output + */ +json_t* RegexHintFilter::diagnostics_json() const +{ + json_t* rval = json_object(); + + json_object_set_new(rval, "queries_diverted", json_integer(m_total_diverted)); + json_object_set_new(rval, "queries_undiverted", json_integer(m_total_undiverted)); + + if (m_mapping.size() > 0) + { + json_t* arr = json_array(); + + for (MappingArray::const_iterator it = m_mapping.begin(); it != m_mapping.end(); it++) + { + json_t* obj = json_object(); + json_t* targets = json_array(); + + for (StringArray::const_iterator it2 = it->m_targets.begin(); it2 != it->m_targets.end(); it2++) + { + json_array_append_new(targets, json_string(it2->c_str())); + } + + json_object_set_new(obj, "match", json_string(it->m_match.c_str())); + json_object_set_new(obj, "targets", targets); + } + + json_object_set_new(rval, "mappings", arr); + } + + if (m_source) + { + json_object_set_new(rval, "source", json_string(m_source->m_address.c_str())); + } + + if (m_user.length()) + { + json_object_set_new(rval, "user", json_string(m_user.c_str())); + } + + return rval; +} + /** * Parse the server list and add the contained servers to the struct's internal * list. Server names are verified to be valid servers. diff --git a/server/modules/filter/namedserverfilter/namedserverfilter.hh b/server/modules/filter/namedserverfilter/namedserverfilter.hh index 37005c866..f9d4f0f8d 100644 --- a/server/modules/filter/namedserverfilter/namedserverfilter.hh +++ b/server/modules/filter/namedserverfilter/namedserverfilter.hh @@ -58,6 +58,7 @@ public: MXS_CONFIG_PARAMETER* ppParams); RegexHintFSession* newSession(MXS_SESSION *session); void diagnostics(DCB* dcb); + json_t* diagnostics_json() const; uint64_t getCapabilities(); const RegexToServers* find_servers(char* sql, int sql_len, pcre2_match_data* mdata); @@ -87,6 +88,7 @@ public: ~RegexHintFSession(); void diagnostics(DCB* pDcb); + json_t* diagnostics_json() const; int routeQuery(GWBUF* buffer); }; diff --git a/server/modules/filter/nullfilter/nullfilter.cc b/server/modules/filter/nullfilter/nullfilter.cc index 1c138ef6e..55256c592 100644 --- a/server/modules/filter/nullfilter/nullfilter.cc +++ b/server/modules/filter/nullfilter/nullfilter.cc @@ -147,6 +147,12 @@ void NullFilter::diagnostics(DCB* pDcb) dcb_printf(pDcb, "Hello, World!\n"); } +// static +json_t* NullFilter::diagnostics_json() const +{ + return NULL; +} + uint64_t NullFilter::getCapabilities() { return m_capabilities; diff --git a/server/modules/filter/nullfilter/nullfilter.hh b/server/modules/filter/nullfilter/nullfilter.hh index 6bb909379..6344b5cea 100644 --- a/server/modules/filter/nullfilter/nullfilter.hh +++ b/server/modules/filter/nullfilter/nullfilter.hh @@ -25,6 +25,7 @@ public: NullFilterSession* newSession(MXS_SESSION* pSession); void diagnostics(DCB* pDcb); + json_t* diagnostics_json() const; uint64_t getCapabilities(); diff --git a/server/modules/filter/qlafilter/qlafilter.c b/server/modules/filter/qlafilter/qlafilter.c index d28472c03..0b852891a 100644 --- a/server/modules/filter/qlafilter/qlafilter.c +++ b/server/modules/filter/qlafilter/qlafilter.c @@ -82,6 +82,7 @@ static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); /** @@ -182,6 +183,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No client reply diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -613,6 +615,51 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + QLA_INSTANCE *my_instance = (QLA_INSTANCE*)instance; + QLA_SESSION *my_session = (QLA_SESSION*)fsession; + + json_t* rval = json_object(); + + if (my_session) + { + json_object_set_new(rval, "session_filename", json_string(my_session->filename)); + } + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + + if (my_instance->user_name) + { + json_object_set_new(rval, "user", json_string(my_instance->user_name)); + } + + if (my_instance->match) + { + json_object_set_new(rval, "match", json_string(my_instance->match)); + } + + if (my_instance->nomatch) + { + json_object_set_new(rval, "exclude", json_string(my_instance->nomatch)); + } + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/regexfilter/regexfilter.c b/server/modules/filter/regexfilter/regexfilter.c index dce95f63f..3e1991155 100644 --- a/server/modules/filter/regexfilter/regexfilter.c +++ b/server/modules/filter/regexfilter/regexfilter.c @@ -49,6 +49,7 @@ static void freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session); static void setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_DOWNSTREAM *downstream); static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); static char *regex_replace(const char *sql, pcre2_code *re, pcre2_match_data *study, @@ -112,6 +113,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, NULL, // No clientReply diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -407,6 +409,45 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + REGEX_INSTANCE *my_instance = (REGEX_INSTANCE*)instance; + REGEX_SESSION *my_session = (REGEX_SESSION*)fsession; + + json_t* rval = json_object(); + + json_object_set_new(rval, "match", json_string(my_instance->match)); + json_object_set_new(rval, "replace", json_string(my_instance->replace)); + + if (my_session) + { + json_object_set_new(rval, "altered", json_integer(my_session->no_change)); + json_object_set_new(rval, "unaltered", json_integer(my_session->replacements)); + } + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + + if (my_instance->user) + { + json_object_set_new(rval, "user", json_string(my_instance->user)); + } + + return rval; +} + /** * Perform a regular expression match and substitution on the SQL * diff --git a/server/modules/filter/tee/tee.c b/server/modules/filter/tee/tee.c index 713147e1e..e0a214b3f 100644 --- a/server/modules/filter/tee/tee.c +++ b/server/modules/filter/tee/tee.c @@ -107,6 +107,7 @@ static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_ static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static int clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); /** @@ -315,6 +316,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -736,6 +738,54 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + TEE_INSTANCE *my_instance = (TEE_INSTANCE*)instance; + TEE_SESSION *my_session = (TEE_SESSION*)fsession; + + json_t* rval = json_object(); + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + + json_object_set_new(rval, "service", json_string(my_instance->service->name)); + + if (my_instance->userName) + { + json_object_set_new(rval, "user", json_string(my_instance->userName)); + } + + if (my_instance->match) + { + json_object_set_new(rval, "match", json_string(my_instance->match)); + } + + if (my_instance->nomatch) + { + json_object_set_new(rval, "exclude", json_string(my_instance->nomatch)); + } + + if (my_session) + { + json_object_set_new(rval, "duplicated", json_integer(my_session->n_duped)); + json_object_set_new(rval, "rejected", json_integer(my_session->n_duped)); + } + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/topfilter/topfilter.c b/server/modules/filter/topfilter/topfilter.c index c5a72a689..7e9ba8505 100644 --- a/server/modules/filter/topfilter/topfilter.c +++ b/server/modules/filter/topfilter/topfilter.c @@ -59,6 +59,7 @@ static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, MXS_ static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static int clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); /** @@ -146,6 +147,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -624,6 +626,73 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) } } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + TOPN_INSTANCE *my_instance = (TOPN_INSTANCE*)instance; + TOPN_SESSION *my_session = (TOPN_SESSION*)fsession; + + json_t* rval = json_object(); + + json_object_set_new(rval, "report_size", json_integer(my_instance->topN)); + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + if (my_instance->user) + { + json_object_set_new(rval, "user", json_string(my_instance->user)); + } + + if (my_instance->match) + { + json_object_set_new(rval, "match", json_string(my_instance->match)); + } + + if (my_instance->exclude) + { + json_object_set_new(rval, "exclude", json_string(my_instance->exclude)); + } + + if (my_session) + { + json_object_set_new(rval, "session_filename", json_string(my_session->filename)); + + json_t* arr = json_array(); + + for (int i = 0; i < my_instance->topN; i++) + { + if (my_session->top[i]->sql) + { + double exec_time = ((my_session->top[i]->duration.tv_sec * 1000.0) + + (my_session->top[i]->duration.tv_usec / 1000.0)) / 1000.0; + + json_t* obj = json_object(); + + json_object_set_new(obj, "rank", json_integer(i + 1)); + json_object_set_new(obj, "time", json_real(exec_time)); + json_object_set_new(obj, "sql", json_string(my_session->top[i]->sql)); + + json_array_append_new(arr, obj); + } + } + + json_object_set_new(rval, "top_queries", arr); + } + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/filter/tpmfilter/tpmfilter.c b/server/modules/filter/tpmfilter/tpmfilter.c index d6dc55a20..b2eec23cb 100644 --- a/server/modules/filter/tpmfilter/tpmfilter.c +++ b/server/modules/filter/tpmfilter/tpmfilter.c @@ -86,6 +86,7 @@ static void setUpstream(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, static int routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static int clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, GWBUF *queue); static void diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb); +static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession); static uint64_t getCapabilities(MXS_FILTER* instance); static void checkNamedPipe(void *args); @@ -156,6 +157,7 @@ MXS_MODULE* MXS_CREATE_MODULE() routeQuery, clientReply, diagnostic, + diagnostic_json, getCapabilities, NULL, // No destroyInstance }; @@ -598,6 +600,51 @@ diagnostic(MXS_FILTER *instance, MXS_FILTER_SESSION *fsession, DCB *dcb) my_instance->query_delimiter); } +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + */ +static json_t* +diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESSION *fsession) +{ + TPM_INSTANCE *my_instance = (TPM_INSTANCE*)instance; + + json_t* rval = json_object(); + + if (my_instance->source) + { + json_object_set_new(rval, "source", json_string(my_instance->source)); + } + + if (my_instance->user) + { + json_object_set_new(rval, "user", json_string(my_instance->user)); + } + + if (my_instance->filename) + { + json_object_set_new(rval, "filename", json_string(my_instance->filename)); + } + + if (my_instance->delimiter) + { + json_object_set_new(rval, "delimiter", json_string(my_instance->delimiter)); + } + + if (my_instance->query_delimiter) + { + json_object_set_new(rval, "query_delimiter", json_string(my_instance->query_delimiter)); + } + + return rval; +} + /** * Capability routine. * diff --git a/server/modules/monitor/auroramon/auroramon.c b/server/modules/monitor/auroramon/auroramon.c index ce910a8c2..34f1c867b 100644 --- a/server/modules/monitor/auroramon/auroramon.c +++ b/server/modules/monitor/auroramon/auroramon.c @@ -30,6 +30,7 @@ typedef struct aurora_monitor THREAD thread; /**< Monitor thread */ char* script; /**< Launchable script */ uint64_t events; /**< Enabled monitor events */ + MXS_MONITOR* monitor; } AURORA_MONITOR; /** @@ -111,8 +112,8 @@ void update_server_status(MXS_MONITOR *monitor, MXS_MONITOR_SERVERS *database) static void monitorMain(void *arg) { - MXS_MONITOR *monitor = (MXS_MONITOR*)arg; - AURORA_MONITOR *handle = monitor->handle; + AURORA_MONITOR *handle = (AURORA_MONITOR*)arg; + MXS_MONITOR *monitor = handle->monitor; if (mysql_thread_init()) { @@ -201,6 +202,7 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) } handle->shutdown = false; + handle->monitor = mon; if (!check_monitor_permissions(mon, "SELECT @@aurora_server_id, server_id FROM " "information_schema.replica_host_status " @@ -215,7 +217,7 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) handle->script = config_copy_string(params, "script"); handle->events = config_get_enum(params, "events", mxs_monitor_event_enum_values); - if (thread_start(&handle->thread, monitorMain, mon) == NULL) + if (thread_start(&handle->thread, monitorMain, handle) == NULL) { MXS_ERROR("Failed to start monitor thread for monitor '%s'.", mon->name); auroramon_free(handle); @@ -250,6 +252,17 @@ diagnostics(DCB *dcb, const MXS_MONITOR *mon) { } +/** + * Diagnostic interface + * + * @param dcb DCB to send output + * @param mon The monitor + */ +static json_t* diagnostics_json(const MXS_MONITOR *mon) +{ + return NULL; +} + /** * The module entry point routine. It is this routine that must populate the * structure that is referred to as the "module object", this is a structure @@ -263,7 +276,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { startMonitor, stopMonitor, - diagnostics + diagnostics, + diagnostics_json }; static MXS_MODULE info = diff --git a/server/modules/monitor/galeramon/galeramon.c b/server/modules/monitor/galeramon/galeramon.c index 5ab1d5fe0..9ed720010 100644 --- a/server/modules/monitor/galeramon/galeramon.c +++ b/server/modules/monitor/galeramon/galeramon.c @@ -50,6 +50,7 @@ static bool warn_erange_on_local_index = true; static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER *params); static void stopMonitor(MXS_MONITOR *); static void diagnostics(DCB *, const MXS_MONITOR *); +static json_t* diagnostics_json(const MXS_MONITOR *); static MXS_MONITOR_SERVERS *get_candidate_master(MXS_MONITOR*); static MXS_MONITOR_SERVERS *set_cluster_master(MXS_MONITOR_SERVERS *, MXS_MONITOR_SERVERS *, int); static void disableMasterFailback(void *, int); @@ -80,7 +81,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { startMonitor, stopMonitor, - diagnostics + diagnostics, + diagnostics_json }; static MXS_MODULE info = @@ -166,8 +168,7 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) handle->galera_nodes_info = nodes_info; handle->cluster_info.c_size = 0; handle->cluster_info.c_uuid = NULL; - - spinlock_init(&handle->lock); + handle->monitor = mon; } handle->disableMasterFailback = config_get_bool(params, "disable_master_failback"); @@ -193,9 +194,13 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) return NULL; } - if (thread_start(&handle->thread, monitorMain, mon) == NULL) + if (thread_start(&handle->thread, monitorMain, handle) == NULL) { MXS_ERROR("Failed to start monitor thread for monitor '%s'.", mon->name); + hashtable_free(handle->galera_nodes_info); + MXS_FREE(handle->script); + MXS_FREE(handle); + return NULL; } return handle; @@ -242,6 +247,36 @@ diagnostics(DCB *dcb, const MXS_MONITOR *mon) } } +/** + * Diagnostic interface + * + * @param arg The monitor handle + */ +static json_t* diagnostics_json(const MXS_MONITOR *mon) +{ + json_t* rval = json_object(); + const GALERA_MONITOR *handle = (const GALERA_MONITOR *)mon->handle; + + json_object_set_new(rval, "disable_master_failback", json_boolean(handle->disableMasterFailback)); + json_object_set_new(rval, "disable_master_role_setting", json_boolean(handle->disableMasterRoleSetting)); + json_object_set_new(rval, "root_node_as_master", json_boolean(handle->root_node_as_master)); + json_object_set_new(rval, "use_priority", json_boolean(handle->use_priority)); + json_object_set_new(rval, "set_donor_nodes", json_boolean(handle->set_donor_nodes)); + + if (handle->script) + { + json_object_set_new(rval, "script", json_string(handle->script)); + } + + if (handle->cluster_info.c_uuid) + { + json_object_set_new(rval, "cluster_uuid", json_string(handle->cluster_info.c_uuid)); + json_object_set_new(rval, "cluster_size", json_integer(handle->cluster_info.c_size)); + } + + return rval; +} + /** * Monitor an individual server. Does not deal with the setting of master or * slave bits, except for clearing them when a server is not joined to the @@ -304,10 +339,10 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) /* Check if the the Galera FSM shows this node is joined to the cluster */ char *cluster_member = "SHOW STATUS WHERE Variable_name IN" - " ('wsrep_cluster_state_uuid'," - " 'wsrep_cluster_size'," - " 'wsrep_local_index'," - " 'wsrep_local_state')"; + " ('wsrep_cluster_state_uuid'," + " 'wsrep_cluster_size'," + " 'wsrep_local_index'," + " 'wsrep_local_state')"; if (mysql_query(database->con, cluster_member) == 0 && (result = mysql_store_result(database->con)) != NULL) @@ -399,7 +434,7 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) if (row[1] == NULL || !strlen(row[1])) { MXS_DEBUG("Node %s is not running Galera Cluster", - database->server->unique_name); + database->server->unique_name); info.cluster_uuid = NULL; info.joined = 0; } @@ -431,8 +466,8 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) { if (hashtable_add(table, database->server->unique_name, &info)) { - MXS_DEBUG("Added %s to galera_nodes_info", - database->server->unique_name); + MXS_DEBUG("Added %s to galera_nodes_info", + database->server->unique_name); } /* Free the info.cluster_uuid as it's been added to the table */ MXS_FREE(info.cluster_uuid); @@ -458,19 +493,15 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) static void monitorMain(void *arg) { - MXS_MONITOR* mon = (MXS_MONITOR*) arg; - GALERA_MONITOR *handle; + GALERA_MONITOR *handle = (GALERA_MONITOR*)arg; + MXS_MONITOR* mon = handle->monitor; MXS_MONITOR_SERVERS *ptr; size_t nrounds = 0; MXS_MONITOR_SERVERS *candidate_master = NULL; int master_stickiness; int is_cluster = 0; int log_no_members = 1; - mxs_monitor_event_t evtype; - spinlock_acquire(&mon->lock); - handle = (GALERA_MONITOR *) mon->handle; - spinlock_release(&mon->lock); master_stickiness = handle->disableMasterFailback; if (mysql_thread_init()) { @@ -1311,13 +1342,13 @@ static bool detect_cluster_size(const GALERA_MONITOR *handle, } else { - if (!ret && c_uuid) - { - /* This error is being logged at every monitor cycle */ - MXS_ERROR("Galera cluster cannot be set with %d members of %d:" - " not enough nodes (%d at least)", - candidate_size, n_nodes, min_cluster_size); - } + if (!ret && c_uuid) + { + /* This error is being logged at every monitor cycle */ + MXS_ERROR("Galera cluster cannot be set with %d members of %d:" + " not enough nodes (%d at least)", + candidate_size, n_nodes, min_cluster_size); + } } } diff --git a/server/modules/monitor/galeramon/galeramon.h b/server/modules/monitor/galeramon/galeramon.h index e9c7ff7c6..d7f305085 100644 --- a/server/modules/monitor/galeramon/galeramon.h +++ b/server/modules/monitor/galeramon/galeramon.h @@ -78,7 +78,6 @@ typedef struct galera_cluster_info */ typedef struct { - SPINLOCK lock; /**< The monitor spinlock */ THREAD thread; /**< Monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ @@ -96,6 +95,7 @@ typedef struct * ordered list of nodes */ HASHTABLE *galera_nodes_info; /**< Contains Galera Cluster variables of all nodes */ GALERA_CLUSTER_INFO cluster_info; /**< Contains Galera cluster info */ + MXS_MONITOR* monitor; } GALERA_MONITOR; MXS_END_DECLS diff --git a/server/modules/monitor/mmmon/mmmon.c b/server/modules/monitor/mmmon/mmmon.c index f6de0ea66..f3a18145b 100644 --- a/server/modules/monitor/mmmon/mmmon.c +++ b/server/modules/monitor/mmmon/mmmon.c @@ -13,16 +13,6 @@ /** * @file mm_mon.c - A Multi-Master Multi Muster cluster monitor - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/09/14 Massimiliano Pinto Initial implementation - * 08/05/15 Markus Makela Addition of launchable scripts - * 17/10/15 Martin Brampton Change DCB callback to hangup - * - * @endverbatim */ #define MXS_MODULE_NAME "mmmon" @@ -50,6 +40,7 @@ MXS_MODULE info = static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER *); static void stopMonitor(MXS_MONITOR *); static void diagnostics(DCB *, const MXS_MONITOR *); +static json_t* diagnostics_json(const MXS_MONITOR *); static void detectStaleMaster(void *, int); static MXS_MONITOR_SERVERS *get_current_master(MXS_MONITOR *); static bool isMySQLEvent(mxs_monitor_event_t event); @@ -70,7 +61,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { startMonitor, stopMonitor, - diagnostics + diagnostics, + diagnostics_json }; static MXS_MODULE info = @@ -136,7 +128,7 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) handle->shutdown = 0; handle->id = MXS_MONITOR_DEFAULT_ID; handle->master = NULL; - spinlock_init(&handle->lock); + handle->monitor = mon; } handle->detectStaleMaster = config_get_bool(params, "detect_stale_master"); @@ -151,9 +143,12 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) return NULL; } - if (thread_start(&handle->thread, monitorMain, mon) == NULL) + if (thread_start(&handle->thread, monitorMain, handle) == NULL) { MXS_ERROR("Failed to start monitor thread for monitor '%s'.", mon->name); + MXS_FREE(handle->script); + MXS_FREE(handle); + return NULL; } return handle; @@ -174,7 +169,7 @@ stopMonitor(MXS_MONITOR *mon) } /** - * Daignostic interface + * Diagnostic interface * * @param dcb DCB to print diagnostics * @param arg The monitor handle @@ -186,6 +181,20 @@ static void diagnostics(DCB *dcb, const MXS_MONITOR *mon) dcb_printf(dcb, "Detect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); } +/** + * Diagnostic interface + * + * @param arg The monitor handle + */ +static json_t* diagnostics_json(const MXS_MONITOR *mon) +{ + const MM_MONITOR *handle = (const MM_MONITOR *)mon->handle; + + json_t* rval = json_object(); + json_object_set_new(rval, "detect_stale_master", json_boolean(handle->detectStaleMaster)); + return rval; +} + /** * Monitor an individual server * @@ -476,16 +485,13 @@ monitorDatabase(MXS_MONITOR* mon, MXS_MONITOR_SERVERS *database) static void monitorMain(void *arg) { - MXS_MONITOR* mon = (MXS_MONITOR*) arg; - MM_MONITOR *handle; + MM_MONITOR *handle = (MM_MONITOR *)arg; + MXS_MONITOR* mon = handle->monitor; MXS_MONITOR_SERVERS *ptr; int detect_stale_master = false; MXS_MONITOR_SERVERS *root_master = NULL; size_t nrounds = 0; - spinlock_acquire(&mon->lock); - handle = (MM_MONITOR *) mon->handle; - spinlock_release(&mon->lock); detect_stale_master = handle->detectStaleMaster; if (mysql_thread_init()) diff --git a/server/modules/monitor/mmmon/mmmon.h b/server/modules/monitor/mmmon/mmmon.h index 726f8ffd6..5af8ac671 100644 --- a/server/modules/monitor/mmmon/mmmon.h +++ b/server/modules/monitor/mmmon/mmmon.h @@ -40,7 +40,6 @@ MXS_BEGIN_DECLS */ typedef struct { - SPINLOCK lock; /**< The monitor spinlock */ THREAD thread; /**< Monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ @@ -49,6 +48,7 @@ typedef struct MXS_MONITOR_SERVERS *master; /**< Master server for Master/Slave replication */ char* script; /*< Script to call when state changes occur on servers */ uint64_t events; /*< enabled events */ + MXS_MONITOR* monitor; } MM_MONITOR; MXS_END_DECLS diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 6e013b62f..73f214e2a 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -57,7 +57,6 @@ MXS_BEGIN_DECLS */ typedef struct { - SPINLOCK lock; /**< The monitor spinlock */ THREAD thread; /**< Monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ @@ -81,6 +80,7 @@ typedef struct bool warn_failover; /**< Log a warning when failover happens */ bool load_journal; /**< Whether journal file should be loaded */ time_t journal_max_age; /**< Maximum age of journal file */ + MXS_MONITOR* monitor; } MYSQL_MONITOR; /** diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index ee884b259..84ba2f40f 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -13,36 +13,6 @@ /** * @file mysql_mon.c - A MySQL replication cluster monitor - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/07/13 Mark Riddoch Initial implementation - * 11/07/13 Mark Riddoch Addition of code to check replication status - * 25/07/13 Mark Riddoch Addition of decrypt for passwords and diagnostic interface - * 20/05/14 Massimiliano Pinto Addition of support for MariadDB multimaster replication setup. - * New server field version_string is updated. - * 28/05/14 Massimiliano Pinto Added set Id and configuration options (setInverval) - * Parameters are now printed in diagnostics - * 03/06/14 Mark Ridoch Add support for maintenance mode - * 17/06/14 Massimiliano Pinto Addition of getServerByNodeId routine and first implementation for - * depth of replication for nodes. - * 23/06/14 Massimiliano Pinto Added replication consistency after replication tree computation - * 27/06/14 Massimiliano Pinto Added replication pending status in monitored server, storing there - * the status to update in server status field before - * starting the replication consistency check. - * This will also give routers a consistent "status" of all servers - * 28/08/14 Massimiliano Pinto Added detectStaleMaster feature: previous detected master will be used again, even if the replication is stopped. - * This means both IO and SQL threads are not working on slaves. - * This option is not enabled by default. - * 10/11/14 Massimiliano Pinto Addition of setNetworkTimeout for connect, read, write - * 18/11/14 Massimiliano Pinto One server only in configuration becomes master. servers=server1 must - * be present in mysql_mon and in router sections as well. - * 08/05/15 Markus Makela Added launchable scripts - * 17/10/15 Martin Brampton Change DCB callback to hangup - * - * @endverbatim */ #define MXS_MODULE_NAME "mysqlmon" @@ -79,6 +49,7 @@ static void monitorMain(void *); static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER*); static void stopMonitor(MXS_MONITOR *); static void diagnostics(DCB *, const MXS_MONITOR *); +static json_t* diagnostics_json(const MXS_MONITOR *); static MXS_MONITOR_SERVERS *getServerByNodeId(MXS_MONITOR_SERVERS *, long); static MXS_MONITOR_SERVERS *getSlaveOfNodeId(MXS_MONITOR_SERVERS *, long); static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *, int); @@ -106,7 +77,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { startMonitor, stopMonitor, - diagnostics + diagnostics, + diagnostics_json }; static MXS_MODULE info = @@ -274,7 +246,7 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params) handle->id = config_get_global_options()->id; handle->warn_failover = true; handle->load_journal = true; - spinlock_init(&handle->lock); + handle->monitor = monitor; } /** This should always be reset to NULL */ @@ -318,9 +290,13 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params) MXS_FREE(handle); handle = NULL; } - else if (thread_start(&handle->thread, monitorMain, monitor) == NULL) + else if (thread_start(&handle->thread, monitorMain, handle) == NULL) { MXS_ERROR("Failed to start monitor thread for monitor '%s'.", monitor->name); + hashtable_free(handle->server_info); + MXS_FREE(handle->script); + MXS_FREE(handle); + handle = NULL; } return handle; @@ -377,6 +353,66 @@ static void diagnostics(DCB *dcb, const MXS_MONITOR *mon) } } +/** + * Diagnostic interface + * + * @param arg The monitor handle + */ +static json_t* diagnostics_json(const MXS_MONITOR *mon) +{ + json_t* rval = json_object(); + + const MYSQL_MONITOR *handle = (const MYSQL_MONITOR *)mon->handle; + json_object_set_new(rval, "monitor_id", json_integer(handle->id)); + json_object_set_new(rval, "detect_stale_master", json_boolean(handle->detectStaleMaster)); + json_object_set_new(rval, "detect_stale_slave", json_boolean(handle->detectStaleSlave)); + json_object_set_new(rval, "detect_replication_lag", json_boolean(handle->replicationHeartbeat)); + json_object_set_new(rval, "multimaster", json_boolean(handle->multimaster)); + json_object_set_new(rval, "detect_standalone_master", json_boolean(handle->detect_standalone_master)); + json_object_set_new(rval, "failcount", json_integer(handle->failcount)); + json_object_set_new(rval, "allow_cluster_recovery", json_boolean(handle->allow_cluster_recovery)); + json_object_set_new(rval, "mysql51_replication", json_boolean(handle->mysql51_replication)); + json_object_set_new(rval, "journal_max_age", json_integer(handle->journal_max_age)); + + if (handle->script) + { + json_object_set_new(rval, "script", json_string(handle->script)); + } + + if (mon->databases) + { + json_t* arr = json_array(); + + for (MXS_MONITOR_SERVERS *db = mon->databases; db; db = db->next) + { + json_t* srv = json_object(); + MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, db->server->unique_name); + json_object_set_new(srv, "name", json_string(db->server->unique_name)); + json_object_set_new(srv, "server_id", json_integer(serv_info->server_id)); + json_object_set_new(srv, "master_id", json_integer(serv_info->master_id)); + + json_object_set_new(srv, "read_only", json_boolean(serv_info->read_only)); + json_object_set_new(srv, "slave_configured", json_boolean(serv_info->slave_configured)); + json_object_set_new(srv, "slave_io_running", json_boolean(serv_info->slave_io)); + json_object_set_new(srv, "slave_sql_running", json_boolean(serv_info->slave_sql)); + + json_object_set_new(srv, "master_binlog_file", json_string(serv_info->binlog_name)); + json_object_set_new(srv, "master_binlog_position", json_integer(serv_info->binlog_pos)); + + if (handle->multimaster) + { + json_object_set_new(srv, "master_group", json_integer(serv_info->group)); + } + + json_array_append_new(arr, srv); + } + + json_object_set_new(rval, "server_info", arr); + } + + return rval; +} + enum mysql_server_version { MYSQL_SERVER_VERSION_100, @@ -1058,8 +1094,8 @@ void do_failover(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db) static void monitorMain(void *arg) { - MXS_MONITOR* mon = (MXS_MONITOR*) arg; - MYSQL_MONITOR *handle; + MYSQL_MONITOR *handle = (MYSQL_MONITOR *) arg; + MXS_MONITOR* mon = handle->monitor; MXS_MONITOR_SERVERS *ptr; int replication_heartbeat; bool detect_stale_master; @@ -1069,9 +1105,6 @@ monitorMain(void *arg) int log_no_master = 1; bool heartbeat_checked = false; - spinlock_acquire(&mon->lock); - handle = (MYSQL_MONITOR *) mon->handle; - spinlock_release(&mon->lock); replication_heartbeat = handle->replicationHeartbeat; detect_stale_master = handle->detectStaleMaster; diff --git a/server/modules/monitor/ndbclustermon/ndbclustermon.c b/server/modules/monitor/ndbclustermon/ndbclustermon.c index ae05aec00..26b36677d 100644 --- a/server/modules/monitor/ndbclustermon/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon/ndbclustermon.c @@ -42,6 +42,7 @@ static void monitorMain(void *); static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER *params); static void stopMonitor(MXS_MONITOR *); static void diagnostics(DCB *, const MXS_MONITOR *); +static json_t* diagnostics_json(const MXS_MONITOR *); bool isNdbEvent(mxs_monitor_event_t event); @@ -62,7 +63,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { startMonitor, stopMonitor, - diagnostics + diagnostics, + diagnostics_json }; static MXS_MODULE info = @@ -127,7 +129,7 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) handle->shutdown = 0; handle->id = MXS_MONITOR_DEFAULT_ID; handle->master = NULL; - spinlock_init(&handle->lock); + handle->monitor = mon; } handle->script = config_copy_string(params, "script"); @@ -142,9 +144,12 @@ startMonitor(MXS_MONITOR *mon, const MXS_CONFIG_PARAMETER *params) return NULL; } - if (thread_start(&handle->thread, monitorMain, mon) == NULL) + if (thread_start(&handle->thread, monitorMain, handle) == NULL) { MXS_ERROR("Failed to start monitor thread for monitor '%s'.", mon->name); + MXS_FREE(handle->script); + MXS_FREE(handle); + return NULL; } return handle; @@ -175,6 +180,17 @@ diagnostics(DCB *dcb, const MXS_MONITOR *mon) { } +/** + * Diagnostic interface + * + * @param dcb DCB to send output + * @param arg The monitor handle + */ +static json_t* diagnostics_json(const MXS_MONITOR *mon) +{ + return NULL; +} + /** * Monitor an individual server * @@ -294,15 +310,11 @@ monitorDatabase(MXS_MONITOR_SERVERS *database, char *defaultUser, char *defaultP static void monitorMain(void *arg) { - MXS_MONITOR* mon = arg; - MYSQL_MONITOR *handle; + MYSQL_MONITOR *handle = (MYSQL_MONITOR*)arg; + MXS_MONITOR* mon = handle->monitor; MXS_MONITOR_SERVERS *ptr; size_t nrounds = 0; - spinlock_acquire(&mon->lock); - handle = (MYSQL_MONITOR *) mon->handle; - spinlock_release(&mon->lock); - if (mysql_thread_init()) { MXS_ERROR("Fatal : mysql_thread_init failed in monitor module. Exiting."); diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index a1f335387..91cec5568 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -248,7 +248,7 @@ int MySQLSendHandshake(DCB* dcb) GWBUF *buf; /* get the version string from service property if available*/ - if (dcb->service->version_string != NULL) + if (dcb->service->version_string[0]) { version_string = dcb->service->version_string; len_version_string = strlen(version_string); diff --git a/server/modules/routing/avrorouter/avro.c b/server/modules/routing/avrorouter/avro.c index ad79425c3..233258adb 100644 --- a/server/modules/routing/avrorouter/avro.c +++ b/server/modules/routing/avrorouter/avro.c @@ -77,6 +77,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessio static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); static void errorReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *message, @@ -157,6 +158,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, routeQuery, diagnostics, + diagnostics_json, clientReply, errorReply, getCapabilities, @@ -802,47 +804,6 @@ routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queu return avro_client_handle_request(router, client, queue); } -/* Not used -static char *event_names[] = -{ - "Invalid", "Start Event V3", "Query Event", "Stop Event", "Rotate Event", - "Integer Session Variable", "Load Event", "Slave Event", "Create File Event", - "Append Block Event", "Exec Load Event", "Delete File Event", - "New Load Event", "Rand Event", "User Variable Event", "Format Description Event", - "Transaction ID Event (2 Phase Commit)", "Begin Load Query Event", - "Execute Load Query Event", "Table Map Event", "Write Rows Event (v0)", - "Update Rows Event (v0)", "Delete Rows Event (v0)", "Write Rows Event (v1)", - "Update Rows Event (v1)", "Delete Rows Event (v1)", "Incident Event", - "Heartbeat Event", "Ignorable Event", "Rows Query Event", "Write Rows Event (v2)", - "Update Rows Event (v2)", "Delete Rows Event (v2)", "GTID Event", - "Anonymous GTID Event", "Previous GTIDS Event" -}; -*/ - -/* Not used -// New MariaDB event numbers starts from 0xa0 -static char *event_names_mariadb10[] = -{ - "Annotate Rows Event", - "Binlog Checkpoint Event", - "GTID Event", - "GTID List Event" -}; -*/ - -/** - * Display an entry from the spinlock statistics data - * - * @param dcb The DCB to print to - * @param desc Description of the statistic - * @param value The statistic value - */ -static void -spin_reporter(void *dcb, char *desc, int value) -{ - dcb_printf((DCB *) dcb, "\t\t%-35s %d\n", desc, value); -} - /** * Display router diagnostics * @@ -939,17 +900,6 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) session->gtid.domain, session->gtid.server_id, session->gtid.seq); - // TODO: Add real value for this - //dcb_printf(dcb, "\t\tAvro Transaction ID: %u\n", 0); - // TODO: Add real value for this - //dcb_printf(dcb, "\t\tAvro N.MaxTransactions: %u\n", 0); - -#if SPINLOCK_PROFILE - dcb_printf(dcb, "\tSpinlock statistics (catch_lock):\n"); - spinlock_stats(&session->catch_lock, spin_reporter, dcb); - dcb_printf(dcb, "\tSpinlock statistics (rses_lock):\n"); - spinlock_stats(&session->file_lock, spin_reporter, dcb); -#endif dcb_printf(dcb, "\t\t--------------------\n\n"); session = session->next; } @@ -957,6 +907,74 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) } } +/** + * Display router diagnostics + * + * @param instance Instance of the router + */ +static json_t* diagnostics_json(const MXS_ROUTER *router) +{ + AVRO_INSTANCE *router_inst = (AVRO_INSTANCE *)router; + + json_t* rval = json_object(); + + char pathbuf[PATH_MAX + 1]; + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", router_inst->avrodir, AVRO_PROGRESS_FILE); + + json_object_set_new(rval, "infofile", json_string(pathbuf)); + json_object_set_new(rval, "avrodir", json_string(router_inst->avrodir)); + json_object_set_new(rval, "binlogdir", json_string(router_inst->binlogdir)); + json_object_set_new(rval, "binlog_name", json_string(router_inst->binlog_name)); + json_object_set_new(rval, "binlog_pos", json_integer(router_inst->current_pos)); + + snprintf(pathbuf, sizeof(pathbuf), "%lu-%lu-%lu", router_inst->gtid.domain, + router_inst->gtid.server_id, router_inst->gtid.seq); + json_object_set_new(rval, "gtid", json_string(pathbuf)); + json_object_set_new(rval, "gtid_timestamp", json_integer(router_inst->gtid.timestamp)); + json_object_set_new(rval, "gtid_event_number", json_integer(router_inst->gtid.event_num)); + json_object_set_new(rval, "clients", json_integer(router_inst->stats.n_clients)); + + if (router_inst->clients) + { + json_t* arr = json_array(); + spinlock_acquire(&router_inst->lock); + + for (AVRO_CLIENT *session = router_inst->clients; session; session = session->next) + { + json_t* client = json_object(); + json_object_set_new(client, "uuid", json_string(session->uuid)); + json_object_set_new(client, "host", json_string(session->dcb->remote)); + json_object_set_new(client, "port", json_integer(dcb_get_port(session->dcb))); + json_object_set_new(client, "user", json_string(session->dcb->user)); + json_object_set_new(client, "format", json_string(avro_client_ouput[session->format])); + json_object_set_new(client, "state", json_string(avro_client_states[session->state])); + json_object_set_new(client, "avrofile", json_string(session->avro_binfile)); + json_object_set_new(client, "avrofile_last_block", + json_integer(session->avro_file.blocks_read)); + json_object_set_new(client, "avrofile_last_record", + json_integer(session->avro_file.records_read)); + + if (session->gtid_start.domain > 0 || session->gtid_start.server_id > 0 || + session->gtid_start.seq > 0) + { + + snprintf(pathbuf, sizeof(pathbuf), "%lu-%lu-%lu", session->gtid_start.domain, + session->gtid_start.server_id, session->gtid_start.seq); + json_object_set_new(client, "requested_gtid", json_string(pathbuf)); + } + snprintf(pathbuf, sizeof(pathbuf), "%lu-%lu-%lu", session->gtid.domain, + session->gtid.server_id, session->gtid.seq); + json_object_set_new(client, "current_gtid", json_string(pathbuf)); + json_array_append_new(arr, client); + } + spinlock_release(&router_inst->lock); + + json_object_set_new(rval, "clients", arr); + } + + return rval; +} + /** * Client Reply routine - in this case this is a message from the * master server, It should be sent to the state machine that manages diff --git a/server/modules/routing/binlogrouter/blr.c b/server/modules/routing/binlogrouter/blr.c index 322ba2be0..9b00e4155 100644 --- a/server/modules/routing/binlogrouter/blr.c +++ b/server/modules/routing/binlogrouter/blr.c @@ -96,6 +96,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessi static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, @@ -160,6 +161,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, routeQuery, diagnostics, + diagnostics_json, clientReply, errorReply, getCapabilities, @@ -242,8 +244,8 @@ createInstance(SERVICE *service, char **options) int rc = 0; char task_name[BLRM_TASK_NAME_LEN + 1] = ""; - if (service->credentials.name == NULL || - service->credentials.authdata == NULL) + if (!service->credentials.name[0] || + !service->credentials.authdata[0]) { MXS_ERROR("%s: Error: Service is missing user credentials." " Add the missing username or passwd parameter to the service.", @@ -1734,6 +1736,338 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) } } +/** + * Display router diagnostics + * + * @param instance Instance of the router + */ +static json_t* diagnostics_json(const MXS_ROUTER *router) +{ + ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router; + int minno = 0; + double min5, min10, min15, min30; + char buf[40]; + struct tm tm; + + json_t* rval = json_object(); + + minno = router_inst->stats.minno; + min30 = 0.0; + min15 = 0.0; + min10 = 0.0; + min5 = 0.0; + for (int j = 0; j < BLR_NSTATS_MINUTES; j++) + { + minno--; + if (minno < 0) + { + minno += BLR_NSTATS_MINUTES; + } + min30 += router_inst->stats.minavgs[minno]; + if (j < 15) + { + min15 += router_inst->stats.minavgs[minno]; + } + if (j < 10) + { + min10 += router_inst->stats.minavgs[minno]; + } + if (j < 5) + { + min5 += router_inst->stats.minavgs[minno]; + } + } + min30 /= 30.0; + min15 /= 15.0; + min10 /= 10.0; + min5 /= 5.0; + + /* SSL options */ + if (router_inst->ssl_enabled) + { + json_t* obj = json_object(); + + json_object_set_new(obj, "ssl_ca_cert", json_string(router_inst->service->dbref->server->server_ssl->ssl_ca_cert)); + json_object_set_new(obj, "ssl_cert", json_string(router_inst->service->dbref->server->server_ssl->ssl_cert)); + json_object_set_new(obj, "ssl_key", json_string(router_inst->service->dbref->server->server_ssl->ssl_key)); + json_object_set_new(obj, "ssl_version", json_string(router_inst->ssl_version ? router_inst->ssl_version : "MAX")); + + json_object_set_new(rval, "master_ssl", obj); + } + + /* Binlog Encryption options */ + if (router_inst->encryption.enabled) + { + json_t* obj = json_object(); + + json_object_set_new(obj, "key", json_string( + router_inst->encryption.key_management_filename)); + json_object_set_new(obj, "algorithm", json_string( + blr_get_encryption_algorithm(router_inst->encryption.encryption_algorithm))); + json_object_set_new(obj, "key_length", + json_integer(8 * router_inst->encryption.key_len)); + + json_object_set_new(rval, "master_encryption", obj); + } + + json_object_set_new(rval, "master_state", json_string(blrm_states[router_inst->master_state])); + + localtime_r(&router_inst->stats.lastReply, &tm); + asctime_r(&tm, buf); + + + json_object_set_new(rval, "binlogdir", json_string(router_inst->binlogdir)); + json_object_set_new(rval, "heartbeat", json_integer(router_inst->heartbeat)); + json_object_set_new(rval, "master_starts", json_integer(router_inst->stats.n_masterstarts)); + json_object_set_new(rval, "master_reconnects", json_integer(router_inst->stats.n_delayedreconnects)); + json_object_set_new(rval, "binlog_name", json_string(router_inst->binlog_name)); + json_object_set_new(rval, "binlog_position", json_integer(router_inst->current_pos)); + + if (router_inst->trx_safe) + { + if (router_inst->pending_transaction.state != BLRM_NO_TRANSACTION) + { + json_object_set_new(rval, "current_trx_position", json_integer(router_inst->binlog_position)); + } + } + + json_object_set_new(rval, "slaves", json_integer(router_inst->stats.n_slaves)); + json_object_set_new(rval, "session_events", json_integer(router_inst->stats.n_binlogs_ses)); + json_object_set_new(rval, "total_events", json_integer(router_inst->stats.n_binlogs)); + json_object_set_new(rval, "bad_crc_count", json_integer(router_inst->stats.n_badcrc)); + + minno = router_inst->stats.minno - 1; + if (minno == -1) + { + minno += BLR_NSTATS_MINUTES; + } + + json_object_set_new(rval, "events_0", json_real(router_inst->stats.minavgs[minno])); + json_object_set_new(rval, "events_5", json_real(min5)); + json_object_set_new(rval, "events_10", json_real(min10)); + json_object_set_new(rval, "events_15", json_real(min15)); + json_object_set_new(rval, "events_30", json_real(min30)); + + json_object_set_new(rval, "fake_events", json_integer(router_inst->stats.n_fakeevents)); + + json_object_set_new(rval, "artificial_events", json_integer(router_inst->stats.n_artificial)); + + json_object_set_new(rval, "binlog_errors", json_integer(router_inst->stats.n_binlog_errors)); + json_object_set_new(rval, "binlog_rotates", json_integer(router_inst->stats.n_rotates)); + json_object_set_new(rval, "heartbeat_events", json_integer(router_inst->stats.n_heartbeats)); + json_object_set_new(rval, "events_read", json_integer(router_inst->stats.n_reads)); + json_object_set_new(rval, "residual_packets", json_integer(router_inst->stats.n_residuals)); + + double average_packets = router_inst->stats.n_reads != 0 ? + ((double)router_inst->stats.n_binlogs / router_inst->stats.n_reads) : 0; + + json_object_set_new(rval, "average_events_per_packets", json_real(average_packets)); + + spinlock_acquire(&router_inst->lock); + if (router_inst->stats.lastReply) + { + if (buf[strlen(buf) - 1] == '\n') + { + buf[strlen(buf) - 1] = '\0'; + } + + json_object_set_new(rval, "latest_event", json_string(buf)); + + if (!router_inst->mariadb10_compat) + { + json_object_set_new(rval, "latest_event_type", json_string( + (router_inst->lastEventReceived <= MAX_EVENT_TYPE) ? + event_names[router_inst->lastEventReceived] : "unknown")); + } + else + { + char *ptr = NULL; + if (router_inst->lastEventReceived <= MAX_EVENT_TYPE) + { + ptr = event_names[router_inst->lastEventReceived]; + } + else + { + /* Check MariaDB 10 new events */ + if (router_inst->lastEventReceived >= MARIADB_NEW_EVENTS_BEGIN && + router_inst->lastEventReceived <= MAX_EVENT_TYPE_MARIADB10) + { + ptr = event_names_mariadb10[(router_inst->lastEventReceived - MARIADB_NEW_EVENTS_BEGIN)]; + } + } + + + json_object_set_new(rval, "latest_event_type", json_string((ptr != NULL) ? ptr : "unknown")); + + if (router_inst->mariadb_gtid && + router_inst->last_mariadb_gtid[0]) + { + json_object_set_new(rval, "latest_gtid", json_string(router_inst->last_mariadb_gtid)); + } + } + + if (router_inst->lastEventTimestamp) + { + time_t last_event = (time_t)router_inst->lastEventTimestamp; + localtime_r(&last_event, &tm); + asctime_r(&tm, buf); + if (buf[strlen(buf) - 1] == '\n') + { + buf[strlen(buf) - 1] = '\0'; + } + + json_object_set_new(rval, "latest_event_timestamp", json_string(buf)); + } + } + spinlock_release(&router_inst->lock); + + json_object_set_new(rval, "active_logs", json_boolean(router_inst->active_logs)); + json_object_set_new(rval, "reconnect_pending", json_boolean(router_inst->reconnect_pending)); + + json_t* ev = json_object(); + + for (int i = 0; i <= MAX_EVENT_TYPE; i++) + { + json_object_set_new(ev, event_names[i], json_integer(router_inst->stats.events[i])); + } + + if (router_inst->mariadb10_compat) + { + /* Display MariaDB 10 new events */ + for (int i = MARIADB_NEW_EVENTS_BEGIN; i <= MAX_EVENT_TYPE_MARIADB10; i++) + { + json_object_set_new(ev, event_names_mariadb10[(i - MARIADB_NEW_EVENTS_BEGIN)], + json_integer(router_inst->stats.events[i])); + } + } + + json_object_set_new(rval, "event_types", ev); + + if (router_inst->slaves) + { + json_t* arr = json_array(); + spinlock_acquire(&router_inst->lock); + + for (ROUTER_SLAVE *session = router_inst->slaves; session; session = session->next) + { + json_t* slave = json_object(); + minno = session->stats.minno; + min30 = 0.0; + min15 = 0.0; + min10 = 0.0; + min5 = 0.0; + for (int j = 0; j < BLR_NSTATS_MINUTES; j++) + { + minno--; + if (minno < 0) + { + minno += BLR_NSTATS_MINUTES; + } + min30 += session->stats.minavgs[minno]; + if (j < 15) + { + min15 += session->stats.minavgs[minno]; + } + if (j < 10) + { + min10 += session->stats.minavgs[minno]; + } + if (j < 5) + { + min5 += session->stats.minavgs[minno]; + } + } + min30 /= 30.0; + min15 /= 15.0; + min10 /= 10.0; + min5 /= 5.0; + + json_object_set_new(rval, "server_id", json_integer(session->serverid)); + + if (session->hostname) + { + json_object_set_new(rval, "hostname", json_string(session->hostname)); + } + if (session->uuid) + { + json_object_set_new(rval, "uuid", json_string(session->uuid)); + } + + json_object_set_new(rval, "address", json_string(session->dcb->remote)); + json_object_set_new(rval, "port", json_integer(dcb_get_port(session->dcb))); + json_object_set_new(rval, "user", json_string(session->dcb->user)); + json_object_set_new(rval, "ssl_enabled", json_boolean(session->dcb->ssl)); + json_object_set_new(rval, "state", json_string(blrs_states[session->state])); + json_object_set_new(rval, "next_sequence", json_integer(session->seqno)); + json_object_set_new(rval, "binlog_file", json_string(session->binlogfile)); + json_object_set_new(rval, "binlog_pos", json_integer(session->binlog_pos)); + json_object_set_new(rval, "crc", json_boolean(!session->nocrc)); + + json_object_set_new(rval, "requests", json_integer(session->stats.n_requests)); + json_object_set_new(rval, "events_sent", json_integer(session->stats.n_events)); + json_object_set_new(rval, "bytes_sent", json_integer(session->stats.n_bytes)); + json_object_set_new(rval, "data_bursts", json_integer(session->stats.n_bursts)); + + if (router_inst->send_slave_heartbeat) + { + json_object_set_new(rval, "heartbeat_period", json_integer(session->heartbeat)); + } + + minno = session->stats.minno - 1; + if (minno == -1) + { + minno += BLR_NSTATS_MINUTES; + } + + if (session->lastEventTimestamp + && router_inst->lastEventTimestamp && session->lastEventReceived != HEARTBEAT_EVENT) + { + unsigned long seconds_behind; + time_t session_last_event = (time_t)session->lastEventTimestamp; + + if (router_inst->lastEventTimestamp > session->lastEventTimestamp) + { + seconds_behind = router_inst->lastEventTimestamp - session->lastEventTimestamp; + } + else + { + seconds_behind = 0; + } + + localtime_r(&session_last_event, &tm); + asctime_r(&tm, buf); + trim(buf); + json_object_set_new(rval, "last_binlog_event_timestamp", json_string(buf)); + json_object_set_new(rval, "seconds_behind_master", json_integer(seconds_behind)); + } + + const char *mode = "connected"; + + if (session->state) + { + if ((session->cstate & CS_WAIT_DATA) == CS_WAIT_DATA) + { + mode = "wait-for-data"; + } + else + { + mode = "catchup"; + } + } + + json_object_set_new(slave, "mode", json_string(mode)); + + json_array_append_new(arr, slave); + } + spinlock_release(&router_inst->lock); + + json_object_set_new(rval, "slaves", arr); + + } + + return rval; +} + /** * Client Reply routine - in this case this is a message from the * master server, It should be sent to the state machine that manages diff --git a/server/modules/routing/binlogrouter/test/testbinlog.c b/server/modules/routing/binlogrouter/test/testbinlog.c index a5e39f75d..3a0ae59f0 100644 --- a/server/modules/routing/binlogrouter/test/testbinlog.c +++ b/server/modules/routing/binlogrouter/test/testbinlog.c @@ -97,8 +97,8 @@ int main(int argc, char **argv) set_libdir(MXS_STRDUP_A("..")); service = service_alloc("test_service", "binlogrouter"); - service->credentials.name = MXS_STRDUP_A("foo"); - service->credentials.authdata = MXS_STRDUP_A("bar"); + strcpy(service->credentials.name, "foo"); + strcpy(service->credentials.authdata, "bar"); { char *lasts; diff --git a/server/modules/routing/cli/cli.c b/server/modules/routing/cli/cli.c index dcdf4c420..dbd7e3919 100644 --- a/server/modules/routing/cli/cli.c +++ b/server/modules/routing/cli/cli.c @@ -49,6 +49,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessi static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static uint64_t getCapabilities(MXS_ROUTER* instance); extern int execute_cmd(CLI_SESSION *cli); @@ -78,6 +79,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, execute, diagnostics, + diagnostics_json, NULL, NULL, getCapabilities, @@ -290,6 +292,11 @@ diagnostics(MXS_ROUTER *instance, DCB *dcb) return; /* Nothing to do currently */ } +static json_t* diagnostics_json(const MXS_ROUTER *instance) +{ + return NULL; +} + static uint64_t getCapabilities(MXS_ROUTER *instance) { return RCAP_TYPE_NONE; diff --git a/server/modules/routing/debugcli/debugcli.c b/server/modules/routing/debugcli/debugcli.c index 073535d2b..ef3610be5 100644 --- a/server/modules/routing/debugcli/debugcli.c +++ b/server/modules/routing/debugcli/debugcli.c @@ -48,6 +48,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessi static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static uint64_t getCapabilities(MXS_ROUTER* instance); extern int execute_cmd(CLI_SESSION *cli); @@ -77,6 +78,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, execute, diagnostics, + diagnostics_json, NULL, NULL, getCapabilities, @@ -293,6 +295,17 @@ diagnostics(MXS_ROUTER *instance, DCB *dcb) return; /* Nothing to do currently */ } +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static json_t* diagnostics_json(const MXS_ROUTER *instance) +{ + return NULL; +} + static uint64_t getCapabilities(MXS_ROUTER* instance) { return RCAP_TYPE_NONE; diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 1ec2e4576..6d44e7245 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1513,6 +1513,34 @@ static void alterMonitor(DCB *dcb, MXS_MONITOR *monitor, char *v1, char *v2, cha } +static void alterService(DCB *dcb, SERVICE *service, char *v1, char *v2, char *v3, + char *v4, char *v5, char *v6, char *v7, char *v8, char *v9, + char *v10, char *v11) +{ + char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11}; + const int items = sizeof(values) / sizeof(values[0]); + + for (int i = 0; i < items && values[i]; i++) + { + char *key = values[i]; + char *value = strchr(key, '='); + + if (value) + { + *value++ = '\0'; + + if (!runtime_alter_service(service, key, value)) + { + dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value); + } + } + else + { + dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]); + } + } +} + struct subcommand alteroptions[] = { { @@ -1574,6 +1602,37 @@ struct subcommand alteroptions[] = ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING } }, + { + "service", 2, 12, alterService, + "Alter service parameters", + "Usage: alter service NAME KEY=VALUE ...\n" + "\n" + "Parameters:\n" + "NAME Service name\n" + "KEY=VALUE List of `key=value` pairs separated by spaces\n" + "\n" + "All services support the following values for KEY:\n" + "user Username used when connecting to servers\n" + "password Password used when connecting to servers\n" + "enable_root_user Allow root user access through this service\n" + "max_retry_interval Maximum restart retry interval\n" + "max_connections Maximum connection limit\n" + "connection_timeout Client idle timeout in seconds\n" + "auth_all_servers Retrieve authentication data from all servers\n" + "strip_db_esc Strip escape characters from database names\n" + "localhost_match_wildcard_host Match wildcard host to 'localhost' address\n" + "version_string The version string given to client connections\n" + "weightby Weighting parameter name\n" + "log_auth_warnings Log authentication warnings\n" + "retry_on_failure Retry service start on failure\n" + "\n" + "Example: alter service my-service user=maxuser password=maxpwd", + { + 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 } diff --git a/server/modules/routing/hintrouter/hintrouter.cc b/server/modules/routing/hintrouter/hintrouter.cc index 88d7dd4f3..79db39110 100644 --- a/server/modules/routing/hintrouter/hintrouter.cc +++ b/server/modules/routing/hintrouter/hintrouter.cc @@ -163,6 +163,33 @@ void HintRouter::diagnostics(DCB* pOut) dcb_printf(pOut, "\tQueries routed to all servers: %d\n", m_routed_to_all); } +json_t* HintRouter::diagnostics_json() const +{ + HR_ENTRY(); + + json_t* rval = json_object(); + json_t* arr = json_array(); + + for (int i = 0; default_action_values[i].name; i++) + { + if (default_action_values[i].enum_value == (uint64_t)m_default_action) + { + json_array_append_new(arr, json_string(default_action_values[i].name)); + } + } + + json_object_set_new(rval, "default_action", arr); + json_object_set_new(rval, "default_server", json_string(m_default_server.c_str())); + json_object_set_new(rval, "max_slave_connections", json_integer(m_max_slaves)); + json_object_set_new(rval, "total_slave_connections", json_integer(m_total_slave_conns)); + json_object_set_new(rval, "route_master", json_integer(m_routed_to_master)); + json_object_set_new(rval, "route_slave", json_integer(m_routed_to_slave)); + json_object_set_new(rval, "route_named_server", json_integer(m_routed_to_named)); + json_object_set_new(rval, "route_all", json_integer(m_routed_to_all)); + + return rval; +} + Dcb HintRouter::connect_to_backend(MXS_SESSION* session, SERVER_REF* sref, HintRouterSession::BackendMap* all_backends) { diff --git a/server/modules/routing/hintrouter/hintrouter.hh b/server/modules/routing/hintrouter/hintrouter.hh index 67f5bffcf..e072ba837 100644 --- a/server/modules/routing/hintrouter/hintrouter.hh +++ b/server/modules/routing/hintrouter/hintrouter.hh @@ -23,6 +23,7 @@ public: static HintRouter* create(SERVICE* pService, char** pzOptions); HintRouterSession* newSession(MXS_SESSION *pSession); void diagnostics(DCB* pOut); + json_t* diagnostics_json() const; uint64_t getCapabilities() const { return RCAP_TYPE_NONE; diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 08b191896..c84093aac 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -70,6 +70,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessi static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static uint64_t getCapabilities(MXS_ROUTER* instance); static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, @@ -103,6 +104,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, execute, diagnostics, + diagnostics_json, NULL, handleError, getCapabilities, @@ -374,6 +376,18 @@ diagnostics(MXS_ROUTER *instance, DCB *dcb) return; /* Nothing to do currently */ } +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static json_t* +diagnostics_json(const MXS_ROUTER *instance) +{ + return NULL; +} + /** * Capabilities interface for the rotuer * diff --git a/server/modules/routing/readconnroute/readconnroute.c b/server/modules/routing/readconnroute/readconnroute.c index 2c05ebd0a..86edd3fa4 100644 --- a/server/modules/routing/readconnroute/readconnroute.c +++ b/server/modules/routing/readconnroute/readconnroute.c @@ -94,6 +94,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_sessio static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errbuf, @@ -123,6 +124,7 @@ MXS_MODULE* MXS_CREATE_MODULE() freeSession, routeQuery, diagnostics, + diagnostics_json, clientReply, handleError, getCapabilities, @@ -619,7 +621,7 @@ static void diagnostics(MXS_ROUTER *router, DCB *dcb) { ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *) router; - char *weightby; + const char *weightby = serviceGetWeightingParameter(router_inst->service); dcb_printf(dcb, "\tNumber of router sessions: %d\n", router_inst->stats.n_sessions); @@ -627,8 +629,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) router_inst->service->stats.n_current); dcb_printf(dcb, "\tNumber of queries forwarded: %d\n", router_inst->stats.n_queries); - if ((weightby = serviceGetWeightingParameter(router_inst->service)) - != NULL) + if (*weightby) { dcb_printf(dcb, "\tConnection distribution based on %s " "server parameter.\n", @@ -645,6 +646,31 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) } } +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static json_t* diagnostics_json(const MXS_ROUTER *router) +{ + ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router; + json_t* rval = json_object(); + + json_object_set_new(rval, "connections", json_integer(router_inst->stats.n_sessions)); + json_object_set_new(rval, "current_connections", json_integer(router_inst->service->stats.n_current)); + json_object_set_new(rval, "queries", json_integer(router_inst->stats.n_queries)); + + const char *weightby = serviceGetWeightingParameter(router_inst->service); + + if (*weightby) + { + json_object_set_new(rval, "weightby", json_string(weightby)); + } + + return rval; +} + /** * Client Reply routine * diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 0575abc37..5d9eca1d0 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -75,6 +75,7 @@ static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static json_t* diagnostics_json(const MXS_ROUTER *instance); static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, @@ -151,6 +152,7 @@ MXS_MODULE *MXS_CREATE_MODULE() freeSession, routeQuery, diagnostics, + diagnostics_json, clientReply, handleError, getCapabilities, @@ -649,7 +651,7 @@ static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, static void diagnostics(MXS_ROUTER *instance, DCB *dcb) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; - char *weightby; + const char *weightby = serviceGetWeightingParameter(router->service); double master_pct = 0.0, slave_pct = 0.0, all_pct = 0.0; dcb_printf(dcb, "\n"); @@ -693,7 +695,7 @@ static void diagnostics(MXS_ROUTER *instance, DCB *dcb) dcb_printf(dcb, "\tNumber of queries forwarded to all: %" PRIu64 " (%.2f%%)\n", router->stats.n_all, all_pct); - if ((weightby = serviceGetWeightingParameter(router->service)) != NULL) + if (*weightby) { dcb_printf(dcb, "\tConnection distribution based on %s " "server parameter.\n", @@ -711,6 +713,57 @@ static void diagnostics(MXS_ROUTER *instance, DCB *dcb) } } +/** + * @brief Diagnostics routine (API) + * + * Print query router statistics to the DCB passed in + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static json_t* diagnostics_json(const MXS_ROUTER *instance) +{ + ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; + + json_t* rval = json_object(); + + json_object_set_new(rval, "use_sql_variables_in", + json_string(mxs_target_to_str(router->rwsplit_config.use_sql_variables_in))); + json_object_set_new(rval, "slave_selection_criteria", + json_string(select_criteria_to_str(router->rwsplit_config.slave_selection_criteria))); + json_object_set_new(rval, "master_failure_mode", + json_string(failure_mode_to_str(router->rwsplit_config.master_failure_mode))); + json_object_set_new(rval, "max_slave_replication_lag", + json_integer(router->rwsplit_config.max_slave_replication_lag)); + json_object_set_new(rval, "retry_failed_reads", + json_boolean(router->rwsplit_config.retry_failed_reads)); + json_object_set_new(rval, "strict_multi_stmt", + json_boolean(router->rwsplit_config.strict_multi_stmt)); + json_object_set_new(rval, "disable_sescmd_history", + json_boolean(router->rwsplit_config.disable_sescmd_history)); + json_object_set_new(rval, "max_sescmd_history", + json_integer(router->rwsplit_config.max_sescmd_history)); + json_object_set_new(rval, "master_accept_reads", + json_boolean(router->rwsplit_config.master_accept_reads)); + + + json_object_set_new(rval, "connections", json_integer(router->stats.n_sessions)); + json_object_set_new(rval, "current_connections", json_integer(router->service->stats.n_current)); + json_object_set_new(rval, "queries", json_integer(router->stats.n_queries)); + json_object_set_new(rval, "route_master", json_integer(router->stats.n_master)); + json_object_set_new(rval, "route_slave", json_integer(router->stats.n_slave)); + json_object_set_new(rval, "route_all", json_integer(router->stats.n_all)); + + const char *weightby = serviceGetWeightingParameter(router->service); + + if (*weightby) + { + json_object_set_new(rval, "weightby", json_string(weightby)); + } + + return rval; +} + /** * @brief Check if we have received a complete reply from the backend * diff --git a/server/modules/routing/schemarouter/schemarouterinstance.cc b/server/modules/routing/schemarouter/schemarouterinstance.cc index 44f428782..2b71b16e8 100644 --- a/server/modules/routing/schemarouter/schemarouterinstance.cc +++ b/server/modules/routing/schemarouter/schemarouterinstance.cc @@ -333,6 +333,32 @@ void SchemaRouter::diagnostics(DCB* dcb) dcb_printf(dcb, "\n"); } +json_t* SchemaRouter::diagnostics_json() const +{ + double sescmd_pct = m_stats.n_sescmd != 0 ? + 100.0 * ((double)m_stats.n_sescmd / (double)m_stats.n_queries) : + 0.0; + + json_t* rval = json_object(); + json_object_set_new(rval, "queries", json_integer(m_stats.n_queries)); + json_object_set_new(rval, "sescmd_percentage", json_real(sescmd_pct)); + json_object_set_new(rval, "longest_sescmd_chain", json_integer(m_stats.longest_sescmd)); + json_object_set_new(rval, "times_sescmd_limit_exceeded", json_integer(m_stats.n_hist_exceeded)); + + /** Session time statistics */ + if (m_stats.sessions > 0) + { + json_object_set_new(rval, "longest_session", json_real(m_stats.ses_longest)); + json_object_set_new(rval, "shortest_session", json_real(m_stats.ses_shortest)); + json_object_set_new(rval, "average_session", json_real(m_stats.ses_average)); + } + + json_object_set_new(rval, "shard_map_hits", json_integer(m_stats.shmap_cache_hit)); + json_object_set_new(rval, "shard_map_misses", json_integer(m_stats.shmap_cache_miss)); + + return rval; +} + uint64_t SchemaRouter::getCapabilities() { return RCAP_TYPE_NONE; diff --git a/server/modules/routing/schemarouter/schemarouterinstance.hh b/server/modules/routing/schemarouter/schemarouterinstance.hh index ffc066774..3b9856119 100644 --- a/server/modules/routing/schemarouter/schemarouterinstance.hh +++ b/server/modules/routing/schemarouter/schemarouterinstance.hh @@ -38,6 +38,7 @@ public: static SchemaRouter* create(SERVICE* pService, char** pzOptions); SchemaRouterSession* newSession(MXS_SESSION* pSession); void diagnostics(DCB* pDcb); + json_t* diagnostics_json() const; uint64_t getCapabilities(); private: