diff --git a/CMakeLists.txt b/CMakeLists.txt index 218964698..69f9539ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,10 +154,6 @@ if(GCOV) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") endif() -if(FAKE_CODE) - set(FLAGS "${FLAGS} -DFAKE_CODE" CACHE STRING "Compilation flags" FORCE) -endif() - if(PROFILE) message(STATUS "Profiling executables") set(FLAGS "${FLAGS} -pg " CACHE STRING "Compilation flags" FORCE) diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index 4904c92a5..0b2fe2a43 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -31,6 +31,7 @@ - [Debug and Diagnostic Support](Reference/Debug-And-Diagnostic-Support.md) - [Routing Hints](Reference/Hint-Syntax.md) - [MaxBinlogCheck](Reference/MaxBinlogCheck.md) + - [MaxScale REST API](REST-API/API.md) ## Tutorials @@ -106,6 +107,15 @@ Documentation for MaxScale protocol modules. - [Change Data Capture (CDC) Protocol](Protocols/CDC.md) - [Change Data Capture (CDC) Users](Protocols/CDC_users.md) +## Authenticators + +A short description of the authentication module type can be found in the +[Authentication Modules](Authenticators/Authentication-Modules.md) +document. + + - [MySQL Authenticator](Authenticators/MySQL-Authenticator.md) + - [GSSAPI Authenticator](Authenticators/GSSAPI-Authenticator.md) + ## Utilities - [RabbitMQ Consumer Client](Filters/RabbitMQ-Consumer-Client.md) diff --git a/Documentation/Filters/Maxrows.md b/Documentation/Filters/Maxrows.md new file mode 100644 index 000000000..47edb688f --- /dev/null +++ b/Documentation/Filters/Maxrows.md @@ -0,0 +1,70 @@ +# Maxrows + +## Overview +The maxrows filter is capable of restricting the amount of rows that a SELECT, + a prepared statement or stored procedure could return to the client application. + +If a resultset from a backend server has more rows than the configured limit +or the resultset size exceeds the configured size, + an empty result will be sent to the client. + +## Configuration + +The maxrows filter is easy to configure and to add to any existing service. + +``` +[MaxRows] +type=filter +module=maxrows + +[MaxRows Routing Service] +type=service +... +filters=maxrows +``` + +### Filter Parameters + +The maxrows filter has no mandatory parameters. +Optional parameters are: + +#### `max_resultset_rows` + +Specifies the maximum number of rows a resultset can have in order to be returned + to the user. + +If a resultset is larger than this an empty result will be sent instead. + +``` +max_resultset_rows=1000 +``` +Zero or a negative value is interpreted as no limitation. + +The default value is `-1`. + +#### `max_resultset_size` + +Specifies the maximum size a resultset can have, measured in kibibytes, +in order to be sent to the client. A resultset larger than this, will +not be sent: an empty resultset will be sent instead. + +``` +max_resultset_size=128 +``` +The default value is 64. + +#### `debug` + +An integer value, using which the level of debug logging made by the maxrows +filter can be controlled. The value is actually a bitfield with different bits +denoting different logging. + + * ` 0` (`0b00000`) No logging is made. + * ` 1` (`0b00001`) A decision to handle data form server is logged. + * ` 2` (`0b00010`) Reached max_resultset_rows or max_resultset_size is logged. + +Default is `0`. To log everything, give `debug` a value of `3`. + +``` +debug=2 +``` diff --git a/Documentation/Filters/Query-Log-All-Filter.md b/Documentation/Filters/Query-Log-All-Filter.md index a52f7a399..db987c84a 100644 --- a/Documentation/Filters/Query-Log-All-Filter.md +++ b/Documentation/Filters/Query-Log-All-Filter.md @@ -30,8 +30,10 @@ The QLA filter accepts the following options. |ignorecase|Use case-insensitive matching | |case |Use case-sensitive matching | |extended |Use extended regular expression syntax (ERE)| - -To use multiple filter options, list them in a comma-separated list. +|session_file| Use session-specific file (default)| +|unified_file| Use one file for all sessions| +|flush_writes| Flush after every write| +To use multiple filter options, list them in a comma-separated list. If no file settings are given, default will be used. Multiple file settings can be enabled simultaneously. ``` options=case,extended diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index bf0b33fde..fc30ab168 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -378,6 +378,16 @@ Configure the directory where the executable files reside. All internal processe execdir=/usr/local/bin/ ``` +#### `persistdir` + +Configure the directory where persisted configurations are stored. When a new +server is created via MaxAdmin, it will be stored in this directory. Do not use +or modify the contents of this directory, use _/etc/maxscale.cnf.d/_ instead. + +``` +persistdir=/var/lib/maxscale/maxscale.cnf.d/ +``` + #### `language` Set the folder where the errmsg.sys file is located in. MariaDB MaxScale will look for the errmsg.sys file installed with MariaDB MaxScale from this folder. diff --git a/Documentation/Monitors/Galera-Monitor.md b/Documentation/Monitors/Galera-Monitor.md index a74545ad7..d4d65488e 100644 --- a/Documentation/Monitors/Galera-Monitor.md +++ b/Documentation/Monitors/Galera-Monitor.md @@ -64,7 +64,16 @@ use_priority=true ## Interaction with Server Priorities -If the `use_priority` option is set and a server is configured with the `priority=` parameter, galeramon will use that as the basis on which the master node is chosen. This requires the `disable_master_role_setting` to be undefined or disabled. The server with the lowest value in `priority` will be chosen as the master node when a replacement Galera node is promoted to a master server inside MaxScale. +If the `use_priority` option is set and a server is configured with the +`priority=` parameter, galeramon will use that as the basis on which the +master node is chosen. This requires the `disable_master_role_setting` to be +undefined or disabled. The server with the lowest positive value in _priority_ +will be chosen as the master node when a replacement Galera node is promoted to +a master server inside MaxScale. + +Nodes with a non-positive value (_priority_ <= 0) will never be chosen as the master. This allows +you to mark some servers as permanent slaves by assigning a non-positive value +into _priority_. Here is an example with two servers. @@ -86,8 +95,21 @@ type=server address=192.168.122.103 port=3306 priority=2 + +[node-4] +type=server +address=192.168.122.104 +port=3306 +priority=0 ``` -In this example `node-1` is always used as the master if available. If `node-1` is not available, then the next node with the highest priority rank is used. In this case it would be `node-3`. If both `node-1` and `node-3` were down, then `node-2` would be used. Nodes without priority are considered as having the lowest priority rank and will be used only if all nodes with priority ranks are not available. +In this example `node-1` is always used as the master if available. If `node-1` +is not available, then the next node with the highest priority rank is used. In +this case it would be `node-3`. If both `node-1` and `node-3` were down, then +`node-2` would be used. Because `node-4` has a value of 0 in _priority_, it will +never be the master. Nodes without priority are considered as having the lowest +priority rank and will be used only if all nodes with priority ranks are not +available. -With priority ranks you can control the order in which MaxScale chooses the master node. This will allow for a controlled failure and replacement of nodes. +With priority ranks you can control the order in which MaxScale chooses the +master node. This will allow for a controlled failure and replacement of nodes. diff --git a/Documentation/REST-API/API.md b/Documentation/REST-API/API.md new file mode 100644 index 000000000..afa08573b --- /dev/null +++ b/Documentation/REST-API/API.md @@ -0,0 +1,453 @@ +# REST API design document + +This document describes the version 1 of the MaxScale REST API. + +## Table of Contents + +- [HTTP Headers](#http-headers) + - [Request Headers](#request-headers) + - [Response Headers](#response-headers) +- [Response Codes](#response-codes) + - [2xx Success](#2xx-success) + - [3xx Redirection](#3xx-redirection) + - [4xx Client Error](#4xx-client-error) + - [5xx Server Error](#5xx-server-error) +- [Resources](#resources) +- [Common Request Parameter](#common-request-parameters) + +## HTTP Headers + +### Request Headers + +REST makes use of the HTTP protocols in its aim to provide a natural way to +understand the workings of an API. The following request headers are understood +by this API. + +#### Accept-Charset + +Acceptable character sets. + +#### Authorization + +Credentials for authentication. + +#### Content-Type + +All PUT and POST requests must use the `Content-Type: application/json` media +type and the request body must be a valid JSON representation of a resource. All +PATCH requests must use the `Content-Type: application/json-patch` media type +and the request body must be a valid JSON Patch document which is applied to the +resource. Curently, only _add_, _remove_, _replace_ and _test_ operations are +supported. + +Read the [JSON Patch](https://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08) +draft for more details on how to use it with PATCH. + +#### Date + +This header is required and should be in the RFC 1123 standard form, e.g. Mon, +18 Nov 2013 08:14:29 -0600. Please note that the date must be in English. It +will be checked by the API for being close to the current date and time. + +#### Host + +The address and port of the server. + +#### If-Match + +The request is performed only if the provided ETag value matches the one on the +server. This field should be used with PUT requests to prevent concurrent +updates to the same resource. + +The value of this header must be a value from the `ETag` header retrieved from +the same resource at an earlier point in time. + +#### If-Modified-Since + +If the content has not changed the server responds with a 304 status code. If +the content has changed the server responds with a 200 status code and the +requested resource. + +The value of this header must be a date value in the +["HTTP-date"](https://www.ietf.org/rfc/rfc2822.txt) format. + +#### If-None-Match + +If the content has not changed the server responds with a 304 status code. If +the content has changed the server responds with a 200 status code and the +requested resource. + +The value of this header must be a value from the `ETag` header retrieved from +the same resource at an earlier point in time. + +#### If-Unmodified-Since + +The request is performed only if the requested resource has not been modified +since the provided date. + +The value of this header must be a date value in the +["HTTP-date"](https://www.ietf.org/rfc/rfc2822.txt) format. + +#### X-HTTP-Method-Override + +Some clients only support GET and PUT requests. By providing the string value of +the intended method in the `X-HTTP-Method-Override` header, a client can perform +a POST, PATCH or DELETE request with the PUT method +(e.g. `X-HTTP-Method-Override: PATCH`). + +_TODO: Add API version header?_ + +### Response Headers + +#### Allow + +All resources return the Allow header with the supported HTTP methods. For +example the resource `/service` will always return the `Accept: GET, PATCH, PUT` +header. + +#### Accept-Patch + +All PATCH capable resources return the `Accept-Patch: application/json-patch` +header. + +#### Date + +Returns the RFC 1123 standard form date when the reply was sent. The date is in +English and it uses the server's local timezone. + +#### ETag + +An identifier for a specific version of a resource. The value of this header +changes whenever a resource is modified. + +When the client sends the `If-Match` or `If-None-Match` header, the provided +value should be the value of the `ETag` header of an earlier GET. + +#### Last-Modified + +The date when the resource was last modified in "HTTP-date" format. + +#### Location + +If an out of date resource location is requested, a HTTP return code of 3XX with +the `Location` header is returned. The value of the header contains the new +location of the requested resource as a relative URI. + +#### WWW-Authenticate + +The requested authentication method. For example, `WWW-Authenticate: Basic` +would require basic HTTP authentication. + +## Response Codes + +Every HTTP response starts with a line with a return code which indicates the +outcome of the request. The API uses some of the standard HTTP values: + +### 2xx Success + +- 200 OK + + - Successful HTTP requests, response has a body. + +- 201 Created + + - A new resource was created. + +- 202 Accepted + + - The request has been accepted for processing, but the processing has not + been completed. + +- 204 No Content + + - Successful HTTP requests, response has no body. + +### 3xx Redirection + +This class of status code indicates the client must take additional action to +complete the request. + +- 301 Moved Permanently + + - This and all future requests should be directed to the given URI. + +- 302 Found + + - The response to the request can be found under another URI using the same + method as in the original request. + +- 303 See Other + + - The response to the request can be found under another URI using a GET + method. + +- 304 Not Modified + + - Indicates that the resource has not been modified since the version + specified by the request headers If-Modified-Since or If-None-Match. + +- 307 Temporary Redirect + + - The request should be repeated with another URI but future requests should + use the original URI. + +- 308 Permanent Redirect + + - The request and all future requests should be repeated using another URI. + +### 4xx Client Error + +The 4xx class of status code is when the client seems to have erred. Except when +responding to a HEAD request, the body of the response contains a JSON +representation of the error in the following format. + +``` +{ + "error": "Method not supported", + "description": "The `/service` resource does not support POST." +} +``` + +The _error_ field contains a short error description and the _description_ field +contains a more detailed version of the error message. + +- 400 Bad Request + + - The server cannot or will not process the request due to client error. + +- 401 Unauthorized + + - Authentication is required. The response includes a WWW-Authenticate header. + +- 403 Forbidden + + - The request was a valid request, but the client does not have the necessary + permissions for the resource. + +- 404 Not Found + + - The requested resource could not be found. + +- 405 Method Not Allowed + + - A request method is not supported for the requested resource. + +- 406 Not Acceptable + + - The requested resource is capable of generating only content not acceptable + according to the Accept headers sent in the request. + +- 409 Conflict + + - Indicates that the request could not be processed because of conflict in the + request, such as an edit conflict be tween multiple simultaneous updates. + +- 411 Length Required + + - The request did not specify the length of its content, which is required by + the requested resource. + +- 412 Precondition Failed + + - The server does not meet one of the preconditions that the requester put on + the request. + +- 413 Payload Too Large + + - The request is larger than the server is willing or able to process. + +- 414 URI Too Long + + - The URI provided was too long for the server to process. + +- 415 Unsupported Media Type + + - The request entity has a media type which the server or resource does not + support. + +- 422 Unprocessable Entity + + - The request was well-formed but was unable to be followed due to semantic + errors. + +- 423 Locked + + - The resource that is being accessed is locked. + +- 428 Precondition Required + + - The origin server requires the request to be conditional. This error code is + returned when none of the `Modified-Since` or `Match` type headers are used. + +- 431 Request Header Fields Too Large + + - The server is unwilling to process the request because either an individual + header field, or all the header fields collectively, are too large. + +### 5xx Server Error + +The server failed to fulfill an apparently valid request. + +Response status codes beginning with the digit "5" indicate cases in which the +server is aware that it has encountered an error or is otherwise incapable of +performing the request. Except when responding to a HEAD request, the server +includes an entity containing an explanation of the error situation. + +``` +{ + "error": "Log rotation failed", + "description": "Failed to rotate log files: 13, Permission denied" +} +``` + +The _error_ field contains a short error description and the _description_ field +contains a more detailed version of the error message. + +- 500 Internal Server Error + + - A generic error message, given when an unexpected condition was encountered + and no more specific message is suitable. + +- 501 Not Implemented + + - The server either does not recognize the request method, or it lacks the + ability to fulfill the request. + +- 502 Bad Gateway + + - The server was acting as a gateway or proxy and received an invalid response + from the upstream server. + +- 503 Service Unavailable + + - The server is currently unavailable (because it is overloaded or down for + maintenance). Generally, this is a temporary state. + +- 504 Gateway Timeout + + - The server was acting as a gateway or proxy and did not receive a timely + response from the upstream server. + +- 505 HTTP Version Not Supported + + - The server does not support the HTTP protocol version used in the request. + +- 506 Variant Also Negotiates + + - Transparent content negotiation for the request results in a circular + reference. + +- 507 Insufficient Storage + + - The server is unable to store the representation needed to complete the + request. + +- 508 Loop Detected + + - The server detected an infinite loop while processing the request (sent in + lieu of 208 Already Reported). + +- 510 Not Extended + + - Further extensions to the request are required for the server to fulfil it. + +### Response Headers Reserved for Future Use + +The following response headers are not currently in use. Future versions of the +API could return them. + +- 206 Partial Content + + - The server is delivering only part of the resource (byte serving) due to a + range header sent by the client. + +- 300 Multiple Choices + + - Indicates multiple options for the resource from which the client may choose + (via agent-driven content negotiation). + +- 407 Proxy Authentication Required + + - The client must first authenticate itself with the proxy. + +- 408 Request Timeout + + - The server timed out waiting for the request. According to HTTP + specifications: "The client did not produce a request within the time that + the server was prepared to wait. The client MAY repeat the request without + modifications at any later time." + +- 410 Gone + + - Indicates that the resource requested is no longer available and will not be + available again. + +- 416 Range Not Satisfiable + + - The client has asked for a portion of the file (byte serving), but the + server cannot supply that portion. + +- 417 Expectation Failed + + - The server cannot meet the requirements of the Expect request-header field. + +- 421 Misdirected Request + + - The request was directed at a server that is not able to produce a response. + +- 424 Failed Dependency + + - The request failed due to failure of a previous request. + +- 426 Upgrade Required + + - The client should switch to a different protocol such as TLS/1.0, given in + the Upgrade header field. + +- 429 Too Many Requests + + - The user has sent too many requests in a given amount of time. Intended for + use with rate-limiting schemes. + +## Resources + +The MaxScale REST API provides the following resources. + +- [/maxscale](Resources-MaxScale.md) +- [/services](Resources-Service.md) +- [/servers](Resources-Server.md) +- [/filters](Resources-Filter.md) +- [/monitors](Resources-Monitor.md) +- [/sessions](Resources-Session.md) +- [/users](Resources-User.md) + +## 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. + +- `fields` + + - A list of fields to return. + + This allows the returned object to be filtered so that only needed + parts are returned. The value of this parameter is a comma separated + list of fields to return. + + For example, the parameter `?fields=id,name` would return object which + would only contain the _id_ and _name_ fields. + +- `range` + + - Return a subset of the object array. + + The value of this parameter is the range of objects to return given as + a inclusive range separated by a hyphen. If the size of the array is + less than the end of the range, only the objects between the requested + start of the range and the actual end of the array are returned. This + means that + + For example, the parameter `?range=10-20` would return objects 10 + through 20 from the object array if the actual size of the original + array is greater than or equal to 20. diff --git a/Documentation/REST-API/Resources-Filter.md b/Documentation/REST-API/Resources-Filter.md new file mode 100644 index 000000000..2235209b1 --- /dev/null +++ b/Documentation/REST-API/Resources-Filter.md @@ -0,0 +1,151 @@ +# Filter Resource + +A filter resource represents an instance of a filter inside MaxScale. Multiple +services can use the same filter and a single service can use multiple filters. + +## Resource Operations + +### Get a filter + +Get a single filter. The _:name_ in the URI must be a valid filter name with all +whitespace replaced with hyphens. The filter names are case-insensitive. + +``` +GET /filters/:name +``` + +#### Response + +``` +Status: 200 OK + +{ + "name": "Query Logging Filter", + "module": "qlafilter", + "parameters": { + "filebase": { + "value": "/var/log/maxscale/qla/log.", + "configurable": false + }, + "match": { + "value": "select.*from.*t1", + "configurable": true + } + }, + "services": [ + "/services/my-service", + "/services/my-second-service" + ] +} +``` + +#### Supported Request Parameter + +- `fields` + +### Get all filters + +Get all filters. + +``` +GET /filters +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "Query Logging Filter", + "module": "qlafilter", + "parameters": { + "filebase": { + "value": "/var/log/maxscale/qla/log.", + "configurable": false + }, + "match": { + "value": "select.*from.*t1", + "configurable": true + } + }, + "services": [ + "/services/my-service", + "/services/my-second-service + ] + }, + { + "name": "DBFW Filter", + "module": "dbfwfilter", + "parameters": { + { + "name": "rules", + "value": "/etc/maxscale-rules", + "configurable": false + } + }, + "services": [ + "/services/my-second-service + ] + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Update a filter + +**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 filter. The _:name_ in the URI must map to a filter name +and the request body must be a valid JSON Patch document which is applied to the +resource. + +``` +PATCH /filter/:name +``` + +### Modifiable Fields + +|Field |Type |Description | +|------------|-------|---------------------------------| +|parameters |object |Module specific filter parameters| + +``` +[ + { "op": "replace", "path": "/parameters/rules/value", "value": "/etc/new-rules" }, + { "op": "add", "path": "/parameters/action/value", "value": "allow" } +] +``` + +#### Response + +Response contains the modified resource. + +``` +Status: 200 OK + +{ + "name": "DBFW Filter", + "module": "dbfwfilter", + "parameters": { + "rules": { + "value": "/etc/new-rules", + "configurable": false + }, + "action": { + "value": "allow", + "configurable": true + } + } + "services": [ + "/services/my-second-service" + ] +} +``` diff --git a/Documentation/REST-API/Resources-MaxScale.md b/Documentation/REST-API/Resources-MaxScale.md new file mode 100644 index 000000000..f2959c1d4 --- /dev/null +++ b/Documentation/REST-API/Resources-MaxScale.md @@ -0,0 +1,216 @@ +# MaxScale Resource + +The MaxScale resource represents a MaxScale instance and it is the core on top +of which the modules build upon. + +## Resource Operations + +## Get global information + +Retrieve global information about a MaxScale instance. This includes various +file locations, configuration options and version information. + +``` +GET /maxscale +``` + +#### Response + +``` +Status: 200 OK + +{ + "config": "/etc/maxscale.cnf", + "cachedir": "/var/cache/maxscale/", + "datadir": "/var/lib/maxscale/" + "libdir": "/usr/lib64/maxscale/", + "piddir": "/var/run/maxscale/", + "execdir": "/usr/bin/", + "languagedir": "/var/lib/maxscale/", + "user": "maxscale", + "threads": 4, + "version": "2.1.0", + "commit": "12e7f17eb361e353f7ac413b8b4274badb41b559" + "started": "Wed, 31 Aug 2016 23:29:26 +0300" +} +``` + +#### Supported Request Parameter + +- `fields` + +## Get thread information + +Get detailed information and statistics about the threads. + +``` +GET /maxscale/threads +``` + +#### Response + +``` +Status: 200 OK + +{ + "load_average": { + "historic": 1.05, + "current": 1.00, + "1min": 0.00, + "5min": 0.00, + "15min": 0.00 + }, + "threads": [ + { + "id": 0, + "state": "processing", + "file_descriptors": 1, + "event": [ + "in", + "out" + ], + "run_time": 300 + }, + { + "id": 1, + "state": "polling", + "file_descriptors": 0, + "event": [], + "run_time": 0 + } + ] +} +``` + +#### Supported Request Parameter + +- `fields` + +## Get logging information + +Get information about the current state of logging, enabled log files and the +location where the log files are stored. + +``` +GET /maxscale/logs +``` + +#### Response + +``` +Status: 200 OK + +{ + "logdir": "/var/log/maxscale/", + "maxlog": true, + "syslog": false, + "log_levels": { + "error": true, + "warning": true, + "notice": true, + "info": false, + "debug": false + }, + "log_augmentation": { + "function": true + }, + "log_throttling": { + "limit": 8, + "window": 2000, + "suppression": 10000 + }, + "last_flushed": "Wed, 31 Aug 2016 23:29:26 +0300" +} +``` + +#### Supported Request Parameter + +- `fields` + +## Flush and rotate log files + +Flushes any pending messages to disk and reopens the log files. The body of the +message is ignored. + +``` +POST /maxscale/logs/flush +``` + +#### Response + +``` +Status: 204 No Content +``` + +## Get task schedule + +Retrieve all pending tasks that are queued for execution. + +``` +GET /maxscale/tasks +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "Load Average", + "type": "repeated", + "frequency": 10, + "next_due": "Fri Sep 9 14:12:37 2016" + } +} +``` + +#### Supported Request Parameter + +- `fields` + +## Get loaded modules + +Retrieve information about all loaded modules. This includes version, API and +maturity information. + +``` +GET /maxscale/modules +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "MySQLBackend", + "type": "Protocol", + "version": "V2.0.0", + "api_version": "1.1.0", + "maturity": "GA" + }, + { + "name": "qlafilter", + "type": "Filter", + "version": "V1.1.1", + "api_version": "1.1.0", + "maturity": "GA" + }, + { + "name": "readwritesplit", + "type": "Router", + "version": "V1.1.0", + "api_version": "1.0.0", + "maturity": "GA" + } +} +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +TODO: Add epoll statistics and rest of the supported methods. diff --git a/Documentation/REST-API/Resources-Monitor.md b/Documentation/REST-API/Resources-Monitor.md new file mode 100644 index 000000000..043af6545 --- /dev/null +++ b/Documentation/REST-API/Resources-Monitor.md @@ -0,0 +1,176 @@ +# Monitor Resource + +A monitor resource represents a monitor inside MaxScale that monitors one or +more servers. + +## Resource Operations + +### Get a monitor + +Get a single monitor. The _:name_ in the URI must be a valid monitor name with +all whitespace replaced with hyphens. The monitor names are case-insensitive. + +``` +GET /monitors/:name +``` + +#### Response + +``` +Status: 200 OK + +{ + "name": "MySQL Monitor", + "module": "mysqlmon", + "state": "started", + "monitor_interval": 2500, + "connect_timeout": 5, + "read_timeout": 2, + "write_timeout": 3, + "servers": [ + "/servers/db-serv-1", + "/servers/db-serv-2", + "/servers/db-serv-3" + ] +} +``` + +#### Supported Request Parameter + +- `fields` + +### Get all monitors + +Get all monitors. + +``` +GET /monitors +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "MySQL Monitor", + "module": "mysqlmon", + "state": "started", + "monitor_interval": 2500, + "connect_timeout": 5, + "read_timeout": 2, + "write_timeout": 3, + "servers": [ + "/servers/db-serv-1", + "/servers/db-serv-2", + "/servers/db-serv-3" + ] + }, + { + "name": "Galera Monitor", + "module": "galeramon", + "state": "started", + "monitor_interval": 5000, + "connect_timeout": 10, + "read_timeout": 5, + "write_timeout": 5, + "servers": [ + "/servers/db-galera-1", + "/servers/db-galera-2", + "/servers/db-galera-3" + ] + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Stop a monitor + +Stops a started monitor. + +``` +PUT /monitor/:name/stop +``` + +#### Response + +``` +Status: 204 No Content +``` + +### Start a monitor + +Starts a stopped monitor. + +``` +PUT /monitor/:name/start +``` + +#### Response + +``` +Status: 204 No Content +``` + +### Update a monitor + +**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 monitor. The _:name_ in the URI must map to a monitor name +and the request body must be a valid JSON Patch document which is applied to the +resource. + +``` +PATCH /monitor/:name +``` + +### Modifiable Fields + +The following values can be modified with the PATCH method. + +|Field |Type |Description | +|-----------------|------------|---------------------------------------------------| +|servers |string array|Servers monitored by this monitor | +|monitor_interval |number |Monitoring interval in milliseconds | +|connect_timeout |number |Connection timeout in seconds | +|read_timeout |number |Read timeout in seconds | +|write_timeout |number |Write timeout in seconds | + +``` +[ + { "op": "remove", "path": "/servers/0" }, + { "op": "replace", "path": "/monitor_interval", "value": 2000 }, + { "op": "replace", "path": "/connect_timeout", "value": 2 }, + { "op": "replace", "path": "/read_timeout", "value": 2 }, + { "op": "replace", "path": "/write_timeout", "value": 2 } +] +``` + +#### Response + +Response contains the modified resource. + +``` +Status: 200 OK + +{ + "name": "MySQL Monitor", + "module": "mysqlmon", + "servers": [ + "/servers/db-serv-2", + "/servers/db-serv-3" + ], + "state": "started", + "monitor_interval": 2000, + "connect_timeout": 2, + "read_timeout": 2, + "write_timeout": 2 +} +``` diff --git a/Documentation/REST-API/Resources-Server.md b/Documentation/REST-API/Resources-Server.md new file mode 100644 index 000000000..a73b0d531 --- /dev/null +++ b/Documentation/REST-API/Resources-Server.md @@ -0,0 +1,207 @@ +# Server Resource + +A server resource represents a backend database server. + +## Resource Operations + +### 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 +``` + +#### Response + +``` +Status: 200 OK + +{ + "name": "db-serv-1", + "address": "192.168.121.58", + "port": 3306, + "protocol": "MySQLBackend", + "status": [ + "master", + "running" + ], + "parameters": { + "report_weight": 10, + "app_weight": 2 + } +} +``` + +**Note**: The _parameters_ field contains all custom parameters for + servers, including the server weighting parameters. + +#### Supported Request Parameter + +- `fields` + +### Get all servers + +``` +GET /servers +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "db-serv-1", + "address": "192.168.121.58", + "port": 3306, + "protocol": "MySQLBackend", + "status": [ + "master", + "running" + ], + "parameters": { + "report_weight": 10, + "app_weight": 2 + } + }, + { + "name": "db-serv-2", + "address": "192.168.121.175", + "port": 3306, + "status": [ + "slave", + "running" + ], + "protocol": "MySQLBackend", + "parameters": { + "app_weight": 6 + } + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### 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. + +``` +PATCH /servers/:name +``` + +### 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.| + +``` +{ + { "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 } +} +``` + +#### Response + +Response contains the modified resource. + +``` +Status: 200 OK + +{ + "name": "db-serv-1", + "protocol": "MySQLBackend", + "address": "192.168.0.100", + "port": 4006, + "state": [ + "maintenance", + "running" + ], + "parameters": { + "report_weight": 1, + "app_weight": 2 + } +} +``` + +### Get all connections to a server + +Get all connections that are connected to a server. + +``` +GET /servers/:name/connections +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "state": "DCB in the polling loop", + "role": "Backend Request Handler", + "server": "/servers/db-serv-01", + "service": "/services/my-service", + "statistics": { + "reads": 2197 + "writes": 1562 + "buffered_writes": 0 + "high_water_events": 0 + "low_water_events": 0 + } + }, + { + "state": "DCB in the polling loop", + "role": "Backend Request Handler", + "server": "/servers/db-serv-01", + "service": "/services/my-second-service" + "statistics": { + "reads": 0 + "writes": 0 + "buffered_writes": 0 + "high_water_events": 0 + "low_water_events": 0 + } + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Close all connections to a server + +Close all connections to a particular server. This will forcefully close all +backend connections. + +``` +DELETE /servers/:name/connections +``` + +#### Response + +``` +Status: 204 No Content +``` diff --git a/Documentation/REST-API/Resources-Service.md b/Documentation/REST-API/Resources-Service.md new file mode 100644 index 000000000..11c15d4e4 --- /dev/null +++ b/Documentation/REST-API/Resources-Service.md @@ -0,0 +1,272 @@ +# Service Resource + +A service resource represents a service inside MaxScale. A service is a +collection of network listeners, filters, a router and a set of backend servers. + +## Resource Operations + +### Get a service + +Get a single service. The _:name_ in the URI must be a valid service name with +all whitespace replaced with hyphens. The service names are case-insensitive. + +``` +GET /services/:name +``` + +#### Response + +``` +Status: 200 OK + +{ + "name": "My Service", + "router": "readwritesplit", + "router_options": { + "disable_sescmd_history": "true" + }, + "state": "started", + "total_connections": 10, + "current_connections": 2, + "started": "2016-08-29T12:52:31+03:00", + "filters": [ + "/filters/Query-Logging-Filter" + ], + "servers": [ + "/servers/db-serv-1", + "/servers/db-serv-2", + "/servers/db-serv-3" + ] +} +``` + +#### Supported Request Parameter + +- `fields` + +### Get all services + +Get all services. + +``` +GET /services +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "My Service", + "router": "readwritesplit", + "router_options": { + "disable_sescmd_history": "true" + }, + "state": "started", + "total_connections": 10, + "current_connections": 2, + "started": "2016-08-29T12:52:31+03:00", + "filters": [ + "/filters/Query-Logging-Filter" + ], + "servers": [ + "/servers/db-serv-1", + "/servers/db-serv-2", + "/servers/db-serv-3" + ] + }, + { + "name": "My Second Service", + "router": "readconnroute", + "router_options": { + "type": "master" + }, + "state": "started", + "total_connections": 10, + "current_connections": 2, + "started": "2016-08-29T12:52:31+03:00", + "servers": [ + "/servers/db-serv-1", + "/servers/db-serv-2" + ] + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Get service listeners + +Get the listeners of a service. The _:name_ in the URI must be a valid service +name with all whitespace replaced with hyphens. The service names are +case-insensitive. + +``` +GET /services/:name/listeners +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "My Listener", + "protocol": "MySQLClient", + "address": "0.0.0.0", + "port": 4006 + }, + { + "name": "My SSL Listener", + "protocol": "MySQLClient", + "address": "127.0.0.1", + "port": 4006, + "ssl": "required", + "ssl_cert": "/home/markusjm/newcerts/server-cert.pem", + "ssl_key": "/home/markusjm/newcerts/server-key.pem", + "ssl_ca_cert": "/home/markusjm/newcerts/ca.pem" + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Update a service + +**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 service. The _:name_ in the URI must map to a service name +and the request body must be a valid JSON Patch document which is applied to the +resource. + +``` +PATCH /services/:name +``` + +### Modifiable Fields + +|Field |Type |Description | +|--------------|------------|---------------------------------------------------| +|servers |string array|Servers used by this service, must be relative links to existing server resources| +|router_options|object |Router specific options| +|filters |string array|Service filters, configured in the same order they are declared in the array (`filters[0]` => first filter, `filters[1]` => second filter)| +|user |string |The username for the service user| +|password |string |The password for the service user| +|root_user |boolean |Allow root user to connect via this service| +|version_string|string |Custom version string given to connecting clients| +|weightby |string |Name of a server weigting parameter which is used for connection weighting| +|connection_timeout|number |Client idle timeout in seconds| +|max_connection|number |Maximum number of allowed connections| +|strip_db_esc|boolean |Strip escape characters from default database name| + +``` +[ + { "op": "replace", "path": "/servers", "value": ["/servers/db-serv-2","/servers/db-serv-3"] }, + { "op": "add", "path": "/router_options/master_failover_mode", "value": "fail_on_write" }, + { "op": "remove", "path": "/filters" } +] +``` + +#### Response + +Response contains the modified resource. + +``` +Status: 200 OK + + { + "name": "My Service", + "router": "readwritesplit", + "router_options": { + "disable_sescmd_history=false", + "master_failover_mode": "fail_on_write" + } + "state": "started", + "total_connections": 10, + "current_connections": 2, + "started": "2016-08-29T12:52:31+03:00", + "servers": [ + "/servers/db-serv-2", + "/servers/db-serv-3" + ] + } +``` + +### Stop a service + +Stops a started service. + +``` +PUT /service/:name/stop +``` + +#### Response + +``` +Status: 204 No Content +``` + +### Start a service + +Starts a stopped service. + +``` +PUT /service/:name/start +``` + +#### Response + +``` +Status: 204 No Content +``` + +### Get all sessions for a service + +Get all sessions for a particular service. + +``` +GET /services/:name/sessions +``` + +#### Response + +Relative links to all sessions for this service. + +``` +Status: 200 OK + +[ + "/sessions/1", + "/sessions/2" +] +``` + +#### Supported Request Parameter + +- `range` + +### Close all sessions for a service + +Close all sessions for a particular service. This will forcefully close all +client connections and any backend connections they have made. + +``` +DELETE /services/:name/sessions +``` + +#### Response + +``` +Status: 204 No Content +``` diff --git a/Documentation/REST-API/Resources-Session.md b/Documentation/REST-API/Resources-Session.md new file mode 100644 index 000000000..03f90d0d7 --- /dev/null +++ b/Documentation/REST-API/Resources-Session.md @@ -0,0 +1,138 @@ +# Session Resource + +A session consists of a client connection, any number of related backend +connections, a router module session and possibly filter module sessions. Each +session is created on a service and a service can have multiple sessions. + +## Resource Operations + +### Get a session + +Get a single session. _:id_ must be a valid session ID. + +``` +GET /sessions/:id +``` + +#### Response + +``` +Status: 200 OK + +{ + "id": 1, + "state": "Session ready for routing", + "user": "jdoe", + "address": "192.168.0.200", + "service": "/services/my-service", + "connected": "Wed Aug 31 03:03:12 2016", + "idle": 260 +} +``` + +#### Supported Request Parameter + +- `fields` + +### Get all sessions + +Get all sessions. + +``` +GET /sessions +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "id": 1, + "state": "Session ready for routing", + "user": "jdoe", + "address": "192.168.0.200", + "service": "/services/My-Service", + "connected": "Wed Aug 31 03:03:12 2016", + "idle": 260 + }, + { + "id": 2, + "state": "Session ready for routing", + "user": "dba", + "address": "192.168.0.201", + "service": "/services/My-Service", + "connected": "Wed Aug 31 03:10:00 2016", + "idle": 1 + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Get all connections created by a session + +Get all backend connections created by a session. _:id_ must be a valid session ID. + +``` +GET /sessions/:id/connections +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "state": "DCB in the polling loop", + "role": "Backend Request Handler", + "server": "/servers/db-serv-01", + "service": "/services/my-service", + "statistics": { + "reads": 2197 + "writes": 1562 + "buffered_writes": 0 + "high_water_events": 0 + "low_water_events": 0 + } + }, + { + "state": "DCB in the polling loop", + "role": "Backend Request Handler", + "server": "/servers/db-serv-02", + "service": "/services/my-service", + "statistics": { + "reads": 0 + "writes": 0 + "buffered_writes": 0 + "high_water_events": 0 + "low_water_events": 0 + } + } +] +``` + +#### Supported Request Parameter + +- `fields` +- `range` + +### Close a session + +Close a session. This will forcefully close the client connection and any +backend connections. + +``` +DELETE /sessions/:id +``` + +#### Response + +``` +Status: 204 No Content +``` diff --git a/Documentation/REST-API/Resources-User.md b/Documentation/REST-API/Resources-User.md new file mode 100644 index 000000000..84c8fc9f8 --- /dev/null +++ b/Documentation/REST-API/Resources-User.md @@ -0,0 +1,81 @@ +# Admin User Resource + +Admin users represent administrative users that are able to query and change +MaxScale's configuration. + +## Resource Operations + +### Get all users + +Get all administrative users. + +``` +GET /users +``` + +#### Response + +``` +Status: 200 OK + +[ + { + "name": "jdoe" + }, + { + "name": "dba" + }, + { + "name": "admin" + } +] + +#### Supported Request Parameter + +- `fields` +- `range` + +### Create a user + +Create a new administrative user. + +``` +PUT /users +``` + +### Modifiable Fields + +All of the following fields need to be defined in the request body. + +|Field |Type |Description | +|---------|------|-------------------------| +|name |string|Username, consisting of alphanumeric characters| +|password |string|Password for the new user| + +``` +{ + "name": "foo", + "password": "bar" +} +``` + +#### Response + +``` +Status: 204 No Content +``` + +### Delete a user + +Delete a user. The _:name_ part of the URI must be a valid user name. The user +names are case-insensitive. + +``` +DELETE /users/:name +``` + +#### Response + +``` +Status: 204 No Content +``` diff --git a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md index 7ffdace44..907b549bf 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md @@ -57,6 +57,18 @@ by default, is configured using the new global configuration entry `log_throttli For more information about this configuration entry, please see [Global Settings](../Getting-Started/Configuration-Guide.md#global-settings). +### Persistent Connections + +Starting with the 2.1 version of MariaDB MaxScale, when a MySQL protocol +persistent connection is taken from the persistent connection pool, the +state of the MySQL session will be reset when the the connection is used +for the first time. This allows persistent connections to be used with no +functional limitations and makes them behave like normal MySQL +connections. + +For more information about persistent connections, please read the +[Administration Tutorial](../Tutorials/Administration-Tutorial.md). + ### User data cache The user data cache stores the cached credentials that are used by some router @@ -75,6 +87,23 @@ removed if they are no longer used by older versions of MaxScale. ## New Features +### Dynamic server configuration + +MaxScale can now change the servers of a service or a monitor at run-time. New +servers can also be created and they will persisted even after a restart. The +following new commands were added to maxadmin, see output of `maxadmin help +` for more details. + +- `create server`: Creates a new server +- `destroy server`: Destroys a created server +- `add server`: Adds a server to a service or a monitor +- `remove server`: Removes a server from a service or a monitor +- `alter server`: Alter server configuration +- `alter monitor`: Alter monitor configuration + +With these new features, you can start MaxScale without the servers and define +them later. + ### Amazon RDS Aurora monitor The new [Aurora Monitor](../Monitors/Aurora-Monitor.md) module allows monitoring diff --git a/Documentation/Tutorials/Administration-Tutorial.md b/Documentation/Tutorials/Administration-Tutorial.md index e9f4e5929..14d64b807 100644 --- a/Documentation/Tutorials/Administration-Tutorial.md +++ b/Documentation/Tutorials/Administration-Tutorial.md @@ -106,13 +106,22 @@ than `persistmaxtime` seconds. It was also be discarded if it has been disconne by the back end server. Connections will be selected that match the user name and protocol for the new request. -**Please note** that because persistent connections have previously been in use, they -may give a different environment from a fresh connection. For example, if the -previous use of the connection issued "use mydatabase" then this setting will be -carried over into the reuse of the same connection. For many applications this will -not be noticeable, since each request will assume that nothing has been set and -will issue fresh requests such as "use" to establish the desired configuration. In -exceptional cases this feature could be a problem. +Starting with the 2.1 version of MaxScale, when a MySQL protocol connection is +taken from the pool the backend protocol module resets the session state. This +allows persistent connections to be used with no functional limitations. + +The session state is reset when the first outgoing network transmission is +done. This _lazy initialization_ of the persistent connections allows +MaxScale to take multiple new connections into use but only initialize the +ones that it actually needs. + +**Please note** that in versions before 2.1 the persistent connections may give +a different environment when compared to a fresh connection. For example, if the +previous use of the connection issued a "USE mydatabase;" statement then this +setting will be carried over into the reuse of the same connection. For many +applications this will not be noticeable, since each request will assume that +nothing has been set and will issue fresh requests such as "USE" to establish +the desired configuration. In exceptional cases this feature could be a problem. It is possible to have pools for as many servers as you wish, with configuration values in each server section. diff --git a/client/maxadmin.c b/client/maxadmin.c index 96602b95e..6deaf1ee7 100644 --- a/client/maxadmin.c +++ b/client/maxadmin.c @@ -91,12 +91,11 @@ prompt(EditLine *el __attribute__((__unused__))) } #endif - static struct option long_options[] = { {"host", required_argument, 0, 'h'}, {"user", required_argument, 0, 'u'}, - {"password", required_argument, 0, 'p'}, + {"password", optional_argument, 0, 'p'}, {"port", required_argument, 0, 'P'}, {"socket", required_argument, 0, 'S'}, {"version", no_argument, 0, 'v'}, @@ -108,6 +107,7 @@ static struct option long_options[] = #define MAXADMIN_DEFAULT_HOST "localhost" #define MAXADMIN_DEFAULT_PORT "6603" #define MAXADMIN_DEFAULT_USER "admin" +#define MAXADMIN_BUFFER_SIZE 2048 /** * The main for the maxadmin client @@ -125,7 +125,7 @@ main(int argc, char **argv) History *hist; HistEvent ev; #else - char buf[1024]; + char buf[MAXADMIN_BUFFER_SIZE]; #endif char *hostname = NULL; char *port = NULL; @@ -141,7 +141,7 @@ main(int argc, char **argv) int option_index = 0; char c; - while ((c = getopt_long(argc, argv, "h:p:P:u:S:v?e", + while ((c = getopt_long(argc, argv, "h:p::P:u:S:v?e", long_options, &option_index)) >= 0) { switch (c) @@ -153,8 +153,12 @@ main(int argc, char **argv) case 'p': use_inet_socket = true; - passwd = strdup(optarg); - memset(optarg, '\0', strlen(optarg)); + // If password was not given, ask for it later + if (optarg != NULL) + { + passwd = strdup(optarg); + memset(optarg, '\0', strlen(optarg)); + } break; case 'P': diff --git a/cmake/install_layout.cmake b/cmake/install_layout.cmake index 3eb9503f3..1abf7a7e2 100644 --- a/cmake/install_layout.cmake +++ b/cmake/install_layout.cmake @@ -20,6 +20,7 @@ set(DEFAULT_CACHE_SUBPATH "cache/maxscale" CACHE PATH "Default cache subpath") set(DEFAULT_LANG_SUBPATH "lib/maxscale" CACHE PATH "Default language file subpath") set(DEFAULT_EXEC_SUBPATH "${MAXSCALE_BINDIR}" CACHE PATH "Default executable subpath") set(DEFAULT_CONFIG_SUBPATH "etc" CACHE PATH "Default configuration subpath") +set(DEFAULT_CONFIG_PERSIST_SUBPATH "maxscale.cnf.d" CACHE PATH "Default persisted configuration subpath") set(DEFAULT_PIDDIR ${MAXSCALE_VARDIR}/${DEFAULT_PID_SUBPATH} CACHE PATH "Default PID file directory") set(DEFAULT_LOGDIR ${MAXSCALE_VARDIR}/${DEFAULT_LOG_SUBPATH} CACHE PATH "Default log directory") @@ -29,6 +30,7 @@ set(DEFAULT_CACHEDIR ${MAXSCALE_VARDIR}/${DEFAULT_CACHE_SUBPATH} CACHE PATH "Def set(DEFAULT_LANGDIR ${MAXSCALE_VARDIR}/${DEFAULT_LANG_SUBPATH} CACHE PATH "Default language file directory") set(DEFAULT_EXECDIR ${CMAKE_INSTALL_PREFIX}/${DEFAULT_EXEC_SUBPATH} CACHE PATH "Default executable directory") set(DEFAULT_CONFIGDIR /${DEFAULT_CONFIG_SUBPATH} CACHE PATH "Default configuration directory") +set(DEFAULT_CONFIG_PERSISTDIR ${DEFAULT_DATADIR}/${DEFAULT_CONFIG_PERSIST_SUBPATH} CACHE PATH "Default persisted configuration directory") # Massage TARGET_COMPONENT into a list if (TARGET_COMPONENT) diff --git a/include/maxscale/atomic.h b/include/maxscale/atomic.h index f6d694e0c..f7614b3d3 100644 --- a/include/maxscale/atomic.h +++ b/include/maxscale/atomic.h @@ -29,7 +29,42 @@ MXS_BEGIN_DECLS -int atomic_add(int *variable, int value); +/** + * Implementation of an atomic add operation for the GCC environment, or the + * X86 processor. If we are working within GNU C then we can use the GCC + * atomic add built in function, which is portable across platforms that + * implement GCC. Otherwise, this function currently supports only X86 + * architecture (without further development). + * + * Adds a value to the contents of a location pointed to by the first parameter. + * The add operation is atomic and the return value is the value stored in the + * location prior to the operation. The number that is added may be signed, + * therefore atomic_subtract is merely an atomic add with a negative value. + * + * @param variable Pointer the the variable to add to + * @param value Value to be added + * @return The value of variable before the add occurred + */ int atomic_add(int *variable, int value); +/** + * @brief Impose a full memory barrier + * + * A full memory barrier guarantees that all store and load operations complete + * before the function is called. + * + * Currently, only the GNUC __sync_synchronize() is used. C11 introduces + * standard functions for atomic memory operations and should be taken into use. + * + * @see https://www.kernel.org/doc/Documentation/memory-barriers.txt + */ +static inline void atomic_synchronize() +{ +#ifdef __GNUC__ + __sync_synchronize(); /* Memory barrier. */ +#else +#error "No GNUC atomics available." +#endif +} + MXS_END_DECLS diff --git a/include/maxscale/dcb.h b/include/maxscale/dcb.h index 617d124e1..98237c685 100644 --- a/include/maxscale/dcb.h +++ b/include/maxscale/dcb.h @@ -276,6 +276,7 @@ typedef struct dcb bool ssl_write_want_read; /*< Flag */ bool ssl_write_want_write; /*< Flag */ int dcb_port; /**< port of target server */ + bool was_persistent; /**< Whether this DCB was in the persistent pool */ skygw_chk_t dcb_chk_tail; } DCB; @@ -301,15 +302,6 @@ typedef enum DCB_USAGE_ALL } DCB_USAGE; -#if defined(FAKE_CODE) -extern unsigned char dcb_fake_write_errno[10240]; -extern __int32_t dcb_fake_write_ev[10240]; -extern bool fail_next_backend_fd; -extern bool fail_next_client_fd; -extern int fail_next_accept; -extern int fail_accept_errno; -#endif /* FAKE_CODE */ - /* A few useful macros */ #define DCB_SESSION(x) (x)->session #define DCB_PROTOCOL(x, type) (type *)((x)->protocol) diff --git a/include/maxscale/debug.h b/include/maxscale/debug.h index dde1f3032..8ab7db618 100644 --- a/include/maxscale/debug.h +++ b/include/maxscale/debug.h @@ -561,8 +561,4 @@ typedef enum skygw_chk_t } -#if defined(FAKE_CODE) -static bool conn_open[10240]; -#endif /* FAKE_CODE */ - MXS_END_DECLS diff --git a/include/maxscale/filter.h b/include/maxscale/filter.h index 1eabbe326..27a1ef8f6 100644 --- a/include/maxscale/filter.h +++ b/include/maxscale/filter.h @@ -83,6 +83,7 @@ typedef struct filter_object int (*clientReply)(FILTER *instance, void *fsession, GWBUF *queue); void (*diagnostics)(FILTER *instance, void *fsession, DCB *dcb); uint64_t (*getCapabilities)(void); + void (*destroyInstance)(FILTER *instance); } FILTER_OBJECT; /** diff --git a/include/maxscale/gwdirs.h.in b/include/maxscale/gwdirs.h.in index 909d324f0..695a57277 100644 --- a/include/maxscale/gwdirs.h.in +++ b/include/maxscale/gwdirs.h.in @@ -32,10 +32,12 @@ MXS_BEGIN_DECLS #define MXS_DEFAULT_LANG_SUBPATH "@DEFAULT_LANG_SUBPATH@" #define MXS_DEFAULT_EXEC_SUBPATH "@DEFAULT_EXEC_SUBPATH@" #define MXS_DEFAULT_CONFIG_SUBPATH "@DEFAULT_CONFIG_SUBPATH@" +#define MXS_DEFAULT_CONFIG_PERSIST_SUBPATH "@DEFAULT_CONFIG_PERSIST_SUBPATH@" /** Default file locations, configured by CMake */ static const char* default_cnf_fname = "maxscale.cnf"; static const char* default_configdir = "@DEFAULT_CONFIGDIR@"; + /*< This should be changed to just /run eventually, * the /var/run folder is an old standard and the newer FSH 3.0 * uses /run for PID files.*/ @@ -46,8 +48,10 @@ static const char* default_libdir = "@DEFAULT_LIBDIR@"; static const char* default_cachedir = "@DEFAULT_CACHEDIR@"; static const char* default_langdir = "@DEFAULT_LANGDIR@"; static const char* default_execdir = "@DEFAULT_EXECDIR@"; +static const char* default_config_persistdir = "@DEFAULT_CONFIG_PERSISTDIR@"; -static char* configdir = NULL; +static char* configdir = NULL; /*< Where the config file is found e.g. /etc/ */ +static char* config_persistdir = NULL;/*< Persisted configs e.g. /var/lib/maxscale.cnf.d/ */ static char* logdir = NULL; static char* libdir = NULL; static char* cachedir = NULL; @@ -62,6 +66,7 @@ void set_datadir(char* param); void set_process_datadir(char* param); void set_cachedir(char* param); void set_configdir(char* param); +void set_config_persistdir(char* param); void set_logdir(char* param); void set_langdir(char* param); void set_piddir(char* param); @@ -71,6 +76,7 @@ char* get_datadir(); char* get_process_datadir(); char* get_cachedir(); char* get_configdir(); +char* get_config_persistdir(); char* get_piddir(); char* get_logdir(); char* get_langdir(); diff --git a/include/maxscale/housekeeper.h b/include/maxscale/housekeeper.h index 3a7922ceb..c25d85df0 100644 --- a/include/maxscale/housekeeper.h +++ b/include/maxscale/housekeeper.h @@ -51,11 +51,34 @@ typedef struct hktask struct hktask *next; /*< Next task in the list */ } HKTASK; -extern void hkinit(); +/** + * Initialises the housekeeper mechanism. + * + * A call to any of the other housekeeper functions can be made only if + * this function returns successfully. + * + * @return True if the housekeeper mechanism was initialized, false otherwise. + */ +extern bool hkinit(); + +/** + * Shuts down the housekeeper mechanism. + * + * Should be called @b only if @c hkinit() returned successfully. + * + * @see hkinit hkfinish + */ +extern void hkshutdown(); + +/** + * Waits for the housekeeper thread to finish. Should be called only after + * hkshutdown() has been called. + */ +extern void hkfinish(); + extern int hktask_add(const char *name, void (*task)(void *), void *data, int frequency); extern int hktask_oneshot(const char *name, void (*task)(void *), void *data, int when); extern int hktask_remove(const char *name); -extern void hkshutdown(); extern void hkshow_tasks(DCB *pdcb); MXS_END_DECLS diff --git a/include/maxscale/maxscale.h b/include/maxscale/maxscale.h index 93d43bd19..e6b1a0abf 100644 --- a/include/maxscale/maxscale.h +++ b/include/maxscale/maxscale.h @@ -44,4 +44,14 @@ void maxscale_reset_starttime(void); time_t maxscale_started(void); int maxscale_uptime(void); +/** + * Initiate shutdown of MaxScale. + * + * This functions informs all threads that they should stop the + * processing and exit. + * + * @return How many times maxscale_shutdown() has been called. + */ +int maxscale_shutdown(void); + MXS_END_DECLS diff --git a/include/maxscale/monitor.h b/include/maxscale/monitor.h index 106b17113..55e795922 100644 --- a/include/maxscale/monitor.h +++ b/include/maxscale/monitor.h @@ -137,6 +137,9 @@ typedef enum #define MONITOR_INTERVAL 10000 // in milliseconds #define MONITOR_DEFAULT_ID 1UL // unsigned long value +#define MAX_MONITOR_USER_LEN 512 +#define MAX_MONITOR_PASSWORD_LEN 512 + /* * Create declarations of the enum for monitor events and also the array of * structs containing the matching names. The data is taken from def_monitor_event.h @@ -177,8 +180,8 @@ typedef struct monitor_servers struct monitor { char *name; /**< The name of the monitor module */ - char *user; /*< Monitor username */ - char *password; /*< Monitor password */ + char user[MAX_MONITOR_USER_LEN]; /*< Monitor username */ + char password[MAX_MONITOR_PASSWORD_LEN]; /*< Monitor password */ SPINLOCK lock; CONFIG_PARAMETER* parameters; /*< configuration parameters */ MONITOR_SERVERS* databases; /*< List of databases the monitor monitors */ @@ -201,7 +204,8 @@ struct monitor extern MONITOR *monitor_alloc(char *, char *); extern void monitor_free(MONITOR *); extern MONITOR *monitor_find(char *); -extern void monitorAddServer(MONITOR *, SERVER *); +extern void monitorAddServer(MONITOR *mon, SERVER *server); +extern void monitorRemoveServer(MONITOR *mon, SERVER *server); extern void monitorAddUser(MONITOR *, char *, char *); extern void monitorAddParameters(MONITOR *monitor, CONFIG_PARAMETER *params); extern void monitorStop(MONITOR *); @@ -229,4 +233,11 @@ connect_result_t mon_connect_to_db(MONITOR* mon, MONITOR_SERVERS *database); void mon_log_connect_error(MONITOR_SERVERS* database, connect_result_t rval); void mon_log_state_change(MONITOR_SERVERS *ptr); +/** + * Check if a monitor uses @c servers + * @param server Server that is queried + * @return True if server is used by at least one monitor + */ +bool monitor_server_in_use(const SERVER *server); + MXS_END_DECLS diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 7731c3c30..49c175615 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -262,28 +262,25 @@ typedef struct server_command_st typedef struct { #if defined(SS_DEBUG) - skygw_chk_t protocol_chk_top; + skygw_chk_t protocol_chk_top; #endif - int fd; /*< The socket descriptor */ - struct dcb *owner_dcb; /*< The DCB of the socket - * we are running on */ - SPINLOCK protocol_lock; - mysql_server_cmd_t current_command; /**< Current command being executed */ - server_command_t protocol_command; /*< session command list */ - server_command_t* protocol_cmd_history; /*< session command history */ - mxs_auth_state_t protocol_auth_state; /*< Authentication status */ - mysql_protocol_state_t protocol_state; /*< Protocol struct status */ - uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, - * created or received */ - uint32_t server_capabilities; /*< server capabilities, - * created or received */ - uint32_t client_capabilities; /*< client capabilities, - * created or received */ - unsigned long tid; /*< MySQL Thread ID, in - * handshake */ - unsigned int charset; /*< MySQL character set at connect time */ + int fd; /*< The socket descriptor */ + struct dcb* owner_dcb; /*< The DCB of the socket we are running on */ + SPINLOCK protocol_lock; /*< Protocol lock */ + mysql_server_cmd_t current_command; /*< Current command being executed */ + server_command_t protocol_command; /*< session command list */ + server_command_t* protocol_cmd_history; /*< session command history */ + mxs_auth_state_t protocol_auth_state; /*< Authentication status */ + mysql_protocol_state_t protocol_state; /*< Protocol struct status */ + uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, created or received */ + uint32_t server_capabilities; /*< server capabilities, created or received */ + uint32_t client_capabilities; /*< client capabilities, created or received */ + unsigned long tid; /*< MySQL Thread ID, in handshake */ + unsigned int charset; /*< MySQL character set at connect time */ + bool ignore_reply; /*< If the reply should be discarded */ + GWBUF* stored_query; /*< Temporarily stored queries */ #if defined(SS_DEBUG) - skygw_chk_t protocol_chk_tail; + skygw_chk_t protocol_chk_tail; #endif } MySQLProtocol; diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index ccef8d2fc..db1960994 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -86,6 +86,38 @@ typedef enum qc_parse_result QC_QUERY_PARSED = 3 /*< The query was fully parsed; completely classified. */ } qc_parse_result_t; +/** + * qc_field_usage_t defines where a particular field appears. + * + * QC_USED_IN_SELECT : The field appears on the left side of FROM in a top-level SELECT statement. + * QC_USED_IN_SUBSELECT: The field appears on the left side of FROM in a sub-select SELECT statement. + * QC_USED_IN_WHERE : The field appears in a WHERE clause. + * QC_USED_IN_SET : The field appears in the SET clause of an UPDATE statement. + * QC_USED_IN_GROUP_BY : The field appears in a GROUP BY clause. + * + * Note that multiple bits may be set at the same time. For instance, for a statement like + * "SELECT fld FROM tbl WHERE fld = 1 GROUP BY fld", the bits QC_USED_IN_SELECT, QC_USED_IN_WHERE + * and QC_USED_IN_GROUP_BY will be set. + */ +typedef enum qc_field_usage +{ + QC_USED_IN_SELECT = 0x01, /*< SELECT fld FROM... */ + QC_USED_IN_SUBSELECT = 0x02, /*< SELECT 1 FROM ... SELECT fld ... */ + QC_USED_IN_WHERE = 0x04, /*< SELECT ... FROM ... WHERE fld = ... */ + QC_USED_IN_SET = 0x08, /*< UPDATE ... SET fld = ... */ + QC_USED_IN_GROUP_BY = 0x10, /*< ... GROUP BY fld */ +} qc_field_usage_t; + +/** + * QC_FIELD_INFO contains information about a field used in a statement. + */ +typedef struct qc_field_info +{ + char* database; /** Present if the field is of the form "a.b.c", NULL otherwise. */ + char* table; /** Present if the field is of the form "a.b", NULL otherwise. */ + char* column; /** Always present. */ + uint32_t usage; /** Bitfield denoting where the column appears. */ +} QC_FIELD_INFO; /** * QUERY_CLASSIFIER defines the object a query classifier plugin must @@ -113,9 +145,10 @@ typedef struct query_classifier char** (*qc_get_table_names)(GWBUF* stmt, int* tblsize, bool fullnames); char* (*qc_get_canonical)(GWBUF* stmt); bool (*qc_query_has_clause)(GWBUF* stmt); - char* (*qc_get_affected_fields)(GWBUF* stmt); char** (*qc_get_database_names)(GWBUF* stmt, int* size); char* (*qc_get_prepare_name)(GWBUF* stmt); + qc_query_op_t (*qc_get_prepare_operation)(GWBUF* stmt); + void (*qc_get_field_info)(GWBUF* stmt, const QC_FIELD_INFO** infos, size_t* n_infos); } QUERY_CLASSIFIER; /** @@ -212,15 +245,38 @@ void qc_thread_end(void); qc_parse_result_t qc_parse(GWBUF* stmt); /** - * Returns the fields the statement affects, as a string of names separated - * by spaces. Note that the fields do not contain any table information. + * Convert a qc_field_usage_t enum to corresponding string. * - * @param stmt A buffer containing a COM_QUERY packet. + * @param usage The value to be converted * - * @return A string containing the fields or NULL if a memory allocation - * failure occurs. The string must be freed by the caller. + * @return The corresponding string. Must @b not be freed. */ -char* qc_get_affected_fields(GWBUF* stmt); +const char* qc_field_usage_to_string(qc_field_usage_t usage); + +/** + * Convert a mask of qc_field_usage_t enum values to corresponding string. + * + * @param usage_mask Mask of qc_field_usage_t values. + * + * @return The corresponding string, or NULL if memory allocation fails. + * @b Must be freed by the caller. + */ +char* qc_field_usage_mask_to_string(uint32_t usage_mask); + +/** + * Returns information about affected fields. + * + * @param stmt A buffer containing a COM_QUERY packet. + * @param infos Pointer to pointer that after the call will point to an + * array of QC_FIELD_INFO:s. + * @param n_infos Pointer to size_t variable where the number of items + * in @c infos will be returned. + * + * @note The returned array belongs to the GWBUF and remains valid for as + * long as the GWBUF is valid. If the data is needed for longer than + * that, it must be copied. + */ +void qc_get_field_info(GWBUF* stmt, const QC_FIELD_INFO** infos, size_t* n_infos); /** * Returns the statement, with literals replaced with question marks. @@ -285,6 +341,17 @@ qc_query_op_t qc_get_operation(GWBUF* stmt); */ char* qc_get_prepare_name(GWBUF* stmt); +/** + * Returns the operator of the prepared statement, if the statement + * is a PREPARE statement. + * + * @param stmt A buffer containing a COM_QUERY packet. + * + * @return The operator of the prepared statement, if the statement + * is a PREPARE statement; otherwise QUERY_OP_UNDEFINED. + */ +qc_query_op_t qc_get_prepare_operation(GWBUF* stmt); + /** * Returns the tables accessed by the statement. * @@ -361,7 +428,7 @@ const char* qc_op_to_string(qc_query_op_t op); */ static inline bool qc_query_is_type(uint32_t typemask, qc_query_type_t type) { - return (typemask & type) == type; + return (typemask & (uint32_t)type) == (uint32_t)type; } /** @@ -396,12 +463,4 @@ const char* qc_type_to_string(qc_query_type_t type); */ char* qc_typemask_to_string(uint32_t typemask); -/** - * @deprecated - * Synonym for qc_query_is_type(). - * - * @see qc_query_is_type - */ -#define QUERY_IS_TYPE(typemask, type) qc_query_is_type(typemask, type) - MXS_END_DECLS diff --git a/include/maxscale/router.h b/include/maxscale/router.h index 90e8083d0..1a403d312 100644 --- a/include/maxscale/router.h +++ b/include/maxscale/router.h @@ -24,6 +24,7 @@ * 16/07/2013 Massimiliano Pinto Added router commands values * 22/10/2013 Massimiliano Pinto Added router errorReply entry point * 27/10/2015 Martin Brampton Add RCAP_TYPE_NO_RSESSION + * 08/11/2016 Massimiliano Pinto Add destroyInstance() entry point * */ @@ -82,6 +83,7 @@ typedef struct router_object error_action_t action, bool* succp); uint64_t (*getCapabilities)(void); + void (*destroyInstance)(ROUTER *instance); } ROUTER_OBJECT; /** diff --git a/include/maxscale/semaphore.h b/include/maxscale/semaphore.h new file mode 100644 index 000000000..a231bfd6f --- /dev/null +++ b/include/maxscale/semaphore.h @@ -0,0 +1,22 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +/** + * @file semaphore.h Semaphores used by MaxScale. + */ + +// As a minimal preparation for other environments than Linux, components +// include , instead of including +// directly. +#include diff --git a/include/maxscale/server.h b/include/maxscale/server.h index 8b85c2b13..eb28e1271 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -48,6 +48,8 @@ MXS_BEGIN_DECLS #define MAX_SERVER_NAME_LEN 1024 +#define MAX_SERVER_MONUSER_LEN 512 +#define MAX_SERVER_MONPW_LEN 512 #define MAX_NUM_SLAVES 128 /**< Maximum number of slaves under a single server*/ /** @@ -86,15 +88,16 @@ typedef struct server #endif SPINLOCK lock; /**< Common access lock */ char *unique_name; /**< Unique name for the server */ - char *name; /**< Server name/IP address*/ + char name[MAX_SERVER_NAME_LEN]; /**< Server name/IP address*/ unsigned short port; /**< Port to listen on */ char *protocol; /**< Protocol module to use */ char *authenticator; /**< Authenticator module name */ void *auth_instance; /**< Authenticator instance */ + char *auth_options; /**< Authenticator options */ SSL_LISTENER *server_ssl; /**< SSL data structure for server, if any */ unsigned int status; /**< Status flag bitmap for the server */ - char *monuser; /**< User name to use to monitor the db */ - char *monpw; /**< Password to use to monitor the db */ + char monuser[MAX_SERVER_MONUSER_LEN]; /**< User name to use to monitor the db */ + char monpw[MAX_SERVER_MONPW_LEN]; /**< Password to use to monitor the db */ SERVER_STATS stats; /**< The server statistics */ struct server *next; /**< Next server */ struct server *nextdb; /**< Next server in list attached to a service */ @@ -112,6 +115,7 @@ typedef struct server long persistpoolmax; /**< Maximum size of persistent connections pool */ long persistmaxtime; /**< Maximum number of seconds connection can live */ int persistmax; /**< Maximum pool size actually achieved since startup */ + bool is_active; /**< Server is active and has not been "destroyed" */ #if defined(SS_DEBUG) skygw_chk_t server_chk_tail; #endif @@ -136,6 +140,11 @@ typedef struct server #define SERVER_STALE_SLAVE 0x2000 /**<< Slave status is possible even without a master */ #define SERVER_RELAY_MASTER 0x4000 /**<< Server is a relay master */ +/** + * Is the server valid and active + */ +#define SERVER_IS_ACTIVE(server) (server->is_active) + /** * Is the server running - the macro returns true if the server is marked as running * regardless of it's state as a master or slave @@ -197,9 +206,57 @@ typedef struct server (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \ (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) -extern SERVER *server_alloc(char *, char *, unsigned short, char*, char*); +/** + * @brief Allocate a new server + * + * This will create a new server that represents a backend server that services + * can use. This function will add the server to the running configuration but + * will not persist the changes. + * + * @param name Unique server name + * @param address The server address + * @param port The port to connect to + * @param protocol The protocol to use to connect to the server + * @param authenticator The server authenticator module + * @param auth_options Options for the authenticator module + * @return The newly created server or NULL if an error occurred + */ +extern SERVER* server_alloc(const char *name, const char *address, unsigned short port, + const char *protocol, const char *authenticator, + const char *auth_options); + +/** + * @brief Create a new server + * + * This function creates a new, persistent server by first allocating a new + * server and then storing the resulting configuration file on disk. This + * function should be used only from administrative interface modules and internal + * modules should use server_alloc() instead. + * + * @param name Server name + * @param address Network address + * @param port Network port + * @param protocol Protocol module name + * @param authenticator Authenticator module name + * @param options Options for the authenticator module + * @return True on success, false if an error occurred + */ +extern bool server_create(const char *name, const char *address, const char *port, + const char *protocol, const char *authenticator, + const char *options); + +/** + * @brief Destroy a server + * + * This removes any created server configuration files and marks the server removed + * If the server is not in use. + * @param server Server to destroy + * @return True if server was destroyed + */ +bool server_destroy(SERVER *server); + extern int server_free(SERVER *); -extern SERVER *server_find_by_unique_name(char *); +extern SERVER *server_find_by_unique_name(const char *name); extern SERVER *server_find(char *, unsigned short); extern void printServer(SERVER *); extern void printAllServers(); @@ -216,13 +273,14 @@ extern void server_transfer_status(SERVER *dest_server, SERVER *source_server); extern void serverAddMonUser(SERVER *, char *, char *); extern void serverAddParameter(SERVER *, char *, char *); extern char *serverGetParameter(SERVER *, char *); -extern void server_update(SERVER *, char *, char *, char *); -extern void server_set_unique_name(SERVER *, char *); +extern void server_update_credentials(SERVER *, char *, char *); extern DCB *server_get_persistent(SERVER *, char *, const char *); extern void server_update_address(SERVER *, char *); extern void server_update_port(SERVER *, unsigned short); extern RESULTSET *serverGetList(); extern unsigned int server_map_status(char *str); extern bool server_set_version_string(SERVER* server, const char* string); +extern bool server_is_ssl_parameter(const char *key); +extern void server_update_ssl(SERVER *server, const char *key, const char *value); MXS_END_DECLS diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 815e8cf62..e8fd7850e 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -97,10 +97,16 @@ typedef struct typedef struct server_ref_t { - struct server_ref_t *next; - SERVER* server; + struct server_ref_t *next; /**< Next server reference */ + SERVER* server; /**< The actual server */ + int weight; /**< Weight of this server */ + int connections; /**< Number of connections created through this reference */ + bool active; /**< Whether this reference is valid and in use*/ } SERVER_REF; +/** Macro to check whether a SERVER_REF is active */ +#define SERVER_REF_IS_ACTIVE(ref) (ref->active && SERVER_IS_ACTIVE(ref->server)) + #define SERVICE_MAX_RETRY_INTERVAL 3600 /*< The maximum interval between service start retries */ /** Value of service timeout if timeout checks are disabled */ @@ -144,6 +150,7 @@ typedef struct service void *router_instance; /**< The router instance for this service */ char *version_string; /** version string for this service listeners */ SERVER_REF *dbref; /** server references */ + int n_dbref; /** Number of server references */ SERVICE_USER credentials; /**< The cedentials of the service user */ SPINLOCK spin; /**< The service spinlock */ SERVICE_STATS stats; /**< The service statistics */ @@ -192,6 +199,7 @@ extern int serviceAddProtocol(SERVICE *service, char *name, char *protocol, extern int serviceHasProtocol(SERVICE *service, const char *protocol, const char* address, unsigned short port); extern void serviceAddBackend(SERVICE *, SERVER *); +extern void serviceRemoveBackend(SERVICE *, const SERVER *); extern int serviceHasBackend(SERVICE *, SERVER *); extern void serviceAddRouterOption(SERVICE *, char *); extern void serviceClearRouterOptions(SERVICE *); @@ -249,4 +257,11 @@ static inline uint64_t service_get_capabilities(const SERVICE *service) return service->capabilities; } +/** + * Check if a service uses @c servers + * @param server Server that is queried + * @return True if server is used by at least one service + */ +bool service_server_in_use(const SERVER *server); + MXS_END_DECLS diff --git a/include/maxscale/users.h b/include/maxscale/users.h index cdd919de4..d037d6cf4 100644 --- a/include/maxscale/users.h +++ b/include/maxscale/users.h @@ -61,16 +61,16 @@ typedef struct users unsigned char cksum[SHA_DIGEST_LENGTH]; /**< The users' table ckecksum */ } USERS; -extern USERS *users_alloc(); /**< Allocate a users table */ -extern void users_free(USERS *); /**< Free a users table */ -extern int users_add(USERS *, char *, char *); /**< Add a user to the users table */ -extern int users_delete(USERS *, char *); /**< Delete a user from the users table */ -extern char *users_fetch(USERS *, char *); /**< Fetch the authentication data for a user */ -extern int users_update(USERS *, char *, char *); /**< Change the password data for a user in - the users table */ -extern int users_default_loadusers(SERV_LISTENER *port); /**< A generic implementation of the authenticator - * loadusers entry point */ -extern void usersPrint(USERS *); /**< Print data about the users loaded */ -extern void dcb_usersPrint(DCB *, USERS *); /**< Print data about the users loaded */ +extern USERS *users_alloc(); /**< Allocate a users table */ +extern void users_free(USERS *); /**< Free a users table */ +extern int users_add(USERS *, const char *, const char *); /**< Add a user to the users table */ +extern int users_delete(USERS *, const char *); /**< Delete a user from the users table */ +extern const char *users_fetch(USERS *, const char *); /**< Fetch the authentication data for a user*/ +extern int users_update(USERS *, const char *, const char *); /**< Change the password data for a user in + the users table */ +extern int users_default_loadusers(SERV_LISTENER *port); /**< A generic implementation of the + authenticator loadusers entry point */ +extern void usersPrint(const USERS *); /**< Print data about the users loaded */ +extern void dcb_usersPrint(DCB *, const USERS *); /**< Print data about the users loaded */ MXS_END_DECLS diff --git a/query_classifier/qc_dummy/qc_dummy.cc b/query_classifier/qc_dummy/qc_dummy.cc index 2ee32e90c..1d326d499 100644 --- a/query_classifier/qc_dummy/qc_dummy.cc +++ b/query_classifier/qc_dummy/qc_dummy.cc @@ -45,11 +45,6 @@ bool qc_is_drop_table_query(GWBUF* querybuf) return false; } -char* qc_get_affected_fields(GWBUF* buf) -{ - return NULL; -} - bool qc_query_has_clause(GWBUF* buf) { return false; @@ -66,6 +61,22 @@ qc_query_op_t qc_get_operation(GWBUF* querybuf) return QUERY_OP_UNDEFINED; } +char* qc_sqlite_get_prepare_name(GWBUF* query) +{ + return NULL; +} + +qc_query_op_t qc_sqlite_get_prepare_operation(GWBUF* query) +{ + return QUERY_OP_UNDEFINED; +} + +void qc_sqlite_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_infos) +{ + *infos = NULL; + *n_infos = 0; +} + bool qc_init(const char* args) { return true; @@ -125,8 +136,10 @@ extern "C" qc_get_table_names, NULL, qc_query_has_clause, - qc_get_affected_fields, qc_get_database_names, + qc_get_prepare_name, + qc_get_prepare_operation, + qc_get_field_info, }; QUERY_CLASSIFIER* GetModuleObject() diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index d8b45ca97..e13a469ea 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -76,6 +76,9 @@ typedef struct parsing_info_st void* pi_handle; /*< parsing info object pointer */ char* pi_query_plain_str; /*< query as plain string */ void (*pi_done_fp)(void *); /*< clean-up function for parsing info */ + QC_FIELD_INFO* field_infos; + size_t field_infos_len; + size_t field_infos_capacity; #if defined(SS_DEBUG) skygw_chk_t pi_chk_tail; #endif @@ -681,12 +684,12 @@ static uint32_t resolve_query_type(THD* thd) #endif // TODO: This test is meaningless, since at this point // TODO: qtype (not type) is QUERY_TYPE_UNKNOWN. - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_UNKNOWN) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_LOCAL_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) + if (qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN) || + qc_query_is_type(qtype, QUERY_TYPE_LOCAL_READ) || + qc_query_is_type(qtype, QUERY_TYPE_READ) || + qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) { /** * These values won't change qtype more restrictive than write. @@ -1029,6 +1032,35 @@ char* qc_get_stmtname(GWBUF* buf) } #endif +/** + * Get the parsing info structure from a GWBUF + * + * @param querybuf A GWBUF + * + * @return The parsing info object, or NULL + */ +parsing_info_t* get_pinfo(GWBUF* querybuf) +{ + parsing_info_t *pi = NULL; + + if ((querybuf != NULL) && GWBUF_IS_PARSED(querybuf)) + { + pi = (parsing_info_t *) gwbuf_get_buffer_object_data(querybuf, GWBUF_PARSING_INFO); + } + + return pi; +} + +LEX* get_lex(parsing_info_t* pi) +{ + MYSQL* mysql = (MYSQL *) pi->pi_handle; + ss_dassert(mysql); + THD* thd = (THD *) mysql->thd; + ss_dassert(thd); + + return thd->lex; +} + /** * Get the parse tree from parsed querybuf. * @param querybuf The parsed GWBUF @@ -1038,31 +1070,19 @@ char* qc_get_stmtname(GWBUF* buf) */ LEX* get_lex(GWBUF* querybuf) { + LEX* lex = NULL; + parsing_info_t* pi = get_pinfo(querybuf); - parsing_info_t* pi; - MYSQL* mysql; - THD* thd; - - if (querybuf == NULL || !GWBUF_IS_PARSED(querybuf)) + if (pi) { - return NULL; + MYSQL* mysql = (MYSQL *) pi->pi_handle; + ss_dassert(mysql); + THD* thd = (THD *) mysql->thd; + ss_dassert(thd); + lex = thd->lex; } - pi = (parsing_info_t *) gwbuf_get_buffer_object_data(querybuf, GWBUF_PARSING_INFO); - - if (pi == NULL) - { - return NULL; - } - - if ((mysql = (MYSQL *) pi->pi_handle) == NULL || - (thd = (THD *) mysql->thd) == NULL) - { - ss_dassert(mysql != NULL && thd != NULL); - return NULL; - } - - return thd->lex; + return lex; } /** @@ -1284,315 +1304,6 @@ bool qc_is_drop_table_query(GWBUF* querybuf) return answer; } -inline void add_str(char** buf, int* buflen, int* bufsize, const char* str) -{ - int isize = strlen(str) + 1; - - if (*buf == NULL || isize + *buflen >= *bufsize) - { - *bufsize = (*bufsize) * 2 + isize; - char *tmp = (char*) realloc(*buf, (*bufsize) * sizeof (char)); - - if (tmp == NULL) - { - MXS_ERROR("Error: memory reallocation failed."); - free(*buf); - *buf = NULL; - *bufsize = 0; - } - - *buf = tmp; - } - - if (*buflen > 0) - { - if (*buf) - { - strcat(*buf, " "); - } - } - - if (*buf) - { - strcat(*buf, str); - } - - *buflen += isize; - -} - -typedef enum collect_source -{ - COLLECT_SELECT, - COLLECT_WHERE, - COLLECT_HAVING, - COLLECT_GROUP_BY, -} collect_source_t; - - -static void collect_name(Item* item, char** bufp, int* buflenp, int* bufsizep, List* excludep) -{ - const char* full_name = item->full_name(); - const char* name = strrchr(full_name, '.'); - - if (!name) - { - // No dot found. - name = full_name; - } - else - { - // Dot found, advance beyond it. - ++name; - } - - bool exclude = false; - - if (excludep) - { - List_iterator ilist(*excludep); - Item* exclude_item = (Item*) ilist.next(); - - for (; !exclude && (exclude_item != NULL); exclude_item = (Item*) ilist.next()) - { - if (exclude_item->name && (strcasecmp(name, exclude_item->name) == 0)) - { - exclude = true; - } - } - } - - if (!exclude) - { - add_str(bufp, buflenp, bufsizep, name); - } -} - -static void collect_affected_fields(collect_source_t source, - Item* item, char** bufp, int* buflenp, int* bufsizep, - List* excludep) -{ - switch (item->type()) - { - case Item::COND_ITEM: - { - Item_cond* cond_item = static_cast(item); - List_iterator ilist(*cond_item->argument_list()); - item = (Item*) ilist.next(); - - for (; item != NULL; item = (Item*) ilist.next()) - { - collect_affected_fields(source, item, bufp, buflenp, bufsizep, excludep); - } - } - break; - - case Item::FIELD_ITEM: - collect_name(item, bufp, buflenp, bufsizep, excludep); - break; - - case Item::REF_ITEM: - { - if (source != COLLECT_SELECT) - { - Item_ref* ref_item = static_cast(item); - - collect_name(item, bufp, buflenp, bufsizep, excludep); - - size_t n_items = ref_item->cols(); - - for (size_t i = 0; i < n_items; ++i) - { - Item* reffed_item = ref_item->element_index(i); - - if (reffed_item != ref_item) - { - collect_affected_fields(source, - ref_item->element_index(i), bufp, buflenp, bufsizep, - excludep); - } - } - } - } - break; - - case Item::ROW_ITEM: - { - Item_row* row_item = static_cast(item); - size_t n_items = row_item->cols(); - - for (size_t i = 0; i < n_items; ++i) - { - collect_affected_fields(source, row_item->element_index(i), bufp, buflenp, bufsizep, - excludep); - } - } - break; - - case Item::FUNC_ITEM: - case Item::SUM_FUNC_ITEM: - { - Item_func* func_item = static_cast(item); - Item** items = func_item->arguments(); - size_t n_items = func_item->argument_count(); - - for (size_t i = 0; i < n_items; ++i) - { - collect_affected_fields(source, items[i], bufp, buflenp, bufsizep, excludep); - } - } - break; - - case Item::SUBSELECT_ITEM: - { - Item_subselect* subselect_item = static_cast(item); - - switch (subselect_item->substype()) - { - case Item_subselect::IN_SUBS: - case Item_subselect::ALL_SUBS: - case Item_subselect::ANY_SUBS: - { - Item_in_subselect* in_subselect_item = static_cast(item); - -#if (((MYSQL_VERSION_MAJOR == 5) &&\ - ((MYSQL_VERSION_MINOR > 5) ||\ - ((MYSQL_VERSION_MINOR == 5) && (MYSQL_VERSION_PATCH >= 48))\ - )\ - ) ||\ - (MYSQL_VERSION_MAJOR >= 10)\ - ) - if (in_subselect_item->left_expr_orig) - { - collect_affected_fields(source, - in_subselect_item->left_expr_orig, bufp, buflenp, bufsizep, - excludep); - } -#else -#pragma message "Figure out what to do with versions < 5.5.48." -#endif - // TODO: Anything else that needs to be looked into? - } - break; - - case Item_subselect::EXISTS_SUBS: - case Item_subselect::SINGLEROW_SUBS: - // TODO: Handle these explicitly as well. - break; - - case Item_subselect::UNKNOWN_SUBS: - default: - MXS_ERROR("Unknown subselect type: %d", subselect_item->substype()); - break; - } - } - break; - - default: - break; - } -} - -char* qc_get_affected_fields(GWBUF* buf) -{ - LEX* lex; - int buffsz = 0, bufflen = 0; - char* where = NULL; - Item* item; - Item::Type itype; - - if (!buf) - { - return NULL; - } - - if (!ensure_query_is_parsed(buf)) - { - return NULL; - } - - if ((lex = get_lex(buf)) == NULL) - { - return NULL; - } - - lex->current_select = lex->all_selects_list; - - if ((where = (char*) malloc(sizeof (char)*1)) == NULL) - { - MXS_ERROR("Memory allocation failed."); - return NULL; - } - - *where = '\0'; - - while (lex->current_select) - { - - List_iterator ilist(lex->current_select->item_list); - item = (Item*) ilist.next(); - - for (; item != NULL; item = (Item*) ilist.next()) - { - collect_affected_fields(COLLECT_SELECT, item, &where, &buffsz, &bufflen, NULL); - } - - if (lex->current_select->group_list.first) - { - ORDER* order = lex->current_select->group_list.first; - while (order) - { - Item* item = *order->item; - - collect_affected_fields(COLLECT_GROUP_BY, item, &where, &buffsz, &bufflen, - &lex->current_select->item_list); - - order = order->next; - } - } - - if (lex->current_select->where) - { - collect_affected_fields(COLLECT_WHERE, lex->current_select->where, &where, &buffsz, &bufflen, - &lex->current_select->item_list); - } - - if (lex->current_select->having) - { - collect_affected_fields(COLLECT_HAVING, lex->current_select->having, &where, &buffsz, &bufflen, - &lex->current_select->item_list); - } - - lex->current_select = lex->current_select->next_select_in_list(); - } - - if ((lex->sql_command == SQLCOM_INSERT) || - (lex->sql_command == SQLCOM_INSERT_SELECT) || - (lex->sql_command == SQLCOM_REPLACE)) - { - List_iterator ilist(lex->field_list); - item = (Item*) ilist.next(); - - for (; item != NULL; item = (Item*) ilist.next()) - { - collect_affected_fields(COLLECT_SELECT, item, &where, &buffsz, &bufflen, NULL); - } - - if (lex->insert_list) - { - List_iterator ilist(*lex->insert_list); - item = (Item*) ilist.next(); - - for (; item != NULL; item = (Item*) ilist.next()) - { - collect_affected_fields(COLLECT_SELECT, item, &where, &buffsz, &bufflen, NULL); - } - } - } - - return where; -} - bool qc_query_has_clause(GWBUF* buf) { bool clause = false; @@ -1719,6 +1430,14 @@ static void parsing_info_done(void* ptr) free(pi->pi_query_plain_str); } + for (size_t i = 0; i < pi->field_infos_len; ++i) + { + free(pi->field_infos[i].database); + free(pi->field_infos[i].table); + free(pi->field_infos[i].column); + } + free(pi->field_infos); + free(pi); } } @@ -1852,6 +1571,7 @@ qc_query_op_t qc_get_operation(GWBUF* querybuf) case SQLCOM_INSERT: case SQLCOM_INSERT_SELECT: case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: operation = QUERY_OP_INSERT; break; @@ -1914,7 +1634,9 @@ char* qc_get_prepare_name(GWBUF* stmt) { LEX* lex = get_lex(stmt); - if (lex->sql_command == SQLCOM_PREPARE) + if ((lex->sql_command == SQLCOM_PREPARE) || + (lex->sql_command == SQLCOM_EXECUTE) || + (lex->sql_command == SQLCOM_DEALLOCATE_PREPARE)) { name = (char*)malloc(lex->prepared_stmt_name.length + 1); if (name) @@ -1929,6 +1651,635 @@ char* qc_get_prepare_name(GWBUF* stmt) return name; } +qc_query_op_t qc_get_prepare_operation(GWBUF* stmt) +{ + qc_query_op_t operation = QUERY_OP_UNDEFINED; + + if (stmt) + { + if (ensure_query_is_parsed(stmt)) + { + LEX* lex = get_lex(stmt); + + if (lex->sql_command == SQLCOM_PREPARE) + { + // This is terriby inefficient, but as qc_mysqlembedded is not used + // for anything else but comparisons it is ok. + const char* prepare_str = lex->prepared_stmt_code.str; + size_t prepare_str_len = lex->prepared_stmt_code.length; + + // MySQL does not parse e.g. "select * from x where ?=5". To work + // around that we'll replace all "?":s with "@a":s. We might replace + // something unnecessarily, but that won't hurt the classification. + size_t n_questions = 0; + const char* p = prepare_str; + while (p < prepare_str + prepare_str_len) + { + if (*p == '?') + { + ++n_questions; + } + + ++p; + } + + size_t payload_len = prepare_str_len + n_questions * 2 + 1; + size_t prepare_stmt_len = MYSQL_HEADER_LEN + payload_len; + + GWBUF* prepare_stmt = gwbuf_alloc(prepare_stmt_len); + + if (prepare_stmt) + { + // Encode the length of the payload in the 3 first bytes. + *((unsigned char*)GWBUF_DATA(prepare_stmt) + 0) = payload_len; + *((unsigned char*)GWBUF_DATA(prepare_stmt) + 1) = (payload_len >> 8); + *((unsigned char*)GWBUF_DATA(prepare_stmt) + 2) = (payload_len >> 16); + // Sequence id + *((unsigned char*)GWBUF_DATA(prepare_stmt) + 3) = 0x00; + // Payload, starts with command. + *((unsigned char*)GWBUF_DATA(prepare_stmt) + 4) = COM_QUERY; + // Is followed by the statement. + char *s = (char*)GWBUF_DATA(prepare_stmt) + 5; + p = prepare_str; + + while (p < prepare_str + prepare_str_len) + { + switch (*p) + { + case '?': + *s++ = '@'; + *s = 'a'; + break; + + default: + *s = *p; + } + + ++p; + ++s; + } + + operation = qc_get_operation(prepare_stmt); + gwbuf_free(prepare_stmt); + } + } + } + } + + return operation; +} + +static bool should_exclude(const char* name, List* excludep) +{ + bool exclude = false; + List_iterator ilist(*excludep); + Item* exclude_item; + + while (!exclude && (exclude_item = ilist++)) + { + const char* exclude_name = exclude_item->name; + + if (exclude_name && (strcasecmp(name, exclude_name) == 0)) + { + exclude = true; + } + + if (!exclude) + { + exclude_name = strrchr(exclude_item->full_name(), '.'); + + if (exclude_name) + { + ++exclude_name; // Char after the '.' + + if (strcasecmp(name, exclude_name) == 0) + { + exclude = true; + } + } + } + } + + return exclude; +} + +static void add_field_info(parsing_info_t* info, + const char* database, + const char* table, + const char* column, + uint32_t usage, + List* excludep) +{ + ss_dassert(column); + + QC_FIELD_INFO item = { (char*)database, (char*)table, (char*)column, usage }; + + size_t i; + for (i = 0; i < info->field_infos_len; ++i) + { + QC_FIELD_INFO* field_info = info->field_infos + i; + + if (strcasecmp(item.column, field_info->column) == 0) + { + if (!item.table && !field_info->table) + { + ss_dassert(!item.database && !field_info->database); + break; + } + else if (item.table && field_info->table && (strcmp(item.table, field_info->table) == 0)) + { + if (!item.database && !field_info->database) + { + break; + } + else if (item.database && + field_info->database && + (strcmp(item.database, field_info->database) == 0)) + { + break; + } + } + } + } + + QC_FIELD_INFO* field_infos = NULL; + + if (i == info->field_infos_len) // If true, the field was not present already. + { + // If only a column is specified, but not a table or database and we + // have a list of expressions that should be excluded, we check if the column + // value is present in that list. This is in order to exclude the second "d" in + // a statement like "select a as d from x where d = 2". + if (!(column && !table && !database && excludep && should_exclude(column, excludep))) + { + if (info->field_infos_len < info->field_infos_capacity) + { + field_infos = info->field_infos; + } + else + { + size_t capacity = info->field_infos_capacity ? 2 * info->field_infos_capacity : 8; + field_infos = (QC_FIELD_INFO*)realloc(info->field_infos, capacity * sizeof(QC_FIELD_INFO)); + + if (field_infos) + { + info->field_infos = field_infos; + info->field_infos_capacity = capacity; + } + } + } + } + else + { + info->field_infos[i].usage |= usage; + } + + // If field_infos is NULL, then the field was found and has already been noted. + if (field_infos) + { + item.database = item.database ? strdup(item.database) : NULL; + item.table = item.table ? strdup(item.table) : NULL; + ss_dassert(item.column); + item.column = strdup(item.column); + + // We are happy if we at least could dup the column. + + if (item.column) + { + field_infos[info->field_infos_len++] = item; + } + } +} + +static void add_field_info(parsing_info_t* pi, Item_field* item, uint32_t usage, List* excludep) +{ + const char* database = item->db_name; + const char* table = item->table_name; + const char* column = item->field_name; + + LEX* lex = get_lex(pi); + + switch (lex->sql_command) + { + case SQLCOM_SHOW_FIELDS: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "COLUMNS"; + } + break; + + case SQLCOM_SHOW_KEYS: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "STATISTICS"; + } + break; + + case SQLCOM_SHOW_STATUS: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "SESSION_STATUS"; + } + break; + + case SQLCOM_SHOW_TABLES: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "TABLE_NAMES"; + } + break; + + case SQLCOM_SHOW_TABLE_STATUS: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "TABLES"; + } + break; + + case SQLCOM_SHOW_VARIABLES: + if (!database) + { + database = "information_schema"; + } + + if (!table) + { + table = "SESSION_STATUS"; + } + break; + + default: + break; + } + + add_field_info(pi, database, table, column, usage, excludep); +} + +static void add_field_info(parsing_info_t* pi, Item* item, uint32_t usage, List* excludep) +{ + const char* database = NULL; + const char* table = NULL; + const char* column = item->name; + + add_field_info(pi, database, table, column, usage, excludep); +} + +typedef enum collect_source +{ + COLLECT_SELECT, + COLLECT_WHERE, + COLLECT_HAVING, + COLLECT_GROUP_BY, +} collect_source_t; + +static void update_field_infos(parsing_info_t* pi, + LEX* lex, + st_select_lex* select, + uint32_t usage, + List* excludep); + +static void update_field_infos(parsing_info_t* pi, + collect_source_t source, + Item* item, + uint32_t usage, + List* excludep) +{ + switch (item->type()) + { + case Item::COND_ITEM: + { + Item_cond* cond_item = static_cast(item); + List_iterator ilist(*cond_item->argument_list()); + + while (Item *i = ilist++) + { + update_field_infos(pi, source, i, usage, excludep); + } + } + break; + + case Item::FIELD_ITEM: + add_field_info(pi, static_cast(item), usage, excludep); + break; + + case Item::REF_ITEM: + { + if (source != COLLECT_SELECT) + { + Item_ref* ref_item = static_cast(item); + + add_field_info(pi, item, usage, excludep); + + size_t n_items = ref_item->cols(); + + for (size_t i = 0; i < n_items; ++i) + { + Item* reffed_item = ref_item->element_index(i); + + if (reffed_item != ref_item) + { + update_field_infos(pi, source, ref_item->element_index(i), usage, excludep); + } + } + } + } + break; + + case Item::ROW_ITEM: + { + Item_row* row_item = static_cast(item); + size_t n_items = row_item->cols(); + + for (size_t i = 0; i < n_items; ++i) + { + update_field_infos(pi, source, row_item->element_index(i), usage, excludep); + } + } + break; + + case Item::FUNC_ITEM: + case Item::SUM_FUNC_ITEM: + { + Item_func* func_item = static_cast(item); + Item** items = func_item->arguments(); + size_t n_items = func_item->argument_count(); + + for (size_t i = 0; i < n_items; ++i) + { + update_field_infos(pi, source, items[i], usage, excludep); + } + } + break; + + case Item::SUBSELECT_ITEM: + { + Item_subselect* subselect_item = static_cast(item); + + switch (subselect_item->substype()) + { + case Item_subselect::IN_SUBS: + case Item_subselect::ALL_SUBS: + case Item_subselect::ANY_SUBS: + { + Item_in_subselect* in_subselect_item = static_cast(item); + +#if (((MYSQL_VERSION_MAJOR == 5) &&\ + ((MYSQL_VERSION_MINOR > 5) ||\ + ((MYSQL_VERSION_MINOR == 5) && (MYSQL_VERSION_PATCH >= 48))\ + )\ + ) ||\ + (MYSQL_VERSION_MAJOR >= 10)\ + ) + if (in_subselect_item->left_expr_orig) + { + update_field_infos(pi, source, + in_subselect_item->left_expr_orig, usage, excludep); + } + st_select_lex* ssl = in_subselect_item->get_select_lex(); + if (ssl) + { + uint32_t sub_usage = usage; + + sub_usage &= ~QC_USED_IN_SELECT; + sub_usage |= QC_USED_IN_SUBSELECT; + + update_field_infos(pi, + get_lex(pi), + ssl, + sub_usage, + excludep); + } +#else +#pragma message "Figure out what to do with versions < 5.5.48." +#endif + // TODO: Anything else that needs to be looked into? + } + break; + + case Item_subselect::EXISTS_SUBS: + // TODO: Handle these explicitly as well. + break; + + case Item_subselect::SINGLEROW_SUBS: + { + Item_singlerow_subselect* ss_item = static_cast(item); + st_select_lex *ssl = ss_item->get_select_lex(); + + usage &= ~QC_USED_IN_SELECT; + usage |= QC_USED_IN_SUBSELECT; + + update_field_infos(pi, get_lex(pi), ssl, usage, excludep); + } + break; + + case Item_subselect::UNKNOWN_SUBS: + default: + MXS_ERROR("Unknown subselect type: %d", subselect_item->substype()); + break; + } + } + break; + + default: + break; + } +} + +static void update_field_infos(parsing_info_t* pi, + LEX* lex, + st_select_lex* select, + uint32_t usage, + List* excludep) +{ + List_iterator ilist(select->item_list); + + while (Item *item = ilist++) + { + update_field_infos(pi, COLLECT_SELECT, item, usage, NULL); + } + + if (select->group_list.first) + { + ORDER* order = select->group_list.first; + while (order) + { + Item* item = *order->item; + + update_field_infos(pi, COLLECT_GROUP_BY, item, QC_USED_IN_GROUP_BY, + &select->item_list); + + order = order->next; + } + } + + if (select->where) + { + update_field_infos(pi, COLLECT_WHERE, + select->where, + QC_USED_IN_WHERE, + &select->item_list); + } + +#if defined(COLLECT_HAVING_AS_WELL) + // A HAVING clause can only refer to fields that already have been + // mentioned. Consequently, they need not be collected. + if (select->having) + { + update_field_infos(pi, COLLECT_HAVING, + select->having, + 0, + &select->item_list); + } +#endif + + TABLE_LIST* table_list = select->get_table_list(); + + if (table_list) + { + st_select_lex *sl = table_list->get_single_select(); + + if (sl) + { + // This is for "SELECT 1 FROM (SELECT ...)" + usage &= ~QC_USED_IN_SELECT; + usage |= QC_USED_IN_SUBSELECT; + update_field_infos(pi, get_lex(pi), sl, usage, excludep); + } + } +} + +void qc_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, size_t* n_infos) +{ + if (!buf) + { + return; + } + + if (!ensure_query_is_parsed(buf)) + { + return; + } + + parsing_info_t* pi = get_pinfo(buf); + ss_dassert(pi); + + if (!pi->field_infos) + { + LEX* lex = get_lex(buf); + ss_dassert(lex); + + if (!lex) + { + return; + } + + uint32_t usage = 0; + + switch (lex->sql_command) + { + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + usage |= QC_USED_IN_SET; + break; + + default: + usage |= QC_USED_IN_SELECT; + } + + lex->current_select = &lex->select_lex; + + update_field_infos(pi, lex, &lex->select_lex, usage, NULL); + + List_iterator ilist(lex->value_list); + while (Item* item = ilist++) + { + update_field_infos(pi, COLLECT_SELECT, item, 0, NULL); + } + + if ((lex->sql_command == SQLCOM_INSERT) || + (lex->sql_command == SQLCOM_INSERT_SELECT) || + (lex->sql_command == SQLCOM_REPLACE)) + { + List_iterator ilist(lex->field_list); + while (Item *item = ilist++) + { + update_field_infos(pi, COLLECT_SELECT, item, 0, NULL); + } + + if (lex->insert_list) + { + List_iterator ilist(*lex->insert_list); + while (Item *item = ilist++) + { + update_field_infos(pi, COLLECT_SELECT, item, 0, NULL); + } + } + } + + if (lex->sql_command == SQLCOM_SET_OPTION) + { +#if defined(WAY_TO_DOWNCAST_SET_VAR_BASE_EXISTS) + // The list of set_var_base contains the value of variables. + // However, the actual type is a derived type of set_var_base + // and there is no information using which we could do the + // downcast... + List_iterator ilist(lex->var_list); + while (set_var_base* var = ilist++) + { + // Is set_var_base a set_var, set_var_user, set_var_password + // set_var_role + ... + } +#endif + // ...so, we will simply assume that any nested selects are + // from statements like "set @a:=(SELECT a from t1)". + + usage &= ~QC_USED_IN_SELECT; + usage |= QC_USED_IN_SUBSELECT; + + st_select_lex* select = lex->all_selects_list; + + while (select) + { + if (select->nest_level != 0) // Not the top-level select. + { + update_field_infos(pi, lex, select, usage, NULL); + } + + select = select->next_select_in_list(); + } + } + } + + *infos = pi->field_infos; + *n_infos = pi->field_infos_len; +} + namespace { @@ -2071,9 +2422,10 @@ static QUERY_CLASSIFIER qc = qc_get_table_names, NULL, qc_query_has_clause, - qc_get_affected_fields, qc_get_database_names, qc_get_prepare_name, + qc_get_prepare_operation, + qc_get_field_info, }; /* @see function load_module in load_utils.c for explanation of the following diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index bb180ac64..9ff54994e 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -60,9 +60,6 @@ typedef struct qc_sqlite_info uint32_t types; // The types of the query. qc_query_op_t operation; // The operation in question. - char* affected_fields; // The affected fields. - size_t affected_fields_len; // The used length of affected_fields. - size_t affected_fields_capacity; // The capacity of affected_fields. bool is_real_query; // SELECT, UPDATE, INSERT, DELETE or a variation. bool has_clause; // Has WHERE or HAVING. char** table_names; // Array of table names used in the query. @@ -79,8 +76,12 @@ typedef struct qc_sqlite_info int keyword_1; // The first encountered keyword. int keyword_2; // The second encountered keyword. char* prepare_name; // The name of a prepared statement. - size_t preparable_stmt_offset; // The start of the preparable statement. + qc_query_op_t prepare_operation; // The operation of a prepared statement. + char* preparable_stmt; // The preparable statement. size_t preparable_stmt_length; // The length of the preparable statement. + QC_FIELD_INFO *field_infos; // Pointer to array of QC_FIELD_INFOs. + size_t field_infos_len; // The used entries in field_infos. + size_t field_infos_capacity; // The capacity of the field_infos array. } QC_SQLITE_INFO; typedef enum qc_log_level @@ -122,11 +123,11 @@ typedef enum qc_token_position QC_TOKEN_RIGHT, // To the right, e.g: "b" in "a = b". } qc_token_position_t; -static void append_affected_field(QC_SQLITE_INFO* info, const char* s); static void buffer_object_free(void* data); static char** copy_string_array(char** strings, int* pn); static void enlarge_string_array(size_t n, size_t len, char*** ppzStrings, size_t* pCapacity); static bool ensure_query_is_parsed(GWBUF* query); +static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos); static void free_string_array(char** sa); static QC_SQLITE_INFO* get_query_info(GWBUF* query); static QC_SQLITE_INFO* info_alloc(void); @@ -139,17 +140,34 @@ static bool parse_query(GWBUF* query); static void parse_query_string(const char* query, size_t len); static bool query_is_parsed(GWBUF* query); static bool should_exclude(const char* zName, const ExprList* pExclude); -static void update_affected_fields(QC_SQLITE_INFO* info, - int prev_token, - const Expr* pExpr, - qc_token_position_t pos, - const ExprList* pExclude); -static void update_affected_fields_from_exprlist(QC_SQLITE_INFO* info, - const ExprList* pEList, const ExprList* pExclude); -static void update_affected_fields_from_idlist(QC_SQLITE_INFO* info, - const IdList* pIds, const ExprList* pExclude); -static void update_affected_fields_from_select(QC_SQLITE_INFO* info, - const Select* pSelect, const ExprList* pExclude); +static void update_field_info(QC_SQLITE_INFO* info, + const char* database, + const char* table, + const char* column, + uint32_t usage, + const ExprList* pExclude); +static void update_field_infos_from_expr(QC_SQLITE_INFO* info, + const struct Expr* pExpr, + uint32_t usage, + const ExprList* pExclude); +static void update_field_infos(QC_SQLITE_INFO* info, + int prev_token, + const Expr* pExpr, + uint32_t usage, + qc_token_position_t pos, + const ExprList* pExclude); +static void update_field_infos_from_exprlist(QC_SQLITE_INFO* info, + const ExprList* pEList, + uint32_t usage, + const ExprList* pExclude); +static void update_field_infos_from_idlist(QC_SQLITE_INFO* info, + const IdList* pIds, + uint32_t usage, + const ExprList* pExclude); +static void update_field_infos_from_select(QC_SQLITE_INFO* info, + const Select* pSelect, + uint32_t usage, + const ExprList* pExclude); static void update_database_names(QC_SQLITE_INFO* info, const char* name); static void update_names(QC_SQLITE_INFO* info, const char* zDatabase, const char* zTable); static void update_names_from_srclist(QC_SQLITE_INFO* info, const SrcList* pSrc); @@ -184,7 +202,7 @@ extern void exposed_sqlite3StartTable(Parse *pParse, /* Parser context */ int isView, /* True if this is a VIEW */ int isVirtual, /* True if this is a VIRTUAL table */ int noErr); /* Do nothing if table already exists */ -extern void maxscaleCollectInfoFromSelect(Parse*, Select*); +extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int); /** * Used for freeing a QC_SQLITE_INFO object added to a GWBUF. @@ -247,6 +265,21 @@ static bool ensure_query_is_parsed(GWBUF* query) return parsed; } +static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos) +{ + if (infos) + { + for (int i = 0; i < n_infos; ++i) + { + MXS_FREE(infos[i].database); + MXS_FREE(infos[i].table); + MXS_FREE(infos[i].column); + } + + MXS_FREE(infos); + } +} + static void free_string_array(char** sa) { if (sa) @@ -288,11 +321,13 @@ static QC_SQLITE_INFO* info_alloc(void) static void info_finish(QC_SQLITE_INFO* info) { - free(info->affected_fields); free_string_array(info->table_names); free_string_array(info->table_fullnames); free(info->created_table_name); free_string_array(info->database_names); + free(info->prepare_name); + free(info->preparable_stmt); + free_field_infos(info->field_infos, info->field_infos_len); } static void info_free(QC_SQLITE_INFO* info) @@ -312,9 +347,6 @@ static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info) info->types = QUERY_TYPE_UNKNOWN; info->operation = QUERY_OP_UNDEFINED; - info->affected_fields = NULL; - info->affected_fields_len = 0; - info->affected_fields_capacity = 0; info->is_real_query = false; info->has_clause = false; info->table_names = NULL; @@ -331,8 +363,12 @@ static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info) info->keyword_1 = 0; // Sqlite3 starts numbering tokens from 1, so 0 means info->keyword_2 = 0; // that we have not seen a keyword. info->prepare_name = NULL; - info->preparable_stmt_offset = 0; + info->prepare_operation = QUERY_OP_UNDEFINED; + info->preparable_stmt = NULL; info->preparable_stmt_length = 0; + info->field_infos = NULL; + info->field_infos_len = 0; + info->field_infos_capacity = 0; return info; } @@ -465,7 +501,7 @@ static bool parse_query(GWBUF* query) this_thread.info->query = NULL; this_thread.info->query_len = 0; - if (info->types & QUERY_TYPE_PREPARE_NAMED_STMT) + if ((info->types & QUERY_TYPE_PREPARE_NAMED_STMT) && info->preparable_stmt) { QC_SQLITE_INFO* preparable_info = info_alloc(); @@ -473,7 +509,7 @@ static bool parse_query(GWBUF* query) { this_thread.info = preparable_info; - const char *preparable_s = s + info->preparable_stmt_offset; + const char *preparable_s = info->preparable_stmt; size_t preparable_len = info->preparable_stmt_length; this_thread.info->query = preparable_s; @@ -482,9 +518,7 @@ static bool parse_query(GWBUF* query) this_thread.info->query = NULL; this_thread.info->query_len = 0; - // TODO: Perhaps the rest of the stuff should be - // TODO: copied as well. - info->operation = preparable_info->operation; + info->prepare_operation = preparable_info->operation; info_free(preparable_info); } @@ -626,42 +660,6 @@ static void log_invalid_data(GWBUF* query, const char* message) } } -static void append_affected_field(QC_SQLITE_INFO* info, const char* s) -{ - size_t len = strlen(s); - size_t required_len = info->affected_fields_len + len + 1; // 1 for NULL - - if (info->affected_fields_len != 0) - { - required_len += 1; // " " between fields - } - - if (required_len > info->affected_fields_capacity) - { - if (info->affected_fields_capacity == 0) - { - info->affected_fields_capacity = 32; - } - - while (required_len > info->affected_fields_capacity) - { - info->affected_fields_capacity *= 2; - } - - info->affected_fields = MXS_REALLOC(info->affected_fields, info->affected_fields_capacity); - MXS_ABORT_IF_NULL(info->affected_fields); - } - - if (info->affected_fields_len != 0) - { - strcpy(info->affected_fields + info->affected_fields_len, " "); - info->affected_fields_len += 1; - } - - strcpy(info->affected_fields + info->affected_fields_len, s); - info->affected_fields_len += len; -} - static bool should_exclude(const char* zName, const ExprList* pExclude) { int i; @@ -679,54 +677,214 @@ static bool should_exclude(const char* zName, const ExprList* pExclude) Expr* pExpr = item->pExpr; - if (pExpr->op == TK_DOT) + if (pExpr->op == TK_EQ) + { + // We end up here e.g with "UPDATE t set t.col = 5 ..." + // So, we pick the left branch. + pExpr = pExpr->pLeft; + } + + while (pExpr->op == TK_DOT) { pExpr = pExpr->pRight; } - // We need to ensure that we do not report fields where there - // is only a difference in case. E.g. - // SELECT A FROM tbl WHERE a = "foo"; - // Affected fields is "A" and not "A a". - if ((pExpr->op == TK_ID) && (strcasecmp(pExpr->u.zToken, zName) == 0)) + if (pExpr->op == TK_ID) { - break; + // We need to ensure that we do not report fields where there + // is only a difference in case. E.g. + // SELECT A FROM tbl WHERE a = "foo"; + // Affected fields is "A" and not "A a". + if (strcasecmp(pExpr->u.zToken, zName) == 0) + { + break; + } } } return i != pExclude->nExpr; } -static void update_affected_fields(QC_SQLITE_INFO* info, - int prev_token, - const Expr* pExpr, - qc_token_position_t pos, - const ExprList* pExclude) +static void update_field_info(QC_SQLITE_INFO* info, + const char* database, + const char* table, + const char* column, + uint32_t usage, + const ExprList* pExclude) +{ + ss_dassert(column); + + QC_FIELD_INFO item = { (char*)database, (char*)table, (char*)column, usage }; + + int i; + for (i = 0; i < info->field_infos_len; ++i) + { + QC_FIELD_INFO* field_info = info->field_infos + i; + + if (strcasecmp(item.column, field_info->column) == 0) + { + if (!item.table && !field_info->table) + { + ss_dassert(!item.database && !field_info->database); + break; + } + else if (item.table && field_info->table && (strcmp(item.table, field_info->table) == 0)) + { + if (!item.database && !field_info->database) + { + break; + } + else if (item.database && + field_info->database && + (strcmp(item.database, field_info->database) == 0)) + { + break; + } + } + } + } + + QC_FIELD_INFO* field_infos = NULL; + + if (i == info->field_infos_len) // If true, the field was not present already. + { + // If only a column is specified, but not a table or database and we + // have a list of expressions that should be excluded, we check if the column + // value is present in that list. This is in order to exclude the second "d" in + // a statement like "select a as d from x where d = 2". + if (!(column && !table && !database && pExclude && should_exclude(column, pExclude))) + { + if (info->field_infos_len < info->field_infos_capacity) + { + field_infos = info->field_infos; + } + else + { + size_t capacity = info->field_infos_capacity ? 2 * info->field_infos_capacity : 8; + field_infos = MXS_REALLOC(info->field_infos, capacity * sizeof(QC_FIELD_INFO)); + + if (field_infos) + { + info->field_infos = field_infos; + info->field_infos_capacity = capacity; + } + } + } + } + else + { + info->field_infos[i].usage |= usage; + } + + // If field_infos is NULL, then the field was found and has already been noted. + if (field_infos) + { + item.database = item.database ? MXS_STRDUP(item.database) : NULL; + item.table = item.table ? MXS_STRDUP(item.table) : NULL; + ss_dassert(item.column); + item.column = MXS_STRDUP(item.column); + + // We are happy if we at least could dup the column. + + if (item.column) + { + field_infos[info->field_infos_len++] = item; + } + } +} + +static void update_field_infos_from_expr(QC_SQLITE_INFO* info, + const struct Expr* pExpr, + uint32_t usage, + const ExprList* pExclude) +{ + QC_FIELD_INFO item = {}; + + if (pExpr->op == TK_ASTERISK) + { + item.column = "*"; + } + else if (pExpr->op == TK_ID) + { + // select a from... + item.column = pExpr->u.zToken; + } + else if (pExpr->op == TK_DOT) + { + if (pExpr->pLeft->op == TK_ID && + (pExpr->pRight->op == TK_ID || pExpr->pRight->op == TK_ASTERISK)) + { + // select a.b from... + item.table = pExpr->pLeft->u.zToken; + if (pExpr->pRight->op == TK_ID) + { + item.column = pExpr->pRight->u.zToken; + } + else + { + item.column = "*"; + } + } + else if (pExpr->pLeft->op == TK_ID && + pExpr->pRight->op == TK_DOT && + pExpr->pRight->pLeft->op == TK_ID && + (pExpr->pRight->pRight->op == TK_ID || pExpr->pRight->pRight->op == TK_ASTERISK)) + { + // select a.b.c from... + item.database = pExpr->pLeft->u.zToken; + item.table = pExpr->pRight->pLeft->u.zToken; + if (pExpr->pRight->pRight->op == TK_ID) + { + item.column = pExpr->pRight->pRight->u.zToken; + } + else + { + item.column = "*"; + } + } + } + + if (item.column) + { + bool should_update = true; + + if ((pExpr->flags & EP_DblQuoted) == 0) + { + if ((strcasecmp(item.column, "true") == 0) || (strcasecmp(item.column, "false") == 0)) + { + should_update = false; + } + } + + if (should_update) + { + update_field_info(info, item.database, item.table, item.column, usage, pExclude); + } + } +} + + +static void update_field_infos(QC_SQLITE_INFO* info, + int prev_token, + const Expr* pExpr, + uint32_t usage, + qc_token_position_t pos, + const ExprList* pExclude) { const char* zToken = pExpr->u.zToken; switch (pExpr->op) { - case TK_ASTERISK: // "select *" - append_affected_field(info, "*"); + case TK_ASTERISK: // select * + update_field_infos_from_expr(info, pExpr, usage, pExclude); break; - case TK_DOT: - // In case of "X.Y" qc_mysqlembedded returns "Y". - update_affected_fields(info, TK_DOT, pExpr->pRight, QC_TOKEN_RIGHT, pExclude); + case TK_DOT: // select a.b ... select a.b.c + update_field_infos_from_expr(info, pExpr, usage, pExclude); break; - case TK_ID: - if ((pExpr->flags & EP_DblQuoted) == 0) - { - if ((strcasecmp(zToken, "true") != 0) && (strcasecmp(zToken, "false") != 0)) - { - if (!pExclude || !should_exclude(zToken, pExclude)) - { - append_affected_field(info, zToken); - } - } - } + case TK_ID: // select a + update_field_infos_from_expr(info, pExpr, usage, pExclude); break; case TK_VARIABLE: @@ -787,12 +945,17 @@ static void update_affected_fields(QC_SQLITE_INFO* info, if (pExpr->pLeft) { - update_affected_fields(info, pExpr->op, pExpr->pLeft, QC_TOKEN_LEFT, pExclude); + update_field_infos(info, pExpr->op, pExpr->pLeft, usage, QC_TOKEN_LEFT, pExclude); } if (pExpr->pRight) { - update_affected_fields(info, pExpr->op, pExpr->pRight, QC_TOKEN_RIGHT, pExclude); + if (usage & QC_USED_IN_SET) + { + usage &= ~QC_USED_IN_SET; + } + + update_field_infos(info, pExpr->op, pExpr->pRight, usage, QC_TOKEN_RIGHT, pExclude); } if (pExpr->x.pList) @@ -802,7 +965,7 @@ static void update_affected_fields(QC_SQLITE_INFO* info, case TK_BETWEEN: case TK_CASE: case TK_FUNCTION: - update_affected_fields_from_exprlist(info, pExpr->x.pList, pExclude); + update_field_infos_from_exprlist(info, pExpr->x.pList, usage, pExclude); break; case TK_EXISTS: @@ -810,11 +973,15 @@ static void update_affected_fields(QC_SQLITE_INFO* info, case TK_SELECT: if (pExpr->flags & EP_xIsSelect) { - update_affected_fields_from_select(info, pExpr->x.pSelect, pExclude); + uint32_t sub_usage = usage; + + sub_usage &= ~QC_USED_IN_SELECT; + sub_usage |= QC_USED_IN_SUBSELECT; + update_field_infos_from_select(info, pExpr->x.pSelect, sub_usage, pExclude); } else { - update_affected_fields_from_exprlist(info, pExpr->x.pList, pExclude); + update_field_infos_from_exprlist(info, pExpr->x.pList, usage, pExclude); } break; } @@ -823,36 +990,36 @@ static void update_affected_fields(QC_SQLITE_INFO* info, } } -static void update_affected_fields_from_exprlist(QC_SQLITE_INFO* info, - const ExprList* pEList, - const ExprList* pExclude) +static void update_field_infos_from_exprlist(QC_SQLITE_INFO* info, + const ExprList* pEList, + uint32_t usage, + const ExprList* pExclude) { for (int i = 0; i < pEList->nExpr; ++i) { struct ExprList_item* pItem = &pEList->a[i]; - update_affected_fields(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, pExclude); + update_field_infos(info, 0, pItem->pExpr, usage, QC_TOKEN_MIDDLE, pExclude); } } -static void update_affected_fields_from_idlist(QC_SQLITE_INFO* info, - const IdList* pIds, - const ExprList* pExclude) +static void update_field_infos_from_idlist(QC_SQLITE_INFO* info, + const IdList* pIds, + uint32_t usage, + const ExprList* pExclude) { for (int i = 0; i < pIds->nId; ++i) { struct IdList_item* pItem = &pIds->a[i]; - if (!pExclude || !should_exclude(pItem->zName, pExclude)) - { - append_affected_field(info, pItem->zName); - } + update_field_info(info, NULL, NULL, pItem->zName, usage, pExclude); } } -static void update_affected_fields_from_select(QC_SQLITE_INFO* info, - const Select* pSelect, - const ExprList* pExclude) +static void update_field_infos_from_select(QC_SQLITE_INFO* info, + const Select* pSelect, + uint32_t usage, + const ExprList* pExclude) { if (pSelect->pSrc) { @@ -868,7 +1035,12 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, if (pSrc->a[i].pSelect) { - update_affected_fields_from_select(info, pSrc->a[i].pSelect, pExclude); + uint32_t sub_usage = usage; + + sub_usage &= ~QC_USED_IN_SELECT; + sub_usage |= QC_USED_IN_SUBSELECT; + + update_field_infos_from_select(info, pSrc->a[i].pSelect, sub_usage, pExclude); } #ifdef QC_COLLECT_NAMES_FROM_USING @@ -878,7 +1050,7 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, // does not reveal its value, right? if (pSrc->a[i].pUsing) { - update_affected_fields_from_idlist(info, pSrc->a[i].pUsing, pSelect->pEList); + update_field_infos_from_idlist(info, pSrc->a[i].pUsing, 0, pSelect->pEList); } #endif } @@ -886,24 +1058,28 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, if (pSelect->pEList) { - update_affected_fields_from_exprlist(info, pSelect->pEList, NULL); + update_field_infos_from_exprlist(info, pSelect->pEList, usage, NULL); } - if (pSelect->pWhere) + if (pSelect->pWhere) { info->has_clause = true; - update_affected_fields(info, 0, pSelect->pWhere, QC_TOKEN_MIDDLE, pSelect->pEList); + update_field_infos(info, 0, pSelect->pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pSelect->pEList); } if (pSelect->pGroupBy) { - update_affected_fields_from_exprlist(info, pSelect->pGroupBy, pSelect->pEList); + update_field_infos_from_exprlist(info, pSelect->pGroupBy, QC_USED_IN_GROUP_BY, pSelect->pEList); } if (pSelect->pHaving) { info->has_clause = true; - update_affected_fields(info, 0, pSelect->pHaving, QC_TOKEN_MIDDLE, pSelect->pEList); +#if defined(COLLECT_HAVING_AS_WELL) + // A HAVING clause can only refer to fields that already have been + // mentioned. Consequently, they need not be collected. + update_field_infos(info, 0, pSelect->pHaving, 0, QC_TOKEN_MIDDLE, pSelect->pEList); +#endif } } @@ -1148,7 +1324,7 @@ void mxs_sqlite3CreateView(Parse *pParse, /* The parsing context */ if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + update_field_infos_from_select(info, pSelect, QC_USED_IN_SELECT, NULL); info->is_real_query = false; } @@ -1218,7 +1394,7 @@ void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcLi if (pWhere) { - update_affected_fields(info, 0, pWhere, QC_TOKEN_MIDDLE, 0); + update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0); } exposed_sqlite3ExprDelete(pParse->db, pWhere); @@ -1282,7 +1458,7 @@ void mxs_sqlite3EndTable(Parse *pParse, /* Parse context */ { if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + update_field_infos_from_select(info, pSelect, QC_USED_IN_SELECT, NULL); info->is_real_query = false; } else if (pOldTable) @@ -1328,17 +1504,28 @@ void mxs_sqlite3Insert(Parse* pParse, if (pColumns) { - update_affected_fields_from_idlist(info, pColumns, NULL); + update_field_infos_from_idlist(info, pColumns, 0, NULL); } if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + uint32_t usage; + + if (pSelect->selFlags & SF_Values) // Synthesized from VALUES clause + { + usage = 0; + } + else + { + usage = QC_USED_IN_SELECT; + } + + update_field_infos_from_select(info, pSelect, usage, NULL); } if (pSet) { - update_affected_fields_from_exprlist(info, pSet, NULL); + update_field_infos_from_exprlist(info, pSet, 0, NULL); } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -1374,7 +1561,7 @@ int mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest) info->status = QC_QUERY_PARSED; info->operation = QUERY_OP_SELECT; - maxscaleCollectInfoFromSelect(pParse, p); + maxscaleCollectInfoFromSelect(pParse, p, 0); // NOTE: By convention, the select is deleted in parse.y. } else @@ -1462,18 +1649,13 @@ void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Exp { struct ExprList_item* pItem = &pChanges->a[i]; - if (pItem->zName) - { - append_affected_field(info, pItem->zName); - } - - update_affected_fields(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, NULL); + update_field_infos(info, 0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL); } } if (pWhere) { - update_affected_fields(info, 0, pWhere, QC_TOKEN_MIDDLE, NULL); + update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges); } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -1481,7 +1663,7 @@ void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Exp exposed_sqlite3ExprDelete(pParse->db, pWhere); } -void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect) +void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect, int sub_select) { QC_SQLITE_INFO* info = this_thread.info; ss_dassert(info); @@ -1499,7 +1681,9 @@ void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect) info->types = QUERY_TYPE_READ; } - update_affected_fields_from_select(info, pSelect, NULL); + uint32_t usage = sub_select ? QC_USED_IN_SUBSELECT : QC_USED_IN_SELECT; + + update_field_infos_from_select(info, pSelect, usage, NULL); } void maxscaleAlterTable(Parse *pParse, /* Parser context. */ @@ -1651,9 +1835,13 @@ void maxscaleExplain(Parse* pParse, SrcList* pName) info->status = QC_QUERY_PARSED; info->types = QUERY_TYPE_READ; update_names(info, "information_schema", "COLUMNS"); - append_affected_field(info, - "COLUMN_DEFAULT COLUMN_KEY COLUMN_NAME " - "COLUMN_TYPE EXTRA IS_NULLABLE"); + uint32_t u = QC_USED_IN_SELECT; + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); exposed_sqlite3SrcListDelete(pParse->db, pName); } @@ -2014,11 +2202,12 @@ void maxscalePrepare(Parse* pParse, Token* pName, Token* pStmt) info->prepare_name[pName->n] = 0; } - // We store the position of the preparable statement inside the original - // statement. That will allow us to later create a new GWBUF of the - // parsable statment and parse that. - info->preparable_stmt_offset = pParse->sLastToken.z - pParse->zTail + 1; // Ignore starting quote. - info->preparable_stmt_length = pStmt->n - 2; // Remove starting and ending quotes. + info->preparable_stmt_length = pStmt->n - 2; + info->preparable_stmt = MXS_MALLOC(info->preparable_stmt_length); + if (info->preparable_stmt) + { + memcpy(info->preparable_stmt, pStmt->z + 1, pStmt->n - 2); + } } void maxscalePrivileges(Parse* pParse, int kind) @@ -2172,7 +2361,8 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) if (pValue->op == TK_SELECT) { - update_affected_fields_from_select(info, pValue->x.pSelect, NULL); + update_field_infos_from_select(info, pValue->x.pSelect, + QC_USED_IN_SUBSELECT, NULL); info->is_real_query = false; // TODO: This is what qc_mysqlembedded claims. } } @@ -2220,6 +2410,8 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) zName = name; } + uint32_t u = QC_USED_IN_SELECT; + switch (pShow->what) { case MXS_SHOW_COLUMNS: @@ -2228,16 +2420,24 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) update_names(info, "information_schema", "COLUMNS"); if (pShow->data == MXS_SHOW_COLUMNS_FULL) { - append_affected_field(info, - "COLLATION_NAME COLUMN_COMMENT COLUMN_DEFAULT " - "COLUMN_KEY COLUMN_NAME COLUMN_TYPE EXTRA " - "IS_NULLABLE PRIVILEGES"); + update_field_info(info, "information_schema", "COLUMNS", "COLLATION_NAME", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_COMMENT", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "PRIVILEGES", u, NULL); } else { - append_affected_field(info, - "COLUMN_DEFAULT COLUMN_KEY COLUMN_NAME " - "COLUMN_TYPE EXTRA IS_NULLABLE"); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); + update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); } } break; @@ -2260,7 +2460,7 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_SHOW_DATABASES; update_names(info, "information_schema", "SCHEMATA"); - append_affected_field(info, "SCHEMA_NAME"); + update_field_info(info, "information_schema", "SCHEMATA", "SCHEMA_NAME", u, NULL); } break; @@ -2270,10 +2470,19 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_WRITE; update_names(info, "information_schema", "STATISTICS"); - append_affected_field(info, - "CARDINALITY COLLATION COLUMN_NAME COMMENT INDEX_COMMENT " - "INDEX_NAME INDEX_TYPE NON_UNIQUE NULLABLE PACKED SEQ_IN_INDEX " - "SUB_PART TABLE_NAME"); + update_field_info(info, "information_schema", "STATISTICS", "CARDINALITY", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "COLLATION", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "COLUMN_NAME", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "COMMENT", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "INDEX_COMMENT", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "INDEX_NAME", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "INDEX_TYPE", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "NON_UNIQUE", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "NULLABLE", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "PACKED", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "SEQ_IN_INDEX", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "SUB_PART", u, NULL); + update_field_info(info, "information_schema", "STATISTICS", "TABLE_NAME", u, NULL); } break; @@ -2281,12 +2490,24 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_WRITE; update_names(info, "information_schema", "TABLES"); - append_affected_field(info, - "AUTO_INCREMENT AVG_ROW_LENGTH CHECKSUM CHECK_TIME " - "CREATE_OPTIONS CREATE_TIME DATA_FREE DATA_LENGTH " - "ENGINE INDEX_LENGTH MAX_DATA_LENGTH ROW_FORMAT " - "TABLE_COLLATION TABLE_COMMENT TABLE_NAME " - "TABLE_ROWS UPDATE_TIME VERSION"); + update_field_info(info, "information_schema", "TABLES", "AUTO_INCREMENT", u, NULL); + update_field_info(info, "information_schema", "TABLES", "AVG_ROW_LENGTH", u, NULL); + update_field_info(info, "information_schema", "TABLES", "CHECKSUM", u, NULL); + update_field_info(info, "information_schema", "TABLES", "CHECK_TIME", u, NULL); + update_field_info(info, "information_schema", "TABLES", "CREATE_OPTIONS", u, NULL); + update_field_info(info, "information_schema", "TABLES", "CREATE_TIME", u, NULL); + update_field_info(info, "information_schema", "TABLES", "DATA_FREE", u, NULL); + update_field_info(info, "information_schema", "TABLES", "DATA_LENGTH", u, NULL); + update_field_info(info, "information_schema", "TABLES", "ENGINE", u, NULL); + update_field_info(info, "information_schema", "TABLES", "INDEX_LENGTH", u, NULL); + update_field_info(info, "information_schema", "TABLES", "MAX_DATA_LENGTH", u, NULL); + update_field_info(info, "information_schema", "TABLES", "ROW_FORMAT", u, NULL); + update_field_info(info, "information_schema", "TABLES", "TABLE_COLLATION", u, NULL); + update_field_info(info, "information_schema", "TABLES", "TABLE_COMMENT", u, NULL); + update_field_info(info, "information_schema", "TABLES", "TABLE_NAME", u, NULL); + update_field_info(info, "information_schema", "TABLES", "TABLE_ROWS", u, NULL); + update_field_info(info, "information_schema", "TABLES", "UPDATE_TIME", u, NULL); + update_field_info(info, "information_schema", "TABLES", "VERSION", u, NULL); } break; @@ -2300,7 +2521,8 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) // TODO: qc_mysqlembedded does not set the type bit. info->types = QUERY_TYPE_UNKNOWN; update_names(info, "information_schema", "SESSION_STATUS"); - append_affected_field(info, "VARIABLE_NAME VARIABLE_VALUE"); + update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", u, NULL); + update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", u, NULL); break; case MXS_SHOW_STATUS_MASTER: @@ -2325,7 +2547,7 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_SHOW_TABLES; update_names(info, "information_schema", "TABLE_NAMES"); - append_affected_field(info, "TABLE_NAME"); + update_field_info(info, "information_schema", "TABLE_NAMES", "TABLE_NAME", u, NULL); } break; @@ -2340,7 +2562,8 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) info->types = QUERY_TYPE_SYSVAR_READ; } update_names(info, "information_schema", "SESSION_VARIABLES"); - append_affected_field(info, "VARIABLE_NAME VARIABLE_VALUE"); + update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", u, NULL); + update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", u, NULL); } break; @@ -2417,7 +2640,6 @@ static bool qc_sqlite_is_real_query(GWBUF* query); static char** qc_sqlite_get_table_names(GWBUF* query, int* tblsize, bool fullnames); static char* qc_sqlite_get_canonical(GWBUF* query); static bool qc_sqlite_query_has_clause(GWBUF* query); -static char* qc_sqlite_get_affected_fields(GWBUF* query); static char** qc_sqlite_get_database_names(GWBUF* query, int* sizep); static bool get_key_and_value(char* arg, const char** pkey, const char** pvalue) @@ -2825,42 +3047,6 @@ static bool qc_sqlite_query_has_clause(GWBUF* query) return has_clause; } -static char* qc_sqlite_get_affected_fields(GWBUF* query) -{ - QC_TRACE(); - ss_dassert(this_unit.initialized); - ss_dassert(this_thread.initialized); - - char* affected_fields = NULL; - QC_SQLITE_INFO* info = get_query_info(query); - - if (info) - { - if (qc_info_is_valid(info->status)) - { - affected_fields = info->affected_fields; - } - else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - log_invalid_data(query, "cannot report what fields are affected"); - } - } - else - { - MXS_ERROR("The query could not be parsed. Response not valid."); - } - - if (!affected_fields) - { - affected_fields = ""; - } - - affected_fields = MXS_STRDUP(affected_fields); - MXS_ABORT_IF_NULL(affected_fields); - - return affected_fields; -} - static char** qc_sqlite_get_database_names(GWBUF* query, int* sizep) { QC_TRACE(); @@ -2923,6 +3109,63 @@ static char* qc_sqlite_get_prepare_name(GWBUF* query) return name; } +static qc_query_op_t qc_sqlite_get_prepare_operation(GWBUF* query) +{ + QC_TRACE(); + ss_dassert(this_unit.initialized); + ss_dassert(this_thread.initialized); + + qc_query_op_t op = QUERY_OP_UNDEFINED; + QC_SQLITE_INFO* info = get_query_info(query); + + if (info) + { + if (qc_info_is_valid(info->status)) + { + op = info->prepare_operation; + } + else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + log_invalid_data(query, "cannot report the operation of a prepared statement"); + } + } + else + { + MXS_ERROR("The query could not be parsed. Response not valid."); + } + + return op; +} + +void qc_sqlite_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_infos) +{ + QC_TRACE(); + ss_dassert(this_unit.initialized); + ss_dassert(this_thread.initialized); + + *infos = NULL; + *n_infos = 0; + + QC_SQLITE_INFO* info = get_query_info(query); + + if (info) + { + if (qc_info_is_valid(info->status)) + { + *infos = info->field_infos; + *n_infos = info->field_infos_len; + } + else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + log_invalid_data(query, "cannot report field info"); + } + } + else + { + MXS_ERROR("The query could not be parsed. Response not valid."); + } +} + /** * EXPORTS */ @@ -2944,9 +3187,10 @@ static QUERY_CLASSIFIER qc = qc_sqlite_get_table_names, NULL, qc_sqlite_query_has_clause, - qc_sqlite_get_affected_fields, qc_sqlite_get_database_names, qc_sqlite_get_prepare_name, + qc_sqlite_get_prepare_operation, + qc_sqlite_get_field_info, }; diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index b015bc5c7..ab6b50ea7 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -94,7 +94,7 @@ extern int mxs_sqlite3Select(Parse*, Select*, SelectDest*); extern void mxs_sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); extern void mxs_sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int); -extern void maxscaleCollectInfoFromSelect(Parse*, Select*); +extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int); extern void maxscaleAlterTable(Parse*, mxs_alter_t command, SrcList*, Token*); extern void maxscaleCall(Parse*, SrcList* pName); @@ -1444,7 +1444,7 @@ table_factor(A) ::= nm(X) DOT nm(Y) as_opt id(Z). { } table_factor(A) ::= LP oneselect(S) RP as_opt id. { - maxscaleCollectInfoFromSelect(pParse, S); + maxscaleCollectInfoFromSelect(pParse, S, 1); sqlite3SelectDelete(pParse->db, S); A = 0; } diff --git a/query_classifier/test/classify.c b/query_classifier/test/classify.c index 9a51cc9fe..c656abae3 100644 --- a/query_classifier/test/classify.c +++ b/query_classifier/test/classify.c @@ -196,10 +196,12 @@ int test(FILE* input, FILE* expected) { tok = strpbrk(strbuff, ";"); unsigned int qlen = tok - strbuff + 1; - GWBUF* buff = gwbuf_alloc(qlen + 6); - *((unsigned char*)(GWBUF_DATA(buff))) = qlen; - *((unsigned char*)(GWBUF_DATA(buff) + 1)) = (qlen >> 8); - *((unsigned char*)(GWBUF_DATA(buff) + 2)) = (qlen >> 16); + unsigned int payload_len = qlen + 1; + unsigned int buf_len = payload_len + 4; + GWBUF* buff = gwbuf_alloc(buf_len); + *((unsigned char*)(GWBUF_DATA(buff))) = payload_len; + *((unsigned char*)(GWBUF_DATA(buff) + 1)) = (payload_len >> 8); + *((unsigned char*)(GWBUF_DATA(buff) + 2)) = (payload_len >> 16); *((unsigned char*)(GWBUF_DATA(buff) + 3)) = 0x00; *((unsigned char*)(GWBUF_DATA(buff) + 4)) = 0x03; memcpy(GWBUF_DATA(buff) + 5, strbuff, qlen); diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 066f086ba..102b9e42b 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -715,68 +715,6 @@ ostream& operator << (ostream& o, const std::set& s) return o; } -bool compare_get_affected_fields(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, - QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) -{ - bool success = false; - const char HEADING[] = "qc_get_affected_fields : "; - - char* rv1 = pClassifier1->qc_get_affected_fields(pCopy1); - char* rv2 = pClassifier2->qc_get_affected_fields(pCopy2); - - std::set fields1; - std::set fields2; - - if (rv1) - { - add_fields(fields1, rv1); - } - - if (rv2) - { - add_fields(fields2, rv2); - } - - stringstream ss; - ss << HEADING; - - if ((!rv1 && !rv2) || (rv1 && rv2 && (fields1 == fields2))) - { - ss << "Ok : " << fields1; - success = true; - } - else - { - ss << "ERR: "; - if (rv1) - { - ss << fields1; - } - else - { - ss << "NULL"; - } - - ss << " != "; - - if (rv2) - { - ss << fields2; - } - else - { - ss << "NULL"; - } - } - - report(success, ss.str()); - - free(rv1); - free(rv2); - - return success; -} - bool compare_get_database_names(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { @@ -844,6 +782,280 @@ bool compare_get_prepare_name(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, return success; } +bool compare_get_prepare_operation(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, + QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) +{ + bool success = false; + const char HEADING[] = "qc_get_prepare_operation : "; + + qc_query_op_t rv1 = pClassifier1->qc_get_prepare_operation(pCopy1); + qc_query_op_t rv2 = pClassifier2->qc_get_prepare_operation(pCopy2); + + stringstream ss; + ss << HEADING; + + if (rv1 == rv2) + { + ss << "Ok : " << qc_op_to_string(rv1); + success = true; + } + else + { + ss << "ERR: " << qc_op_to_string(rv1) << " != " << qc_op_to_string(rv2); + } + + report(success, ss.str()); + + return success; +} + +bool operator == (const QC_FIELD_INFO& lhs, const QC_FIELD_INFO& rhs) +{ + bool rv = false; + if (lhs.column && rhs.column && (strcasecmp(lhs.column, rhs.column) == 0)) + { + if (!lhs.table && !rhs.table) + { + rv = true; + } + else if (lhs.table && rhs.table && (strcmp(lhs.table, rhs.table) == 0)) + { + if (!lhs.database && !rhs.database) + { + rv = true; + } + else if (lhs.database && rhs.database && (strcmp(lhs.database, rhs.database) == 0)) + { + rv = true; + } + } + } + + return rv; +} + +ostream& operator << (ostream& out, const QC_FIELD_INFO& x) +{ + if (x.database) + { + out << x.database; + out << "."; + ss_dassert(x.table); + } + + if (x.table) + { + out << x.table; + out << "."; + } + + ss_dassert(x.column); + out << x.column; + + return out; +} + +class QcFieldInfo +{ +public: + QcFieldInfo(const QC_FIELD_INFO& info) + : m_database(info.database ? info.database : "") + , m_table(info.table ? info.table : "") + , m_column(info.column ? info.column : "") + , m_usage(info.usage) + {} + + bool eq(const QcFieldInfo& rhs) const + { + return + m_database == rhs.m_database && + m_table == rhs.m_table && + m_column == rhs.m_column && + m_usage == rhs.m_usage; + } + + bool lt(const QcFieldInfo& rhs) const + { + bool rv = false; + + if (m_database < rhs.m_database) + { + rv = true; + } + else if (m_database > rhs.m_database) + { + rv = false; + } + else + { + if (m_table < rhs.m_table) + { + rv = true; + } + else if (m_table > rhs.m_table) + { + rv = false; + } + else + { + if (m_column < rhs.m_column) + { + rv = true; + } + else if (m_column > rhs.m_column) + { + rv = false; + } + else + { + rv = (m_usage < rhs.m_usage); + } + } + } + + return rv; + } + + void print(ostream& out) const + { + if (!m_database.empty()) + { + out << m_database; + out << "."; + } + + if (!m_table.empty()) + { + out << m_table; + out << "."; + } + + out << m_column; + + out << "("; + char* s = qc_field_usage_mask_to_string(m_usage); + out << s; + free(s); + out << ")"; + } + +private: + std::string m_database; + std::string m_table; + std::string m_column; + uint32_t m_usage; +}; + +ostream& operator << (ostream& out, const QcFieldInfo& x) +{ + x.print(out); + return out; +} + +ostream& operator << (ostream& out, std::set& x) +{ + std::set::iterator i = x.begin(); + std::set::iterator end = x.end(); + + while (i != end) + { + out << *i++; + if (i != end) + { + out << " "; + } + } + + return out; +} + +bool operator < (const QcFieldInfo& lhs, const QcFieldInfo& rhs) +{ + return lhs.lt(rhs); +} + +bool operator == (const QcFieldInfo& lhs, const QcFieldInfo& rhs) +{ + return lhs.eq(rhs); +} + +bool are_equal(const QC_FIELD_INFO* fields1, size_t n_fields1, + const QC_FIELD_INFO* fields2, size_t n_fields2) +{ + bool rv = (n_fields1 == n_fields2); + + if (rv) + { + + size_t i = 0; + while (rv && (i < n_fields1)) + { + rv = *fields1 == *fields2; + ++i; + } + } + + return rv; +} + +ostream& print(ostream& out, const QC_FIELD_INFO* fields, size_t n_fields) +{ + size_t i = 0; + while (i < n_fields) + { + out << fields[i++]; + + if (i != n_fields) + { + out << " "; + } + } + + return out; +} + +bool compare_get_field_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, + QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) +{ + bool success = false; + const char HEADING[] = "qc_get_field_info : "; + + const QC_FIELD_INFO* infos1; + const QC_FIELD_INFO* infos2; + size_t n_infos1; + size_t n_infos2; + + pClassifier1->qc_get_field_info(pCopy1, &infos1, &n_infos1); + pClassifier2->qc_get_field_info(pCopy2, &infos2, &n_infos2); + + stringstream ss; + ss << HEADING; + + int i; + + std::set f1; + f1.insert(infos1, infos1 + n_infos1); + + std::set f2; + f2.insert(infos2, infos2 + n_infos2); + + if (f1 == f2) + { + ss << "Ok : "; + ss << f1; + success = true; + } + else + { + ss << "ERR: " << f1 << " != " << f2; + } + + report(success, ss.str()); + + return success; +} + + bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& s) { GWBUF* pCopy1 = create_gwbuf(s); @@ -860,9 +1072,10 @@ bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, con errors += !compare_get_table_names(pClassifier1, pCopy1, pClassifier2, pCopy2, false); errors += !compare_get_table_names(pClassifier1, pCopy1, pClassifier2, pCopy2, true); errors += !compare_query_has_clause(pClassifier1, pCopy1, pClassifier2, pCopy2); - errors += !compare_get_affected_fields(pClassifier1, pCopy1, pClassifier2, pCopy2); errors += !compare_get_database_names(pClassifier1, pCopy1, pClassifier2, pCopy2); errors += !compare_get_prepare_name(pClassifier1, pCopy1, pClassifier2, pCopy2); + errors += !compare_get_prepare_operation(pClassifier1, pCopy1, pClassifier2, pCopy2); + errors += !compare_get_field_info(pClassifier1, pCopy1, pClassifier2, pCopy2); gwbuf_free(pCopy1); gwbuf_free(pCopy2); diff --git a/query_classifier/test/insert.test b/query_classifier/test/insert.test index 2d95dffa1..ce3034f78 100644 --- a/query_classifier/test/insert.test +++ b/query_classifier/test/insert.test @@ -222,9 +222,11 @@ replace into t1 values (4, 4); select row_count(); # Reports that 2 rows are affected. This conforms to documentation. # (Useful for differentiating inserts from updates). -insert into t1 values (2, 2) on duplicate key update data= data + 10; +# MXSTODO: insert into t1 values (2, 2) on duplicate key update data= data + 10; +# qc_sqlite: Cannot parse "on duplicate" select row_count(); -insert into t1 values (5, 5) on duplicate key update data= data + 10; +# MXSTODO: insert into t1 values (5, 5) on duplicate key update data= data + 10; +# qc_sqlite: Cannot parse "on duplicate" select row_count(); drop table t1; diff --git a/query_classifier/test/qc_sqlite_unsupported.test b/query_classifier/test/qc_sqlite_unsupported.test index 49480924a..11a2dd999 100644 --- a/query_classifier/test/qc_sqlite_unsupported.test +++ b/query_classifier/test/qc_sqlite_unsupported.test @@ -20,3 +20,8 @@ SET @x:= (SELECT h FROM t1 WHERE (a,b,c,d,e,f,g)=(1,2,3,4,5,6,7)); # REMOVE: expr(A) ::= LP(B) expr(X) RP(E). {A.pExpr = X.pExpr; spanSet(&A,&B,&E);} # REMOVE: expr(A) ::= LP expr(X) COMMA(OP) expr(Y) RP. {spanBinaryExpr(&A,pParse,@OP,&X,&Y);} # ADD : expr(A) ::= LP exprlist RP. { ... } + +insert into t1 values (2, 2) on duplicate key update data= data + 10; +# Problem: warning: [qc_sqlite] Statement was only partially parsed (Sqlite3 error: SQL logic error +# or missing database, near "on": syntax error): "insert into t1 values (2, 2) on duplicate +# key update data= data + 10;" \ No newline at end of file diff --git a/server/core/atomic.c b/server/core/atomic.c index fe5c5eb45..72b76f432 100644 --- a/server/core/atomic.c +++ b/server/core/atomic.c @@ -12,7 +12,7 @@ */ /** - * @file atomic.c - Implementation of atomic opertions for the gateway + * @file atomic.c - Implementation of atomic operations for MaxScale * * @verbatim * Revision History @@ -23,22 +23,6 @@ * @endverbatim */ -/** - * Implementation of an atomic add operation for the GCC environment, or the - * X86 processor. If we are working within GNU C then we can use the GCC - * atomic add built in function, which is portable across platforms that - * implement GCC. Otherwise, this function currently supports only X86 - * architecture (without further development). - * - * Adds a value to the contents of a location pointed to by the first parameter. - * The add operation is atomic and the return value is the value stored in the - * location prior to the operation. The number that is added may be signed, - * therefore atomic_subtract is merely an atomic add with a negative value. - * - * @param variable Pointer the the variable to add to - * @param value Value to be added - * @return The value of variable before the add occurred - */ int atomic_add(int *variable, int value) { diff --git a/server/core/config.c b/server/core/config.c index f85d685d6..43a738397 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -65,6 +65,7 @@ #include #include #include +#include typedef struct duplicate_context { @@ -540,6 +541,46 @@ static bool config_load_dir(const char *dir, DUPLICATE_CONTEXT *dcontext, CONFIG return rv == 0; } +/** + * Check if a directory exists + * + * This function also logs warnings if the directory cannot be accessed or if + * the file is not a directory. + * @param dir Directory to check + * @return True if the file is an existing directory + */ +static bool is_directory(const char *dir) +{ + bool rval = false; + struct stat st; + if (stat(dir, &st) == -1) + { + if (errno == ENOENT) + { + MXS_NOTICE("%s does not exist, not reading.", dir); + } + else + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_WARNING("Could not access %s, not reading: %s", + dir, strerror_r(errno, errbuf, sizeof(errbuf))); + } + } + else + { + if (S_ISDIR(st.st_mode)) + { + rval = true; + } + else + { + MXS_WARNING("%s exists, but it is not a directory. Ignoring.", dir); + } + } + + return rval; +} + /** * @brief Load the specified configuration file for MaxScale * @@ -573,37 +614,25 @@ config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONT rval = true; - struct stat st; - if (stat(dir, &st) == -1) + if (is_directory(dir)) { - if (errno == ENOENT) - { - MXS_NOTICE("%s does not exist, not reading.", dir); - } - else - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_WARNING("Could not access %s, not reading: %s", - dir, strerror_r(errno, errbuf, sizeof(errbuf))); - } + rval = config_load_dir(dir, &dcontext, &ccontext); } - else + + /** Create the persisted configuration directory if it doesn't exist */ + const char* persist_cnf = get_config_persistdir(); + mxs_mkdir_all(persist_cnf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + if (is_directory(persist_cnf)) { - if (S_ISDIR(st.st_mode)) - { - rval = config_load_dir(dir, &dcontext, &ccontext); - } - else - { - MXS_WARNING("%s exists, but it is not a directory. Ignoring.", dir); - } + rval = config_load_dir(persist_cnf, &dcontext, &ccontext); } if (rval) { - if (check_config_objects(ccontext.next) && process_config(ccontext.next)) + if (!check_config_objects(ccontext.next) || !process_config(ccontext.next)) { - rval = true; + rval = false; } } } @@ -633,13 +662,9 @@ config_load(const char *filename) global_defaults(); feedback_defaults(); + config_file = filename; bool rval = config_load_and_process(filename, process_config_context); - if (rval) - { - config_file = filename; - } - return rval; } @@ -1804,10 +1829,9 @@ process_config_update(CONFIG_CONTEXT *context) if (address && port && (server = server_find(address, atoi(port))) != NULL) { - char *protocol = config_get_value(obj->parameters, "protocol"); char *monuser = config_get_value(obj->parameters, "monuser"); char *monpw = config_get_value(obj->parameters, "monpw"); - server_update(server, protocol, monuser, monpw); + server_update_credentials(server, monuser, monpw); obj->element = server; } else @@ -2706,11 +2730,7 @@ int create_new_server(CONFIG_CONTEXT *obj) if (address && port && protocol) { - if ((obj->element = server_alloc(address, protocol, atoi(port), auth, auth_opts))) - { - server_set_unique_name(obj->element, obj->object); - } - else + if ((obj->element = server_alloc(obj->object, address, atoi(port), protocol, auth, auth_opts)) == NULL) { MXS_ERROR("Failed to create a new server, memory allocation failed."); error_count++; @@ -2743,22 +2763,32 @@ int create_new_server(CONFIG_CONTEXT *obj) const char *poolmax = config_get_value_string(obj->parameters, "persistpoolmax"); if (poolmax) { - server->persistpoolmax = strtol(poolmax, &endptr, 0); - if (*endptr != '\0') + long int persistpoolmax = strtol(poolmax, &endptr, 0); + if (*endptr != '\0' || persistpoolmax < 0) { MXS_ERROR("Invalid value for 'persistpoolmax' for server %s: %s", server->unique_name, poolmax); + error_count++; + } + else + { + server->persistpoolmax = persistpoolmax; } } const char *persistmax = config_get_value_string(obj->parameters, "persistmaxtime"); if (persistmax) { - server->persistmaxtime = strtol(persistmax, &endptr, 0); - if (*endptr != '\0') + long int persistmaxtime = strtol(persistmax, &endptr, 0); + if (*endptr != '\0' || persistmaxtime < 0) { MXS_ERROR("Invalid value for 'persistmaxtime' for server %s: %s", server->unique_name, persistmax); + error_count++; + } + else + { + server->persistmaxtime = persistmaxtime; } } @@ -2828,12 +2858,6 @@ int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj) s = strtok_r(NULL, ",", &lasts); } } - else if (servers == NULL && !is_internal_service(router)) - { - MXS_ERROR("The service '%s' is missing a definition of the servers " - "that provide the service.", obj->object); - error_count++; - } if (roptions) { @@ -2881,31 +2905,39 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* else { obj->element = NULL; - MXS_ERROR("Monitor '%s' is missing the require 'module' parameter.", obj->object); + MXS_ERROR("Monitor '%s' is missing the required 'module' parameter.", obj->object); error_count++; } char *servers = config_get_value(obj->parameters, "servers"); - if (servers == NULL) - { - MXS_ERROR("Monitor '%s' is missing the 'servers' parameter that " - "lists the servers that it monitors.", obj->object); - error_count++; - } if (error_count == 0) { monitorAddParameters(obj->element, obj->parameters); - char *interval = config_get_value(obj->parameters, "monitor_interval"); - if (interval) + char *interval_str = config_get_value(obj->parameters, "monitor_interval"); + if (interval_str) { - monitorSetInterval(obj->element, atoi(interval)); + char *endptr; + long interval = strtol(interval_str, &endptr, 0); + /* The interval must be >0 because it is used as a divisor. + Perhaps a greater minimum value should be added? */ + if (*endptr == '\0' && interval > 0) + { + monitorSetInterval(obj->element, (unsigned long)interval); + } + else + { + MXS_NOTICE("Invalid 'monitor_interval' parameter for monitor '%s', " + "using default value of %d milliseconds.", + obj->object, MONITOR_INTERVAL); + } } else { MXS_NOTICE("Monitor '%s' is missing the 'monitor_interval' parameter, " - "using default value of 10000 milliseconds.", obj->object); + "using default value of %d milliseconds.", + obj->object, MONITOR_INTERVAL); } char *connect_timeout = config_get_value(obj->parameters, "backend_connect_timeout"); @@ -2938,36 +2970,39 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* } } - /* get the servers to monitor */ - char *s, *lasts; - s = strtok_r(servers, ",", &lasts); - while (s) + if (servers) { - CONFIG_CONTEXT *obj1 = context; - int found = 0; - while (obj1) + /* get the servers to monitor */ + char *s, *lasts; + s = strtok_r(servers, ",", &lasts); + while (s) { - if (strcmp(trim(s), obj1->object) == 0 && obj->element && obj1->element) + CONFIG_CONTEXT *obj1 = context; + int found = 0; + while (obj1) { - found = 1; - if (hashtable_add(monitorhash, obj1->object, "") == 0) + if (strcmp(trim(s), obj1->object) == 0 && obj->element && obj1->element) { - MXS_WARNING("Multiple monitors are monitoring server [%s]. " - "This will cause undefined behavior.", - obj1->object); + found = 1; + if (hashtable_add(monitorhash, obj1->object, "") == 0) + { + MXS_WARNING("Multiple monitors are monitoring server [%s]. " + "This will cause undefined behavior.", + obj1->object); + } + monitorAddServer(obj->element, obj1->element); } - monitorAddServer(obj->element, obj1->element); + obj1 = obj1->next; + } + if (!found) + { + MXS_ERROR("Unable to find server '%s' that is " + "configured in the monitor '%s'.", s, obj->object); + error_count++; } - obj1 = obj1->next; - } - if (!found) - { - MXS_ERROR("Unable to find server '%s' that is " - "configured in the monitor '%s'.", s, obj->object); - error_count++; - } - s = strtok_r(NULL, ",", &lasts); + s = strtok_r(NULL, ",", &lasts); + } } char *user = config_get_value(obj->parameters, "user"); diff --git a/server/core/dcb.c b/server/core/dcb.c index 24e252e91..c07bd979b 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -93,15 +93,6 @@ #include #include -#if defined(FAKE_CODE) -unsigned char dcb_fake_write_errno[10240]; -__int32_t dcb_fake_write_ev[10240]; -bool fail_next_backend_fd; -bool fail_next_client_fd; -int fail_next_accept; -int fail_accept_errno; -#endif /* FAKE_CODE */ - /* The list of all DCBs */ static LIST_CONFIG DCBlist = {LIST_TYPE_RECYCLABLE, sizeof(DCB), SPINLOCK_INIT}; @@ -130,9 +121,6 @@ static int dcb_create_SSL(DCB* dcb, SSL_LISTENER *ssl); static int dcb_read_SSL(DCB *dcb, GWBUF **head); static GWBUF *dcb_basic_read(DCB *dcb, int bytesavailable, int maxbytes, int nreadtotal, int *nsingleread); static GWBUF *dcb_basic_read_SSL(DCB *dcb, int *nsingleread); -#if defined(FAKE_CODE) -static inline void dcb_write_fake_code(DCB *dcb); -#endif static void dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno); static inline void dcb_write_tidy_up(DCB *dcb, bool below_water); static int gw_write(DCB *dcb, GWBUF *writeq, bool *stop_writing); @@ -684,9 +672,6 @@ dcb_process_victim_queue(DCB *listofdcb) } else { -#if defined(FAKE_CODE) - conn_open[dcb->fd] = false; -#endif /* FAKE_CODE */ dcb->fd = DCBFD_CLOSED; MXS_DEBUG("%lu [dcb_process_victim_queue] Closed socket " @@ -772,6 +757,7 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol) MXS_DEBUG("%lu [dcb_connect] Reusing a persistent connection, dcb %p\n", pthread_self(), dcb); dcb->persistentstart = 0; + dcb->was_persistent = true; return dcb; } else @@ -867,6 +853,8 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol) dcb->dcb_server_status = server->status; dcb->dcb_port = server->port; + dcb->was_persistent = false; + /** * backend_dcb is connected to backend server, and once backend_dcb * is added to poll set, authentication takes place as part of @@ -1384,34 +1372,6 @@ dcb_write(DCB *dcb, GWBUF *queue) return 1; } -#if defined(FAKE_CODE) -/** - * Fake code for dcb_write - * (Should have fuller description) - * - * @param dcb The DCB of the client - */ -static inline void -dcb_write_fake_code(DCB *dcb) -{ - if (dcb->session != NULL) - { - if (dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER && fail_next_client_fd) - { - dcb_fake_write_errno[dcb->fd] = 32; - dcb_fake_write_ev[dcb->fd] = 29; - fail_next_client_fd = false; - } - else if (dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER && fail_next_backend_fd) - { - dcb_fake_write_errno[dcb->fd] = 32; - dcb_fake_write_ev[dcb->fd] = 29; - fail_next_backend_fd = false; - } - } -} -#endif /* FAKE_CODE */ - /** * Check the parameters for dcb_write * @@ -1824,6 +1784,7 @@ dcb_maybe_add_persistent(DCB *dcb) MXS_DEBUG("%lu [dcb_maybe_add_persistent] Adding DCB to persistent pool, user %s.\n", pthread_self(), dcb->user); + dcb->was_persistent = false; dcb->dcb_is_zombie = false; dcb->persistentstart = time(NULL); if (dcb->session) @@ -2421,28 +2382,10 @@ gw_write(DCB *dcb, GWBUF *writeq, bool *stop_writing) errno = 0; -#if defined(FAKE_CODE) - if (fd > 0 && dcb_fake_write_errno[fd] != 0) - { - ss_dassert(dcb_fake_write_ev[fd] != 0); - written = write(fd, buf, nbytes / 2); /*< leave peer to read missing bytes */ - - if (written > 0) - { - written = -1; - errno = dcb_fake_write_errno[fd]; - } - } - else if (fd > 0) - { - written = write(fd, buf, nbytes); - } -#else if (fd > 0) { written = write(fd, buf, nbytes); } -#endif /* FAKE_CODE */ saved_errno = errno; errno = 0; @@ -3089,14 +3032,9 @@ dcb_accept(DCB *listener, GWPROTOCOL *protocol_funcs) if ((c_sock = dcb_accept_one_connection(listener, (struct sockaddr *)&client_conn)) >= 0) { listener->stats.n_accepts++; -#if defined(SS_DEBUG) MXS_DEBUG("%lu [gw_MySQLAccept] Accepted fd %d.", pthread_self(), c_sock); -#endif /* SS_DEBUG */ -#if defined(FAKE_CODE) - conn_open[c_sock] = true; -#endif /* FAKE_CODE */ /* set nonblocking */ sendbuf = MXS_CLIENT_SO_SNDBUF; @@ -3229,27 +3167,12 @@ dcb_accept_one_connection(DCB *listener, struct sockaddr *client_conn) socklen_t client_len = sizeof(struct sockaddr_storage); int eno = 0; -#if defined(FAKE_CODE) - if (fail_next_accept > 0) - { - c_sock = -1; - eno = fail_accept_errno; - fail_next_accept -= 1; - } - else - { - fail_accept_errno = 0; -#endif /* FAKE_CODE */ - - /* new connection from client */ - c_sock = accept(listener->fd, - client_conn, - &client_len); - eno = errno; - errno = 0; -#if defined(FAKE_CODE) - } -#endif /* FAKE_CODE */ + /* new connection from client */ + c_sock = accept(listener->fd, + client_conn, + &client_len); + eno = errno; + errno = 0; if (c_sock == -1) { @@ -3367,9 +3290,6 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) "attempting to register on an epoll instance."); return -1; } -#if defined(FAKE_CODE) - conn_open[listener_socket] = true; -#endif /* FAKE_CODE */ return 0; } diff --git a/server/core/gateway.c b/server/core/gateway.c index 2099bdb7b..10bacd28d 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -128,6 +128,7 @@ static struct option long_options[] = {"configdir", required_argument, 0, 'C'}, {"datadir", required_argument, 0, 'D'}, {"execdir", required_argument, 0, 'E'}, + {"persistdir", required_argument, 0, 'F'}, {"language", required_argument, 0, 'N'}, {"piddir", required_argument, 0, 'P'}, {"basedir", required_argument, 0, 'R'}, @@ -181,7 +182,6 @@ static int set_user(const char* user); bool pid_file_exists(); void write_child_exit_code(int fd, int code); static bool change_cwd(); -void shutdown_server(); static void log_exit_status(); static bool daemonize(); static bool sniff_configuration(const char* filepath); @@ -288,20 +288,45 @@ static void sigusr1_handler (int i) } static const char shutdown_msg[] = "\n\nShutting down MaxScale\n\n"; +static const char patience_msg[] = + "\n" + "Patience is a virtue...\n" + "Shutdown in progress, but one more Ctrl-C or SIGTERM and MaxScale goes down,\n" + "no questions asked.\n"; static void sigterm_handler(int i) { last_signal = i; - shutdown_server(); - write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1); + int n_shutdowns = maxscale_shutdown(); + + if (n_shutdowns == 1) + { + write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1); + } + else + { + exit(EXIT_FAILURE); + } } static void sigint_handler(int i) { last_signal = i; - shutdown_server(); - write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1); + int n_shutdowns = maxscale_shutdown(); + + if (n_shutdowns == 1) + { + write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1); + } + else if (n_shutdowns == 2) + { + write(STDERR_FILENO, patience_msg, sizeof(patience_msg) - 1); + } + else + { + exit(EXIT_FAILURE); + } } static void @@ -895,8 +920,9 @@ static void usage(void) " -B, --libdir=PATH path to module directory\n" " -C, --configdir=PATH path to configuration file directory\n" " -D, --datadir=PATH path to data directory,\n" - " stored embedded mysql tables\n" + " stores internal MaxScale data\n" " -E, --execdir=PATH path to the maxscale and other executable files\n" + " -F, --persistdir=PATH path to persisted configuration directory\n" " -N, --language=PATH path to errmsg.sys file\n" " -P, --piddir=PATH path to PID file directory\n" " -R, --basedir=PATH base path for all other paths\n" @@ -920,6 +946,7 @@ static void usage(void) " execdir : %s\n" " language : %s\n" " piddir : %s\n" + " persistdir : %s\n" "\n" "If '--basedir' is provided then all other paths, including the default\n" "configuration file path, are defined relative to that. As an example,\n" @@ -930,7 +957,8 @@ static void usage(void) progname, get_configdir(), default_cnf_fname, get_configdir(), get_logdir(), get_cachedir(), get_libdir(), - get_datadir(), get_execdir(), get_langdir(), get_piddir()); + get_datadir(), get_execdir(), get_langdir(), get_piddir(), + get_config_persistdir()); } @@ -1205,6 +1233,12 @@ bool set_dirs(const char *basedir) set_piddir(path); } + if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_DATA_SUBPATH "/" + MXS_DEFAULT_CONFIG_PERSIST_SUBPATH, true, true))) + { + set_config_persistdir(path); + } + return rv; } @@ -1276,15 +1310,6 @@ int main(int argc, char **argv) progname = *argv; snprintf(datadir, PATH_MAX, "%s", default_datadir); datadir[PATH_MAX] = '\0'; -#if defined(FAKE_CODE) - memset(conn_open, 0, sizeof(bool) * 10240); - memset(dcb_fake_write_errno, 0, sizeof(unsigned char) * 10240); - memset(dcb_fake_write_ev, 0, sizeof(__int32_t) * 10240); - fail_next_backend_fd = false; - fail_next_client_fd = false; - fail_next_accept = 0; - fail_accept_errno = 0; -#endif /* FAKE_CODE */ file_write_header(stderr); /*< * Register functions which are called at exit except libmysqld-related, @@ -1303,7 +1328,7 @@ int main(int argc, char **argv) } } - while ((opt = getopt_long(argc, argv, "dcf:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:", + while ((opt = getopt_long(argc, argv, "dcf:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:", long_options, &option_index)) != -1) { bool succp = true; @@ -1453,6 +1478,16 @@ int main(int argc, char **argv) succp = false; } break; + case 'F': + if (handle_path_arg(&tmp_path, optarg, NULL, true, true)) + { + set_config_persistdir(tmp_path); + } + else + { + succp = false; + } + break; case 'R': if (handle_path_arg(&tmp_path, optarg, NULL, true, false)) { @@ -1936,7 +1971,13 @@ int main(int argc, char **argv) /* * Start the housekeeper thread */ - hkinit(); + if (!hkinit()) + { + char* logerr = "Failed to start housekeeper thread."; + print_log_n_stderr(true, true, logerr, logerr, 0); + rc = MAXSCALE_INTERNALERROR; + goto return_main; + } /*< * Start the polling threads, note this is one less than is @@ -1974,6 +2015,11 @@ int main(int argc, char **argv) */ poll_waitevents((void *)0); + /*< + * Wait for the housekeeper to finish. + */ + hkfinish(); + /*< * Wait server threads' completion. */ @@ -2030,14 +2076,22 @@ return_main: /*< * Shutdown MaxScale server */ -void -shutdown_server() +int maxscale_shutdown() { - service_shutdown(); - poll_shutdown(); - hkshutdown(); - memlog_flush_all(); - log_flush_shutdown(); + static int n_shutdowns = 0; + + int n = atomic_add(&n_shutdowns, 1); + + if (n == 0) + { + service_shutdown(); + poll_shutdown(); + hkshutdown(); + memlog_flush_all(); + log_flush_shutdown(); + } + + return n + 1; } static void log_flush_shutdown(void) @@ -2481,6 +2535,20 @@ static int cnf_preparser(void* data, const char* section, const char* name, cons } } } + else if (strcmp(name, "persistdir") == 0) + { + if (strcmp(get_config_persistdir(), default_config_persistdir) == 0) + { + if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false)) + { + set_config_persistdir(tmp); + } + else + { + return 0; + } + } + } else if (strcmp(name, "syslog") == 0) { if (!syslog_configured) diff --git a/server/core/gwdirs.c b/server/core/gwdirs.c index eb8ca6394..22de96bc6 100644 --- a/server/core/gwdirs.c +++ b/server/core/gwdirs.c @@ -26,6 +26,17 @@ void set_configdir(char* str) configdir = str; } +/** + * Set the configuration parts file directory + * @param str Path to directory + */ +void set_config_persistdir(char* str) +{ + MXS_FREE(config_persistdir); + clean_up_pathname(str); + config_persistdir = str; +} + /** * Set the log file directory * @param str Path to directory @@ -160,6 +171,15 @@ char* get_configdir() return configdir ? configdir : (char*) default_configdir; } +/** + * Get the configuration file directory + * @return The path to the configuration file directory + */ +char* get_config_persistdir() +{ + return config_persistdir ? config_persistdir : (char*) default_config_persistdir; +} + /** * Get the PID file directory which contains maxscale.pid * @return Path to the PID file directory diff --git a/server/core/housekeeper.c b/server/core/housekeeper.c index 48096b2f3..0efc5cc14 100644 --- a/server/core/housekeeper.c +++ b/server/core/housekeeper.c @@ -10,13 +10,14 @@ * 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 +#include /** * @file housekeeper.c Provide a mechanism to run periodic tasks @@ -49,22 +50,28 @@ static HKTASK *tasks = NULL; */ static SPINLOCK tasklock = SPINLOCK_INIT; -static int do_shutdown = 0; +static bool do_shutdown = 0; + long hkheartbeat = 0; /*< One heartbeat is 100 milliseconds */ static THREAD hk_thr_handle; static void hkthread(void *); -/** - * Initialise the housekeeper thread - */ -void +bool hkinit() { - if (thread_start(&hk_thr_handle, hkthread, NULL) == NULL) + bool inited = false; + + if (thread_start(&hk_thr_handle, hkthread, NULL) != NULL) { - MXS_ERROR("Failed to start housekeeper thread."); + inited = true; } + else + { + MXS_ALERT("Failed to start housekeeper thread."); + } + + return inited; } /** @@ -255,21 +262,17 @@ hkthread(void *data) void *taskdata; int i; - for (;;) + while (!do_shutdown) { for (i = 0; i < 10; i++) { - if (do_shutdown) - { - return; - } thread_millisleep(100); hkheartbeat++; } now = time(0); spinlock_acquire(&tasklock); ptr = tasks; - while (ptr) + while (!do_shutdown && ptr) { if (ptr->nextdue <= now) { @@ -297,16 +300,25 @@ hkthread(void *data) } spinlock_release(&tasklock); } + + MXS_NOTICE("Housekeeper shutting down."); } -/** - * Called to shutdown the housekeeper - * - */ void hkshutdown() { - do_shutdown = 1; + do_shutdown = true; + atomic_synchronize(); +} + +void hkfinish() +{ + ss_dassert(do_shutdown); + + MXS_NOTICE("Waiting for housekeeper to shut down."); + thread_wait(hk_thr_handle); + do_shutdown = false; + MXS_NOTICE("Housekeeper has shut down."); } /** diff --git a/server/core/monitor.c b/server/core/monitor.c index 60bc4e238..94f930a8a 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -58,7 +58,7 @@ const monitor_def_t monitor_event_definitions[MAX_MONITOR_EVENT] = static MONITOR *allMonitors = NULL; static SPINLOCK monLock = SPINLOCK_INIT; -static void monitor_servers_free(MONITOR_SERVERS *servers); +static void monitor_server_free_all(MONITOR_SERVERS *servers); /** * Allocate a new monitor, load the associated module for the monitor @@ -93,9 +93,8 @@ monitor_alloc(char *name, char *module) mon->name = name; mon->handle = NULL; mon->databases = NULL; - mon->password = NULL; - mon->user = NULL; - mon->password = NULL; + *mon->password = '\0'; + *mon->user = '\0'; mon->read_timeout = DEFAULT_READ_TIMEOUT; mon->write_timeout = DEFAULT_WRITE_TIMEOUT; mon->connect_timeout = DEFAULT_CONNECT_TIMEOUT; @@ -142,7 +141,7 @@ monitor_free(MONITOR *mon) } spinlock_release(&monLock); free_config_parameter(mon->parameters); - monitor_servers_free(mon->databases); + monitor_server_free_all(mon->databases); MXS_FREE(mon->name); MXS_FREE(mon); } @@ -258,6 +257,13 @@ monitorAddServer(MONITOR *mon, SERVER *server) /* pending status is updated by get_replication_tree */ db->pending_status = 0; + monitor_state_t old_state = mon->state; + + if (old_state == MONITOR_STATE_RUNNING) + { + monitorStop(mon); + } + spinlock_acquire(&mon->lock); if (mon->databases == NULL) @@ -274,23 +280,87 @@ monitorAddServer(MONITOR *mon, SERVER *server) ptr->next = db; } spinlock_release(&mon->lock); + + if (old_state == MONITOR_STATE_RUNNING) + { + monitorStart(mon, mon->parameters); + } +} + +static void monitor_server_free(MONITOR_SERVERS *tofree) +{ + if (tofree) + { + if (tofree->con) + { + mysql_close(tofree->con); + } + MXS_FREE(tofree); + } } /** * Free monitor server list * @param servers Servers to free */ -static void monitor_servers_free(MONITOR_SERVERS *servers) +static void monitor_server_free_all(MONITOR_SERVERS *servers) { while (servers) { MONITOR_SERVERS *tofree = servers; servers = servers->next; - if (tofree->con) + monitor_server_free(tofree); + } +} + +/** + * Remove a server from a monitor. + * + * @param mon The Monitor instance + * @param server The Server to remove + */ +void monitorRemoveServer(MONITOR *mon, SERVER *server) +{ + monitor_state_t old_state = mon->state; + + if (old_state == MONITOR_STATE_RUNNING) + { + monitorStop(mon); + } + + spinlock_acquire(&mon->lock); + + MONITOR_SERVERS *ptr = mon->databases; + + if (ptr->server == server) + { + mon->databases = mon->databases->next; + } + else + { + MONITOR_SERVERS *prev = ptr; + + while (ptr) { - mysql_close(tofree->con); + if (ptr->server == server) + { + prev->next = ptr->next; + break; + } + prev = ptr; + ptr = ptr->next; } - MXS_FREE(tofree); + } + spinlock_release(&mon->lock); + + if (ptr) + { + monitor_server_free(ptr); + } + + if (old_state == MONITOR_STATE_RUNNING) + { + monitorStart(mon, mon->parameters); } } @@ -305,8 +375,8 @@ static void monitor_servers_free(MONITOR_SERVERS *servers) void monitorAddUser(MONITOR *mon, char *user, char *passwd) { - mon->user = MXS_STRDUP_A(user); - mon->password = MXS_STRDUP_A(passwd); + snprintf(mon->user, sizeof(mon->user), "%s", user); + snprintf(mon->password, sizeof(mon->password), "%s", passwd); } /** @@ -536,13 +606,8 @@ monitorGetList() */ bool check_monitor_permissions(MONITOR* monitor, const char* query) { - if (monitor->databases == NULL) - { - MXS_ERROR("[%s] Monitor is missing the servers parameter.", monitor->name); - return false; - } - - if (config_get_global_options()->skip_permission_checks) + if (monitor->databases == NULL || // No servers to check + config_get_global_options()->skip_permission_checks) { return true; } @@ -992,8 +1057,15 @@ mon_connect_to_db(MONITOR* mon, MONITOR_SERVERS *database) if ((database->con = mysql_init(NULL))) { - char *uname = database->server->monuser ? database->server->monuser : mon->user; - char *passwd = database->server->monpw ? database->server->monpw : mon->password; + char *uname = mon->user; + char *passwd = mon->password; + + if (database->server->monuser[0] && database->server->monpw[0]) + { + uname = database->server->monuser; + passwd = database->server->monpw; + } + char *dpwd = decryptPassword(passwd); mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *) &mon->connect_timeout); @@ -1036,12 +1108,9 @@ void mon_log_connect_error(MONITOR_SERVERS* database, connect_result_t rval) { MXS_ERROR(rval == MONITOR_CONN_TIMEOUT ? - "Monitor timed out when connecting to " - "server %s:%d : \"%s\"" : - "Monitor was unable to connect to " - "server %s:%d : \"%s\"", - database->server->name, - database->server->port, + "Monitor timed out when connecting to server %s:%d : \"%s\"" : + "Monitor was unable to connect to server %s:%d : \"%s\"", + database->server->name, database->server->port, mysql_error(database->con)); } @@ -1057,3 +1126,29 @@ void mon_log_state_change(MONITOR_SERVERS *ptr) MXS_FREE(prev); MXS_FREE(next); } + +bool monitor_server_in_use(const SERVER *server) +{ + bool rval = false; + + spinlock_acquire(&monLock); + + for (MONITOR *mon = allMonitors; mon && !rval; mon = mon->next) + { + spinlock_acquire(&mon->lock); + + for (MONITOR_SERVERS *db = mon->databases; db && !rval; db = db->next) + { + if (db->server == server) + { + rval = true; + } + } + + spinlock_release(&mon->lock); + } + + spinlock_release(&monLock); + + return rval; +} diff --git a/server/core/poll.c b/server/core/poll.c index ea7fa657c..90295a9bb 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -895,18 +895,6 @@ process_pollq(int thread_id) thread_data[thread_id].event = ev; } -#if defined(FAKE_CODE) - if (dcb_fake_write_ev[dcb->fd] != 0) - { - MXS_DEBUG("%lu [poll_waitevents] " - "Added fake events %d to ev %d.", - pthread_self(), - dcb_fake_write_ev[dcb->fd], - ev); - ev |= dcb_fake_write_ev[dcb->fd]; - dcb_fake_write_ev[dcb->fd] = 0; - } -#endif /* FAKE_CODE */ ss_debug(spinlock_acquire(&dcb->dcb_initlock)); ss_dassert(dcb->state != DCB_STATE_ALLOC); /* It isn't obvious that this is impossible */ @@ -1007,20 +995,6 @@ process_pollq(int thread_id) if (ev & EPOLLERR) { int eno = gw_getsockerrno(dcb->fd); -#if defined(FAKE_CODE) - if (eno == 0) - { - eno = dcb_fake_write_errno[dcb->fd]; - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_DEBUG("%lu [poll_waitevents] " - "Added fake errno %d. " - "%s", - pthread_self(), - eno, - strerror_r(eno, errbuf, sizeof(errbuf))); - } - dcb_fake_write_errno[dcb->fd] = 0; -#endif /* FAKE_CODE */ if (eno != 0) { char errbuf[MXS_STRERROR_BUFLEN]; diff --git a/server/core/query_classifier.c b/server/core/query_classifier.c index 5800f38e1..0a12f374a 100644 --- a/server/core/query_classifier.c +++ b/server/core/query_classifier.c @@ -27,6 +27,12 @@ #define QC_TRACE() #endif +struct type_name_info +{ + const char* name; + size_t name_len; +}; + static const char default_qc_name[] = "qc_sqlite"; static QUERY_CLASSIFIER* classifier; @@ -189,12 +195,12 @@ bool qc_query_has_clause(GWBUF* query) return classifier->qc_query_has_clause(query); } -char* qc_get_affected_fields(GWBUF* query) +void qc_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_infos) { QC_TRACE(); ss_dassert(classifier); - return classifier->qc_get_affected_fields(query); + classifier->qc_get_field_info(query, infos, n_infos); } char** qc_get_database_names(GWBUF* query, int* sizep) @@ -213,6 +219,141 @@ char* qc_get_prepare_name(GWBUF* query) return classifier->qc_get_prepare_name(query); } +struct type_name_info field_usage_to_type_name_info(qc_field_usage_t usage) +{ + struct type_name_info info; + + switch (usage) + { + case QC_USED_IN_SELECT: + { + static const char name[] = "QC_USED_IN_SELECT"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + + case QC_USED_IN_SUBSELECT: + { + static const char name[] = "QC_USED_IN_SUBSELECT"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + + case QC_USED_IN_WHERE: + { + static const char name[] = "QC_USED_IN_WHERE"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + + case QC_USED_IN_SET: + { + static const char name[] = "QC_USED_IN_SET"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + + case QC_USED_IN_GROUP_BY: + { + static const char name[] = "QC_USED_IN_GROUP_BY"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + + default: + { + static const char name[] = "UNKNOWN_FIELD_USAGE"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + } + + return info; +} + + + +const char* qc_field_usage_to_string(qc_field_usage_t usage) +{ + return field_usage_to_type_name_info(usage).name; +} + +static const qc_field_usage_t FIELD_USAGE_VALUES[] = +{ + QC_USED_IN_SELECT, + QC_USED_IN_SUBSELECT, + QC_USED_IN_WHERE, + QC_USED_IN_SET, + QC_USED_IN_GROUP_BY, +}; + +static const int N_FIELD_USAGE_VALUES = + sizeof(FIELD_USAGE_VALUES) / sizeof(FIELD_USAGE_VALUES[0]); +static const int FIELD_USAGE_MAX_LEN = 20; // strlen("QC_USED_IN_SUBSELECT"); + +char* qc_field_usage_mask_to_string(uint32_t mask) +{ + size_t len = 0; + + // First calculate how much space will be needed. + for (int i = 0; i < N_FIELD_USAGE_VALUES; ++i) + { + if (mask & FIELD_USAGE_VALUES[i]) + { + if (len != 0) + { + ++len; // strlen("|"); + } + + len += FIELD_USAGE_MAX_LEN; + } + } + + ++len; + + // Then make one allocation and build the string. + char* s = (char*) MXS_MALLOC(len); + + if (s) + { + if (len > 1) + { + char* p = s; + + for (int i = 0; i < N_FIELD_USAGE_VALUES; ++i) + { + qc_field_usage_t value = FIELD_USAGE_VALUES[i]; + + if (mask & value) + { + if (p != s) + { + strcpy(p, "|"); + ++p; + } + + struct type_name_info info = field_usage_to_type_name_info(value); + + strcpy(p, info.name); + p += info.name_len; + } + } + } + else + { + *s = 0; + } + } + + return s; +} + const char* qc_op_to_string(qc_query_op_t op) { switch (op) @@ -261,12 +402,6 @@ const char* qc_op_to_string(qc_query_op_t op) } } -struct type_name_info -{ - const char* name; - size_t name_len; -}; - struct type_name_info type_to_type_name_info(qc_query_type_t type) { struct type_name_info info; diff --git a/server/core/secrets.c b/server/core/secrets.c index b197ff1e0..60cc244b5 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -175,8 +175,8 @@ secrets_readKeys(const char* path) if (secret_stats.st_mode != (S_IRUSR | S_IFREG)) { close(fd); - MXS_ERROR("Ignoring secrets file " - "%s, invalid permissions.", + MXS_ERROR("Ignoring secrets file %s, invalid permissions." + "The only permission on the file should be owner:read.", secret_file); return NULL; } diff --git a/server/core/server.c b/server/core/server.c index 632aa4a94..1faee1076 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -35,6 +35,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -44,6 +49,7 @@ #include #include #include +#include static SPINLOCK server_spin = SPINLOCK_INIT; static SERVER *allServers = NULL; @@ -51,63 +57,62 @@ static SERVER *allServers = NULL; static void spin_reporter(void *, char *, int); static void server_parameter_free(SERVER_PARAM *tofree); -/** - * Allocate a new server withn the gateway - * - * - * @param servname The server name - * @param protocol The protocol to use to connect to the server - * @param port The port to connect to - * - * @return The newly created server or NULL if an error occured - */ -SERVER * -server_alloc(char *servname, char *protocol, unsigned short port, char *authenticator, - char *auth_options) + +SERVER* server_alloc(const char *name, const char *address, unsigned short port, + const char *protocol, const char *authenticator, const char *auth_options) { - if (authenticator) + if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) { - authenticator = MXS_STRDUP(authenticator); - } - else if ((authenticator = (char*)get_default_authenticator(protocol)) == NULL || - (authenticator = MXS_STRDUP(authenticator)) == NULL) - { - MXS_ERROR("No authenticator defined for server at %s:%u and no default " - "authenticator for protocol '%s'.", servname, port, protocol); + MXS_ERROR("No authenticator defined for server '%s' and no default " + "authenticator for protocol '%s'.", name, protocol); + return NULL; } void *auth_instance = NULL; if (!authenticator_init(&auth_instance, authenticator, auth_options)) { - MXS_ERROR("Failed to initialize authenticator module '%s' for server" - " at %s:%u.", authenticator, servname, port); - MXS_FREE(authenticator); + MXS_ERROR("Failed to initialize authenticator module '%s' for server '%s' ", + authenticator, name); return NULL; } - servname = MXS_STRNDUP(servname, MAX_SERVER_NAME_LEN); - protocol = MXS_STRDUP(protocol); + char *my_auth_options = NULL; + + if (auth_options && (my_auth_options = MXS_STRDUP(auth_options)) == NULL) + { + return NULL; + } SERVER *server = (SERVER *)MXS_CALLOC(1, sizeof(SERVER)); + char *my_name = MXS_STRDUP(name); + char *my_protocol = MXS_STRDUP(protocol); + char *my_authenticator = MXS_STRDUP(authenticator); - if (!servname || !protocol || !server || !authenticator) + if (!server || !my_name || !my_protocol || !my_authenticator) { - MXS_FREE(servname); - MXS_FREE(protocol); MXS_FREE(server); - MXS_FREE(authenticator); + MXS_FREE(my_name); + MXS_FREE(my_protocol); + MXS_FREE(my_authenticator); return NULL; } + if (snprintf(server->name, sizeof(server->name), "%s", address) > sizeof(server->name)) + { + MXS_WARNING("Truncated server address '%s' to the maximum size of %lu characters.", + address, sizeof(server->name)); + } + #if defined(SS_DEBUG) server->server_chk_top = CHK_NUM_SERVER; server->server_chk_tail = CHK_NUM_SERVER; #endif - server->name = servname; - server->protocol = protocol; - server->authenticator = authenticator; + server->unique_name = my_name; + server->protocol = my_protocol; + server->authenticator = my_authenticator; server->auth_instance = auth_instance; + server->auth_options = my_auth_options; server->port = port; server->status = SERVER_RUNNING; server->node_id = -1; @@ -121,6 +126,9 @@ server_alloc(char *servname, char *protocol, unsigned short port, char *authenti server->persistmax = 0; server->persistmaxtime = 0; server->persistpoolmax = 0; + server->monuser[0] = '\0'; + server->monpw[0] = '\0'; + server->is_active = true; spinlock_init(&server->persistlock); spinlock_acquire(&server_spin); @@ -164,7 +172,6 @@ server_free(SERVER *tofreeserver) spinlock_release(&server_spin); /* Clean up session and free the memory */ - MXS_FREE(tofreeserver->name); MXS_FREE(tofreeserver->protocol); MXS_FREE(tofreeserver->unique_name); MXS_FREE(tofreeserver->server_string); @@ -243,42 +250,38 @@ server_get_persistent(SERVER *server, char *user, const char *protocol) return NULL; } -/** - * Set a unique name for the server - * - * @param server The server to set the name on - * @param name The unique name for the server - */ -void -server_set_unique_name(SERVER *server, char *name) +static inline SERVER* next_active_server(SERVER *server) { - server->unique_name = MXS_STRDUP_A(name); + while (server && !server->is_active) + { + server = server->next; + } + + return server; } /** - * Find an existing server using the unique section name in - * configuration file + * @brief Find a server with the specified name * - * @param servname The Server name or address - * @param port The server port - * @return The server or NULL if not found + * @param name Name of the server + * @return The server or NULL if not found */ -SERVER * -server_find_by_unique_name(char *name) +SERVER * server_find_by_unique_name(const char *name) { - SERVER *server; - spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); + while (server) { if (server->unique_name && strcmp(server->unique_name, name) == 0) { break; } - server = server->next; + server = next_active_server(server->next); } + spinlock_release(&server_spin); + return server; } @@ -292,19 +295,20 @@ server_find_by_unique_name(char *name) SERVER * server_find(char *servname, unsigned short port) { - SERVER *server; - spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); + while (server) { if (strcmp(server->name, servname) == 0 && server->port == port) { break; } - server = server->next; + server = next_active_server(server->next); } + spinlock_release(&server_spin); + return server; } @@ -335,15 +339,15 @@ printServer(SERVER *server) void printAllServers() { - SERVER *server; - spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); + while (server) { printServer(server); - server = server->next; + server = next_active_server(server->next); } + spinlock_release(&server_spin); } @@ -356,15 +360,15 @@ printAllServers() void dprintAllServers(DCB *dcb) { - SERVER *server; - spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); + while (server) { dprintServer(dcb, server); - server = server->next; + server = next_active_server(server->next); } + spinlock_release(&server_spin); } @@ -377,19 +381,20 @@ dprintAllServers(DCB *dcb) void dprintAllServersJson(DCB *dcb) { - SERVER *server; char *stat; int len = 0; int el = 1; spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); while (server) { - server = server->next; + server = next_active_server(server->next); len++; } - server = allServers; + + server = next_active_server(allServers); + dcb_printf(dcb, "[\n"); while (server) { @@ -456,9 +461,10 @@ dprintAllServersJson(DCB *dcb) { dcb_printf(dcb, " }\n"); } - server = server->next; + server = next_active_server(server->next); el++; } + dcb_printf(dcb, "]\n"); spinlock_release(&server_spin); } @@ -473,6 +479,11 @@ dprintAllServersJson(DCB *dcb) void dprintServer(DCB *dcb, SERVER *server) { + if (!SERVER_IS_ACTIVE(server)) + { + return; + } + dcb_printf(dcb, "Server %p (%s)\n", server, server->unique_name); dcb_printf(dcb, "\tServer: %s\n", server->name); char* stat = server_status(server); @@ -602,30 +613,32 @@ dprintPersistentDCBs(DCB *pdcb, SERVER *server) void dListServers(DCB *dcb) { - SERVER *server; - char *stat; - spinlock_acquire(&server_spin); - server = allServers; + SERVER *server = next_active_server(allServers); + bool have_servers = false; + if (server) { + have_servers = true; dcb_printf(dcb, "Servers.\n"); dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); dcb_printf(dcb, "%-18s | %-15s | Port | Connections | %-20s\n", "Server", "Address", "Status"); dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); } + while (server) { - stat = server_status(server); + char *stat = server_status(server); dcb_printf(dcb, "%-18s | %-15s | %5d | %11d | %s\n", server->unique_name, server->name, server->port, server->stats.n_current, stat); MXS_FREE(stat); - server = server->next; + server = next_active_server(server->next); } - if (allServers) + + if (have_servers) { dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); } @@ -775,8 +788,16 @@ server_transfer_status(SERVER *dest_server, SERVER *source_server) void serverAddMonUser(SERVER *server, char *user, char *passwd) { - server->monuser = MXS_STRDUP_A(user); - server->monpw = MXS_STRDUP_A(passwd); + if (snprintf(server->monuser, sizeof(server->monuser), "%s", user) > sizeof(server->monuser)) + { + MXS_WARNING("Truncated monitor user for server '%s', maximum username " + "length is %lu characters.", server->unique_name, sizeof(server->monuser)); + } + if (snprintf(server->monpw, sizeof(server->monpw), "%s", passwd) > sizeof(server->monpw)) + { + MXS_WARNING("Truncated monitor password for server '%s', maximum password " + "length is %lu characters.", server->unique_name, sizeof(server->monpw)); + } } /** @@ -793,28 +814,12 @@ serverAddMonUser(SERVER *server, char *user, char *passwd) * @param passwd The password to use for the monitor user */ void -server_update(SERVER *server, char *protocol, char *user, char *passwd) +server_update_credentials(SERVER *server, char *user, char *passwd) { - if (!strcmp(server->protocol, protocol)) - { - MXS_NOTICE("Update server protocol for server %s to protocol %s.", - server->name, - protocol); - MXS_FREE(server->protocol); - server->protocol = MXS_STRDUP_A(protocol); - } - if (user != NULL && passwd != NULL) { - if (strcmp(server->monuser, user) == 0 || - strcmp(server->monpw, passwd) == 0) - { - MXS_NOTICE("Update server monitor credentials for server %s", - server->name); - MXS_FREE(server->monuser); - MXS_FREE(server->monpw); - serverAddMonUser(server, user, passwd); - } + serverAddMonUser(server, user, passwd); + MXS_NOTICE("Updated monitor credentials for server '%s'", server->name); } } @@ -922,16 +927,19 @@ serverRowCallback(RESULTSET *set, void *data) return NULL; } (*rowno)++; - row = resultset_make_row(set); - resultset_row_set(row, 0, server->unique_name); - resultset_row_set(row, 1, server->name); - sprintf(buf, "%d", server->port); - resultset_row_set(row, 2, buf); - sprintf(buf, "%d", server->stats.n_current); - resultset_row_set(row, 3, buf); - stat = server_status(server); - resultset_row_set(row, 4, stat); - MXS_FREE(stat); + if (SERVER_IS_ACTIVE(server)) + { + row = resultset_make_row(set); + resultset_row_set(row, 0, server->unique_name); + resultset_row_set(row, 1, server->name); + sprintf(buf, "%d", server->port); + resultset_row_set(row, 2, buf); + sprintf(buf, "%d", server->stats.n_current); + resultset_row_set(row, 3, buf); + stat = server_status(server); + resultset_row_set(row, 4, stat); + MXS_FREE(stat); + } spinlock_release(&server_spin); return row; } @@ -979,11 +987,7 @@ server_update_address(SERVER *server, char *address) spinlock_acquire(&server_spin); if (server && address) { - if (server->name) - { - MXS_FREE(server->name); - } - server->name = MXS_STRDUP_A(address); + strcpy(server->name, address); } spinlock_release(&server_spin); } @@ -1070,3 +1074,294 @@ bool server_set_version_string(SERVER* server, const char* string) return rval; } + +/** + * Creates a server configuration at the location pointed by @c filename + * + * @param server Server to serialize into a configuration + * @param filename Filename where configuration is written + * @return True on success, false on error + */ +static bool create_server_config(SERVER *server, const char *filename) +{ + int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (file == -1) + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to open file '%s' when serializing server '%s': %d, %s", + filename, server->unique_name, errno, strerror_r(errno, errbuf, sizeof(errbuf))); + return false; + } + + // TODO: Check for return values on all of the dprintf calls + dprintf(file, "[%s]\n", 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); + + if (server->auth_options) + { + dprintf(file, "authenticator_options=%s\n", server->auth_options); + } + + if (*server->monpw && *server->monuser) + { + dprintf(file, "monitoruser=%s\n", server->monuser); + dprintf(file, "monitorpw=%s\n", server->monpw); + } + + if (server->persistpoolmax) + { + dprintf(file, "persistpoolmax=%ld\n", server->persistpoolmax); + } + + if (server->persistmaxtime) + { + dprintf(file, "persistmaxtime=%ld\n", server->persistmaxtime); + } + + if (server->server_ssl) + { + dprintf(file, "ssl=required\n"); + + if (server->server_ssl->ssl_cert) + { + dprintf(file, "ssl_cert=%s\n", server->server_ssl->ssl_cert); + } + + if (server->server_ssl->ssl_key) + { + dprintf(file, "ssl_key=%s\n", 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); + } + if (server->server_ssl->ssl_cert_verify_depth) + { + dprintf(file, "ssl_cert_verify_depth=%d\n", server->server_ssl->ssl_cert_verify_depth); + } + + const char *version = NULL; + + switch (server->server_ssl->ssl_method_type) + { + case SERVICE_TLS10: + version = "TLSV10"; + break; + +#ifdef OPENSSL_1_0 + case SERVICE_TLS11: + version = "TLSV11"; + break; + + case SERVICE_TLS12: + version = "TLSV12"; + break; +#endif + case SERVICE_SSL_TLS_MAX: + version = "MAX"; + break; + + default: + break; + } + + if (version) + { + dprintf(file, "ssl_version=%s\n", version); + } + } + + close(file); + + return true; +} + +/** + * @brief Serialize a server to a file + * + * This converts @c server into an INI format file. This allows created servers + * to be persisted to disk. This will replace any existing files with the same + * name. + * + * @param server Server to serialize + * @return False if the serialization of the server fails, true if it was successful + */ +static bool server_serialize(SERVER *server) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), + server->unique_name); + + if (unlink(filename) == -1 && errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove temporary server configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else if (create_server_config(server, filename)) + { + char final_filename[PATH_MAX]; + strcpy(final_filename, filename); + + char *dot = strrchr(final_filename, '.'); + ss_dassert(dot); + *dot = '\0'; + + if (rename(filename, final_filename) == 0) + { + rval = true; + } + else + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to rename temporary server configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + } + + return rval; +} + +/** Try to find a server with a matching name that has been destroyed */ +static SERVER* find_destroyed_server(const char *name, const char *protocol, + const char *authenticator, const char *auth_options) +{ + spinlock_acquire(&server_spin); + SERVER *server = allServers; + while (server) + { + CHK_SERVER(server); + if (strcmp(server->unique_name, name) == 0 && + strcmp(server->protocol, protocol) == 0 && + strcmp(server->authenticator, authenticator) == 0) + { + if ((auth_options == NULL && server->auth_options == NULL) || + (auth_options && server->auth_options && + strcmp(server->auth_options, auth_options) == 0)) + { + break; + } + } + server = server->next; + } + spinlock_release(&server_spin); + + return server; +} + +bool server_create(const char *name, const char *address, const char *port, + const char *protocol, const char *authenticator, + const char *authenticator_options) +{ + bool rval = false; + + if (server_find_by_unique_name(name) == NULL) + { + // TODO: Get default values from the protocol module + if (port == NULL) + { + port = "3306"; + } + if (protocol == NULL) + { + protocol = "MySQLBackend"; + } + if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) + { + MXS_ERROR("No authenticator defined for server '%s' and no default " + "authenticator for protocol '%s'.", name, protocol); + return false; + } + + /** First check if this service has been created before */ + SERVER *server = find_destroyed_server(name, protocol, authenticator, + authenticator_options); + + if (server) + { + /** Found old server, replace network details with new ones and + * reactivate it */ + snprintf(server->name, sizeof(server->name), "%s", address); + server->port = atoi(port); + server->is_active = true; + rval = true; + } + else if ((server = server_alloc(name, address, atoi(port), protocol, authenticator, + authenticator_options))) + { + if (server_serialize(server)) + { + /** server_alloc will add the server to the global list of + * servers so we don't need to manually add it. */ + rval = true; + } + } + } + + return rval; +} + +bool server_destroy(SERVER *server) +{ + bool rval = false; + + if (service_server_in_use(server) || monitor_server_in_use(server)) + { + MXS_ERROR("Cannot destroy server '%s' as it is used by at least one " + "service or monitor", server->unique_name); + } + else + { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), + server->unique_name); + + if (unlink(filename) == -1) + { + if (errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else + { + rval = true; + MXS_WARNING("Server '%s' was not created at runtime. Remove the " + "server manually from the correct configuration file.", + server->unique_name); + } + } + else + { + rval = true; + } + + if (rval) + { + MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name, + server->name, server->port); + server->is_active = false; + } + } + + return rval; +} + +bool server_is_ssl_parameter(const char *key) +{ + // TODO: Implement this + return false; +} + +void server_update_ssl(SERVER *server, const char *key, const char *value) +{ + // TODO: Implement this +} diff --git a/server/core/service.c b/server/core/service.c index c936d91bd..7fe22d93e 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -34,6 +34,7 @@ * 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 */ @@ -66,6 +67,9 @@ #include #include +/** Base value for server weights */ +#define SERVICE_BASE_SERVER_WEIGHT 1000 + /** To be used with configuration type checks */ typedef struct typelib_st { @@ -96,6 +100,7 @@ static void service_add_qualified_param(SERVICE* svc, CONFIG_PARAMETER* param); static void service_internal_restart(void *data); static void service_queue_check(void *data); +static void service_calculate_weights(SERVICE *service); /** * Allocate a new service for the gateway to support @@ -145,6 +150,7 @@ service_alloc(const char *servname, const char *router) service->capabilities = service->router->getCapabilities(); service->client_count = 0; + service->n_dbref = 0; service->name = (char*)servname; service->routerModule = (char*)router; service->users_from_all = false; @@ -474,6 +480,9 @@ static void free_string_array(char** array) int serviceStart(SERVICE *service) { + /** Calculate the server weights */ + service_calculate_weights(service); + int listeners = 0; char **router_options = copy_string_array(service->routerOptions); @@ -729,6 +738,28 @@ int serviceHasProtocol(SERVICE *service, const char *protocol, return proto != NULL; } +/** + * Allocate a new server reference + * + * @param server Server to refer to + * @return Server reference or NULL on error + */ +static SERVER_REF* server_ref_create(SERVER *server) +{ + SERVER_REF *sref = MXS_MALLOC(sizeof(SERVER_REF)); + + if (sref) + { + sref->next = NULL; + sref->server = server; + sref->weight = SERVICE_BASE_SERVER_WEIGHT; + sref->connections = 0; + sref->active = true; + } + + return sref; +} + /** * Add a backend database server to a service * @@ -738,31 +769,71 @@ int serviceHasProtocol(SERVICE *service, const char *protocol, void serviceAddBackend(SERVICE *service, SERVER *server) { - SERVER_REF *sref = MXS_MALLOC(sizeof(SERVER_REF)); + SERVER_REF *new_ref = server_ref_create(server); - if (sref) + if (new_ref) { - sref->next = NULL; - sref->server = server; - spinlock_acquire(&service->spin); + + service->n_dbref++; + if (service->dbref) { SERVER_REF *ref = service->dbref; - while (ref->next) + SERVER_REF *prev = ref; + + while (ref) { + if (ref->server == server) + { + ref->active = true; + break; + } + prev = ref; ref = ref->next; } - ref->next = sref; + + if (ref == NULL) + { + /** A new server that hasn't been used by this service */ + atomic_synchronize(); + prev->next = new_ref; + } } else { - service->dbref = sref; + atomic_synchronize(); + service->dbref = new_ref; } spinlock_release(&service->spin); } } +/** + * @brief Remove a server from a service + * + * This function sets the server reference into an inactive state. This does not + * remove the server from the list or free any of the memory. + * + * @param service Service to modify + * @param server Server to remove + */ +void serviceRemoveBackend(SERVICE *service, const SERVER *server) +{ + spinlock_acquire(&service->spin); + + for (SERVER_REF *ref = service->dbref; ref; ref = ref->next) + { + if (ref->server == server) + { + ref->active = false; + service->n_dbref--; + break; + } + } + + spinlock_release(&service->spin); +} /** * Test if a server is part of a service * @@ -1294,8 +1365,11 @@ void dprintService(DCB *dcb, SERVICE *service) dcb_printf(dcb, "\tBackend databases:\n"); while (server) { - dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->server->name, server->server->port, - server->server->protocol); + if (SERVER_REF_IS_ACTIVE(server)) + { + dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->server->name, + server->server->port, server->server->protocol); + } server = server->next; } if (service->weightby) @@ -1802,6 +1876,23 @@ void service_shutdown() while (svc != NULL) { svc->svc_do_shutdown = true; + /* Call destroyInstance hook for routers */ + if (svc->router->destroyInstance) + { + svc->router->destroyInstance(svc->router_instance); + } + if (svc->n_filters) + { + FILTER_DEF **filters = svc->filters; + for (int i=0; i < svc->n_filters; i++) + { + if (filters[i]->obj->destroyInstance) + { + /* Call destroyInstance hook for filters */ + filters[i]->obj->destroyInstance(filters[i]->filter); + } + } + } svc = svc->next; } spinlock_release(&service_spin); @@ -2027,3 +2118,102 @@ bool service_all_services_have_listeners() spinlock_release(&service_spin); return rval; } + +static void service_calculate_weights(SERVICE *service) +{ + char *weightby = serviceGetWeightingParameter(service); + if (weightby && service->dbref) + { + /** Service has a weighting parameter and at least one server */ + int total = 0; + + /** Calculate total weight */ + for (SERVER_REF *server = service->dbref; server; server = server->next) + { + server->weight = SERVICE_BASE_SERVER_WEIGHT; + char *param = serverGetParameter(server->server, weightby); + if (param) + { + total += atoi(param); + } + } + + if (total == 0) + { + MXS_WARNING("Weighting Parameter for service '%s' will be ignored as " + "no servers have values for the parameter '%s'.", + service->name, weightby); + } + else if (total < 0) + { + MXS_ERROR("Sum of weighting parameter '%s' for service '%s' exceeds " + "maximum value of %d. Weighting will be ignored.", + weightby, service->name, INT_MAX); + } + else + { + /** Calculate the relative weight of the servers */ + for (SERVER_REF *server = service->dbref; server; server = server->next) + { + char *param = serverGetParameter(server->server, weightby); + if (param) + { + int wght = atoi(param); + int perc = (wght * SERVICE_BASE_SERVER_WEIGHT) / total; + + if (perc == 0) + { + MXS_ERROR("Weighting parameter '%s' with a value of %d for" + " server '%s' rounds down to zero with total weight" + " of %d for service '%s'. No queries will be " + "routed to this server as long as a server with" + " positive weight is available.", + weightby, wght, server->server->unique_name, + total, service->name); + } + else if (perc < 0) + { + MXS_ERROR("Weighting parameter '%s' for server '%s' is too large, " + "maximum value is %d. No weighting will be used for this " + "server.", weightby, server->server->unique_name, + INT_MAX / SERVICE_BASE_SERVER_WEIGHT); + perc = SERVICE_BASE_SERVER_WEIGHT; + } + server->weight = perc; + } + else + { + MXS_WARNING("Server '%s' has no parameter '%s' used for weighting" + " for service '%s'.", server->server->unique_name, + weightby, service->name); + } + } + } + } +} + +bool service_server_in_use(const SERVER *server) +{ + bool rval = false; + + spinlock_acquire(&service_spin); + + for (SERVICE *service = allServices; service && !rval; service = service->next) + { + spinlock_acquire(&service->spin); + + for (SERVER_REF *ref = service->dbref; ref && !rval; ref = ref->next) + { + if (ref->active && ref->server == server) + { + rval = true; + } + } + + spinlock_release(&service->spin); + } + + spinlock_release(&service_spin); + + return rval; +} diff --git a/server/core/session.c b/server/core/session.c index 8d62f23b8..ac29c884a 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -124,6 +124,8 @@ session_alloc(SERVICE *service, DCB *client_dcb) MXS_OOM(); return NULL; } + /** Assign a session id and increase */ + session->ses_id = (size_t)atomic_add(&session_id, 1) + 1; session->ses_is_child = (bool) DCB_IS_CLONE(client_dcb); session->service = service; session->client_dcb = client_dcb; @@ -221,8 +223,6 @@ session_alloc(SERVICE *service, DCB *client_dcb) session->client_dcb->user, session->client_dcb->remote); } - /** Assign a session id and increase, insert session into list */ - session->ses_id = (size_t)atomic_add(&session_id, 1) + 1; atomic_add(&service->stats.n_sessions, 1); atomic_add(&service->stats.n_current, 1); CHK_SESSION(session); diff --git a/server/core/test/testserver.c b/server/core/test/testserver.c index 9c10e6edf..67991f864 100644 --- a/server/core/test/testserver.c +++ b/server/core/test/testserver.c @@ -37,6 +37,11 @@ #include #include #include + +// This is pretty ugly but it's required to test internal functions +#include "../config.c" +#include "../server.c" + /** * test1 Allocate a server and do lots of other things * @@ -51,7 +56,7 @@ test1() /* Server tests */ ss_dfprintf(stderr, "testserver : creating server called MyServer"); set_libdir(MXS_STRDUP_A("../../modules/authenticator/")); - server = server_alloc("MyServer", "HTTPD", 9876, "NullAuthAllow", NULL); + server = server_alloc("uniquename", "127.0.0.1", 9876, "HTTPD", "NullAuthAllow", NULL); ss_info_dassert(server, "Allocating the server should not fail"); mxs_log_flush_sync(); @@ -67,7 +72,6 @@ test1() ss_dfprintf(stderr, "\t..done\nTesting Unique Name for Server."); ss_info_dassert(NULL == server_find_by_unique_name("uniquename"), "Should not find non-existent unique name."); - server_set_unique_name(server, "uniquename"); mxs_log_flush_sync(); ss_info_dassert(server == server_find_by_unique_name("uniquename"), "Should find by unique name."); ss_dfprintf(stderr, "\t..done\nTesting Status Setting for Server."); @@ -103,11 +107,83 @@ test1() } +#define TEST(A, B) do { if(!(A)){ printf(B"\n"); return false; }} while(false) + +bool test_load_config(const char *input, SERVER *server) +{ + DUPLICATE_CONTEXT dcontext; + + if (duplicate_context_init(&dcontext)) + { + CONFIG_CONTEXT ccontext = {.object = ""}; + + if (config_load_single_file(input, &dcontext, &ccontext)) + { + CONFIG_CONTEXT *obj = ccontext.next; + CONFIG_PARAMETER *param = obj->parameters; + + TEST(strcmp(obj->object, server->unique_name) == 0, "Server names differ"); + TEST(strcmp(server->name, config_get_param(param, "address")->value) == 0, "Server addresses differ"); + TEST(strcmp(server->protocol, config_get_param(param, "protocol")->value) == 0, "Server protocols differ"); + TEST(strcmp(server->authenticator, config_get_param(param, "authenticator")->value) == 0, + "Server authenticators differ"); + TEST(strcmp(server->auth_options, config_get_param(param, "authenticator_options")->value) == 0, + "Server authenticator options differ"); + TEST(server->port == atoi(config_get_param(param, "port")->value), "Server ports differ"); + TEST(create_new_server(obj) == 0, "Failed to create server from loaded config"); + } + } + + return true; +} + +bool test_serialize() +{ + char name[] = "serialized-server"; + char config_name[] = "serialized-server.cnf"; + char old_config_name[] = "serialized-server.cnf.old"; + char *persist_dir = MXS_STRDUP_A("./"); + set_config_persistdir(persist_dir); + SERVER *server = server_alloc(name, "127.0.0.1", 9876, "HTTPD", "NullAuthAllow", "fake=option"); + TEST(server, "Server allocation failed"); + + /** Make sure the files don't exist */ + unlink(config_name); + unlink(old_config_name); + + /** Serialize server to disk */ + TEST(server_serialize(server), "Failed to synchronize original server"); + + /** Load it again */ + TEST(test_load_config(config_name, server), "Failed to load the serialized server"); + + /** We should have two identical servers */ + SERVER *created = server_find_by_unique_name(name); + TEST(created->next == server, "We should end up with two servers"); + + rename(config_name, old_config_name); + + /** Serialize the loaded server to disk */ + TEST(server_serialize(created), "Failed to synchronize the copied server"); + + /** Check that they serialize to identical files */ + char cmd[1024]; + sprintf(cmd, "diff ./%s ./%s", config_name, old_config_name); + TEST(system(cmd) == 0, "The files are not identical"); + + return true; +} + int main(int argc, char **argv) { int result = 0; result += test1(); + if (!test_serialize()) + { + result++; + } + exit(result); } diff --git a/server/core/test/testusers.c b/server/core/test/testusers.c index f6bea1ef6..5986e5a00 100644 --- a/server/core/test/testusers.c +++ b/server/core/test/testusers.c @@ -45,9 +45,9 @@ static int test1() { - USERS *users; - char *authdata; - int result, count; + USERS *users; + const char *authdata; + int result, count; /* Poll tests */ ss_dfprintf(stderr, diff --git a/server/core/users.c b/server/core/users.c index 2c2a7ca56..c136a7a6d 100644 --- a/server/core/users.c +++ b/server/core/users.c @@ -87,12 +87,12 @@ users_free(USERS *users) * @return The number of users added to the table */ int -users_add(USERS *users, char *user, char *auth) +users_add(USERS *users, const char *user, const char *auth) { int add; atomic_add(&users->stats.n_adds, 1); - add = hashtable_add(users->data, user, auth); + add = hashtable_add(users->data, (char*)user, (char*)auth); atomic_add(&users->stats.n_entries, add); return add; } @@ -105,12 +105,12 @@ users_add(USERS *users, char *user, char *auth) * @return The number of users deleted from the table */ int -users_delete(USERS *users, char *user) +users_delete(USERS *users, const char *user) { int del; atomic_add(&users->stats.n_deletes, 1); - del = hashtable_delete(users->data, user); + del = hashtable_delete(users->data, (char*)user); atomic_add(&users->stats.n_entries, -del); return del; } @@ -122,11 +122,12 @@ users_delete(USERS *users, char *user) * @param user The user name * @return The authentication data or NULL on error */ -char -*users_fetch(USERS *users, char *user) +const char +*users_fetch(USERS *users, const char *user) { atomic_add(&users->stats.n_fetches, 1); - return hashtable_fetch(users->data, user); + // TODO: Returning data from the hashtable is not threadsafe. + return hashtable_fetch(users->data, (char*)user); } /** @@ -139,13 +140,13 @@ char * @return Number of users updated */ int -users_update(USERS *users, char *user, char *auth) +users_update(USERS *users, const char *user, const char *auth) { - if (hashtable_delete(users->data, user) == 0) + if (hashtable_delete(users->data, (char*)user) == 0) { return 0; } - return hashtable_add(users->data, user, auth); + return hashtable_add(users->data, (char*)user, (char*)auth); } /** @@ -154,7 +155,7 @@ users_update(USERS *users, char *user, char *auth) * @param users The users table */ void -usersPrint(USERS *users) +usersPrint(const USERS *users) { printf("Users table data\n"); hashtable_stats(users->data); @@ -167,7 +168,7 @@ usersPrint(USERS *users) * @param users The users table */ void -dcb_usersPrint(DCB *dcb, USERS *users) +dcb_usersPrint(DCB *dcb, const USERS *users) { if (users == NULL || users->data == NULL) { diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index ec4cbad6f..d08a0d40a 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -2649,17 +2649,12 @@ static bool check_server_permissions(SERVICE *service, SERVER* server, bool check_service_permissions(SERVICE* service) { if (is_internal_service(service->routerModule) || - config_get_global_options()->skip_permission_checks) + config_get_global_options()->skip_permission_checks || + service->dbref == NULL) // No servers to check { return true; } - if (service->dbref == NULL) - { - MXS_ERROR("[%s] Service is missing the servers parameter.", service->name); - return false; - } - char *user, *password; if (serviceGetUser(service, &user, &password) == 0) diff --git a/server/modules/authenticator/cdc_plain_auth.c b/server/modules/authenticator/cdc_plain_auth.c index 1b19d9b20..19c60a29f 100644 --- a/server/modules/authenticator/cdc_plain_auth.c +++ b/server/modules/authenticator/cdc_plain_auth.c @@ -139,7 +139,7 @@ static int cdc_auth_check(DCB *dcb, CDC_protocol *protocol, char *username, uint { if (dcb->listener->users) { - char *user_password = users_fetch(dcb->listener->users, username); + const char *user_password = users_fetch(dcb->listener->users, username); if (user_password) { diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index 39d7775eb..bd8b7a1b9 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(cache) +add_subdirectory(maxrows) add_subdirectory(ccrfilter) add_subdirectory(dbfwfilter) add_subdirectory(gatekeeper) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.c index 8ddb27e8a..b7519fc3f 100644 --- a/server/modules/filter/cache/cache.c +++ b/server/modules/filter/cache/cache.c @@ -80,7 +80,8 @@ FILTER_OBJECT *GetModuleObject() routeQuery, clientReply, diagnostics, - getCapabilities + getCapabilities, + NULL, // destroyInstance }; return &object; diff --git a/server/modules/filter/cache/rules.c b/server/modules/filter/cache/rules.c index cc2b3bdaa..7dd5bbdf0 100644 --- a/server/modules/filter/cache/rules.c +++ b/server/modules/filter/cache/rules.c @@ -782,9 +782,119 @@ static bool cache_rule_compare_n(CACHE_RULE *self, const char *value, size_t len static bool cache_rule_matches_column(CACHE_RULE *self, const char *default_db, const GWBUF *query) { ss_dassert(self->attribute == CACHE_ATTRIBUTE_COLUMN); - ss_info_dassert(!true, "Column matching not implemented yet."); - return false; + // TODO: Do this "parsing" when the rule item is created. + char buffer[strlen(self->value) + 1]; + strcpy(buffer, self->value); + + const char* rule_column = NULL; + const char* rule_table = NULL; + const char* rule_database = NULL; + char* dot1 = strchr(buffer, '.'); + char* dot2 = dot1 ? strchr(buffer, '.') : NULL; + + if (dot1 && dot2) + { + rule_database = buffer; + *dot1 = 0; + rule_table = dot1 + 1; + *dot2 = 0; + rule_column = dot2 + 1; + } + else if (dot1) + { + rule_table = buffer; + *dot1 = 0; + rule_column = dot1 + 1; + } + else + { + rule_column = buffer; + } + + const QC_FIELD_INFO *infos; + size_t n_infos; + + int n_tables; + char** tables = qc_get_table_names((GWBUF*)query, &n_tables, false); + + const char* default_table = NULL; + + if (n_tables == 1) + { + // Only if we have exactly one table can we assume anything + // about a table that has not been mentioned explicitly. + default_table = tables[0]; + } + + qc_get_field_info((GWBUF*)query, &infos, &n_infos); + + bool matches = false; + + size_t i = 0; + while (!matches && (i < n_infos)) + { + const QC_FIELD_INFO *info = (infos + i); + + if ((strcmp(info->column, rule_column) == 0) || (strcmp(info->column, "*") == 0)) + { + if (rule_table) + { + const char* check_table = info->table ? info->table : default_table; + + if (check_table && (strcmp(check_table, rule_table) == 0)) + { + if (rule_database) + { + const char *check_database = info->database ? info->database : default_db; + + if (check_database && (strcmp(check_database, rule_database) == 0)) + { + matches = true; + } + else + { + // If the rules specifies a database and either the database + // does not match or we do not know the database, the rule + // does *not* match. + matches = false; + } + } + else + { + // If the rule specifies no table, then if the table and column matches, + // the rule matches. + matches = true; + } + } + else + { + // The rules specifies a table and either the table does not match + // or we do not know the table, the rule does *not* match. + matches = false; + } + } + else + { + // If the rule specifies no table, then if the column matches, the + // rule matches. + matches = true; + } + } + + ++i; + } + + if (tables) + { + for (i = 0; i < (size_t)n_tables; ++i) + { + MXS_FREE(tables[i]); + } + MXS_FREE(tables); + } + + return matches; } /** diff --git a/server/modules/filter/cache/storage/storage_rocksdb/BuildRocksDB.cmake b/server/modules/filter/cache/storage/storage_rocksdb/BuildRocksDB.cmake index bfe2aa37e..bdc630b4e 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/BuildRocksDB.cmake +++ b/server/modules/filter/cache/storage/storage_rocksdb/BuildRocksDB.cmake @@ -1,7 +1,7 @@ # Build RocksDB -if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7))) - message(STATUS "GCC >= 4.7, RocksDB is built.") +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8))) + message(STATUS "GCC >= 4.8, RocksDB is built.") set(ROCKSDB_REPO "https://github.com/facebook/rocksdb.git" CACHE STRING "RocksDB Git repository") diff --git a/server/modules/filter/cache/test/testrules.c b/server/modules/filter/cache/test/testrules.c index 3039e6605..153f342a5 100644 --- a/server/modules/filter/cache/test/testrules.c +++ b/server/modules/filter/cache/test/testrules.c @@ -14,12 +14,37 @@ #include #include "rules.h" #include +#include +#include #if !defined(SS_DEBUG) #define SS_DEBUG #endif #include -struct test_case +GWBUF* create_gwbuf(const char* s) +{ + size_t query_len = strlen(s); + size_t payload_len = query_len + 1; + size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len; + + GWBUF* gwbuf = gwbuf_alloc(gwbuf_len); + ss_dassert(gwbuf); + + *((unsigned char*)((char*)GWBUF_DATA(gwbuf))) = payload_len; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 1)) = (payload_len >> 8); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 2)) = (payload_len >> 16); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 3)) = 0x00; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 4)) = 0x03; + memcpy((char*)GWBUF_DATA(gwbuf) + MYSQL_HEADER_LEN + 1, s, query_len); + + return gwbuf; +} + +// +// Test user rules. Basically tests that a user specification is translated +// into the correct pcre2 regex. +// +struct user_test_case { const char* json; struct @@ -29,31 +54,33 @@ struct test_case } expect; }; -#define TEST_CASE(op_from, from, op_to, to) \ +#define USER_TEST_CASE(op_from, from, op_to, to) \ { "{ \"use\": [ { \"attribute\": \"user\", \"op\": \"" #op_from "\", \"value\": \"" #from "\" } ] }",\ { op_to, #to } } -const struct test_case test_cases[] = +#define COLUMN_ + +const struct user_test_case user_test_cases[] = { - TEST_CASE(=, bob, CACHE_OP_LIKE, bob@.*), - TEST_CASE(=, 'bob', CACHE_OP_LIKE, bob@.*), - TEST_CASE(=, bob@%, CACHE_OP_LIKE, bob@.*), - TEST_CASE(=, 'bob'@'%.52', CACHE_OP_LIKE, bob@.*\\.52), - TEST_CASE(=, bob@127.0.0.1, CACHE_OP_EQ, bob@127.0.0.1), - TEST_CASE(=, b*b@127.0.0.1, CACHE_OP_EQ, b*b@127.0.0.1), - TEST_CASE(=, b*b@%.0.0.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\.0\\.1), - TEST_CASE(=, b*b@%.0.%.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\..*\\.1), + USER_TEST_CASE(=, bob, CACHE_OP_LIKE, bob@.*), + USER_TEST_CASE(=, 'bob', CACHE_OP_LIKE, bob@.*), + USER_TEST_CASE(=, bob@%, CACHE_OP_LIKE, bob@.*), + USER_TEST_CASE(=, 'bob'@'%.52', CACHE_OP_LIKE, bob@.*\\.52), + USER_TEST_CASE(=, bob@127.0.0.1, CACHE_OP_EQ, bob@127.0.0.1), + USER_TEST_CASE(=, b*b@127.0.0.1, CACHE_OP_EQ, b*b@127.0.0.1), + USER_TEST_CASE(=, b*b@%.0.0.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\.0\\.1), + USER_TEST_CASE(=, b*b@%.0.%.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\..*\\.1), }; -const size_t n_test_cases = sizeof(test_cases) / sizeof(test_cases[0]); +const size_t n_user_test_cases = sizeof(user_test_cases) / sizeof(user_test_cases[0]); -int test() +int test_user() { int errors = 0; - for (int i = 0; i < n_test_cases; ++i) + for (int i = 0; i < n_user_test_cases; ++i) { - const struct test_case *test_case = &test_cases[i]; + const struct user_test_case *test_case = &user_test_cases[i]; CACHE_RULES *rules = cache_rules_parse(test_case->json, 0); ss_dassert(rules); @@ -78,9 +105,86 @@ int test() rule->value); ++errors; } + + cache_rules_free(rules); } - return errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + return errors; +} + +// +// +// +struct store_test_case +{ + const char *rule; // The rule in JSON format. + bool matches; // Whether or not the rule should match the query. + const char *default_db; // The current default db. + const char *query; // The query to be matched against the rule. +}; + +#define STORE_TEST_CASE(attribute, op, value, matches, default_db, query) \ +{ "{ \"store\": [ { \"attribute\": \"" attribute "\", \"op\": \"" op "\", \"value\": \"" value "\" } ] }",\ + matches, default_db, query } + +// In the following, +// true: The query SHOULD match the rule, +// false: The query should NOT match the rule. +const struct store_test_case store_test_cases[] = +{ + STORE_TEST_CASE("column", "=", "a", true, NULL, "SELECT a FROM tbl"), + STORE_TEST_CASE("column", "=", "b", false, NULL, "SELECT a FROM tbl") +}; + +const size_t n_store_test_cases = sizeof(store_test_cases) / sizeof(store_test_cases[0]); + +int test_store() +{ + int errors = 0; + + for (int i = 0; i < n_store_test_cases; ++i) + { + const struct store_test_case *test_case = &store_test_cases[i]; + + CACHE_RULES *rules = cache_rules_parse(test_case->rule, 0); + ss_dassert(rules); + + CACHE_RULE *rule = rules->store_rules; + ss_dassert(rule); + + GWBUF *packet = create_gwbuf(test_case->query); + + bool matches = cache_rules_should_store(rules, test_case->default_db, packet); + + if (matches != test_case->matches) + { + printf("Query : %s\n" + "Rule : %s\n" + "Expected: %s\n" + "Result : %s\n\n", + test_case->query, + test_case->rule, + test_case->matches ? "A match" : "Not a match", + matches ? "A match" : "Not a match"); + } + + gwbuf_free(packet); + + cache_rules_free(rules); + } + + return errors; +} + + +int test() +{ + int errors = 0; + + errors += test_user(); + errors += test_store(); + + return errors ? EXIT_FAILURE : EXIT_SUCCESS; } int main() @@ -89,7 +193,14 @@ int main() if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) { - rc = test(); + if (qc_init("qc_sqlite", "")) + { + rc = test(); + } + else + { + MXS_ERROR("Could not initialize query classifier."); + } mxs_log_finish(); } diff --git a/server/modules/filter/ccrfilter/ccrfilter.c b/server/modules/filter/ccrfilter/ccrfilter.c index 3abda9137..157c750ed 100644 --- a/server/modules/filter/ccrfilter/ccrfilter.c +++ b/server/modules/filter/ccrfilter/ccrfilter.c @@ -75,9 +75,10 @@ static FILTER_OBJECT MyObject = setDownstream, NULL, // No Upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; #define CCR_DEFAULT_TIME 60 diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index 5008bfad2..b292286ff 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -114,11 +114,12 @@ static FILTER_OBJECT MyObject = closeSession, freeSession, setDownstream, - NULL, + NULL, // No setUpStream routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** @@ -1701,13 +1702,12 @@ bool rule_matches(FW_INSTANCE* my_instance, RULELIST *rulelist, char* query) { - char *ptr, *where, *msg = NULL; + char *ptr, *msg = NULL; char emsg[512]; unsigned char* memptr = (unsigned char*) queue->start; bool is_sql, is_real, matches; qc_query_op_t optype = QUERY_OP_UNDEFINED; - STRLINK* strln = NULL; QUERYSPEED* queryspeed = NULL; QUERYSPEED* rule_qs = NULL; time_t time_now; @@ -1745,7 +1745,7 @@ bool rule_matches(FW_INSTANCE* my_instance, case QUERY_OP_UPDATE: case QUERY_OP_INSERT: case QUERY_OP_DELETE: - // In these cases, we have to be able to trust what qc_get_affected_fields + // In these cases, we have to be able to trust what qc_get_field_info // returns. Unless the query was parsed completely, we cannot do that. msg = create_parse_error(my_instance, "parsed completely", query, &matches); goto queryresolved; @@ -1817,32 +1817,29 @@ bool rule_matches(FW_INSTANCE* my_instance, case RT_COLUMN: if (is_sql && is_real) { - where = qc_get_affected_fields(queue); - if (where != NULL) - { - char* saveptr; - char* tok = strtok_r(where, " ", &saveptr); - while (tok) - { - strln = (STRLINK*) rulelist->rule->data; - while (strln) - { - if (strcasecmp(tok, strln->value) == 0) - { - matches = true; + const QC_FIELD_INFO* infos; + size_t n_infos; + qc_get_field_info(queue, &infos, &n_infos); - sprintf(emsg, "Permission denied to column '%s'.", strln->value); - MXS_INFO("dbfwfilter: rule '%s': query targets forbidden column: %s", - rulelist->rule->name, strln->value); - msg = MXS_STRDUP_A(emsg); - MXS_FREE(where); - goto queryresolved; - } - strln = strln->next; + for (size_t i = 0; i < n_infos; ++i) + { + const char* tok = infos[i].column; + + STRLINK* strln = (STRLINK*) rulelist->rule->data; + while (strln) + { + if (strcasecmp(tok, strln->value) == 0) + { + matches = true; + + sprintf(emsg, "Permission denied to column '%s'.", strln->value); + MXS_INFO("dbfwfilter: rule '%s': query targets forbidden column: %s", + rulelist->rule->name, strln->value); + msg = MXS_STRDUP_A(emsg); + goto queryresolved; } - tok = strtok_r(NULL, ",", &saveptr); + strln = strln->next; } - MXS_FREE(where); } } break; @@ -1850,23 +1847,22 @@ bool rule_matches(FW_INSTANCE* my_instance, case RT_WILDCARD: if (is_sql && is_real) { - char * strptr; - where = qc_get_affected_fields(queue); + const QC_FIELD_INFO* infos; + size_t n_infos; + qc_get_field_info(queue, &infos, &n_infos); - if (where != NULL) + for (size_t i = 0; i < n_infos; ++i) { - strptr = where; + const char* column = infos[i].column; - if (strchr(strptr, '*')) + if (strcmp(column, "*") == 0) { matches = true; msg = MXS_STRDUP_A("Usage of wildcard denied."); MXS_INFO("dbfwfilter: rule '%s': query contains a wildcard.", rulelist->rule->name); - MXS_FREE(where); goto queryresolved; } - MXS_FREE(where); } } break; diff --git a/server/modules/filter/gatekeeper/gatekeeper.c b/server/modules/filter/gatekeeper/gatekeeper.c index 58e1d0fd2..57bb97848 100644 --- a/server/modules/filter/gatekeeper/gatekeeper.c +++ b/server/modules/filter/gatekeeper/gatekeeper.c @@ -103,9 +103,10 @@ static FILTER_OBJECT MyObject = setDownstream, NULL, // No upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/hintfilter/hintfilter.c b/server/modules/filter/hintfilter/hintfilter.c index b6a0e7dd3..fe35db089 100644 --- a/server/modules/filter/hintfilter/hintfilter.c +++ b/server/modules/filter/hintfilter/hintfilter.c @@ -52,9 +52,10 @@ static FILTER_OBJECT MyObject = setDownstream, NULL, // No upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/luafilter/luafilter.c b/server/modules/filter/luafilter/luafilter.c index 137446f8c..f65542977 100644 --- a/server/modules/filter/luafilter/luafilter.c +++ b/server/modules/filter/luafilter/luafilter.c @@ -96,6 +96,7 @@ static FILTER_OBJECT MyObject = clientReply, diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** @@ -570,7 +571,7 @@ static void diagnostic(FILTER *instance, void *fsession, DCB *dcb) lua_gettop(my_instance->global_lua_state); if (lua_isstring(my_instance->global_lua_state, -1)) { - dcb_printf(dcb, lua_tostring(my_instance->global_lua_state, -1)); + dcb_printf(dcb, "%s", lua_tostring(my_instance->global_lua_state, -1)); dcb_printf(dcb, "\n"); } } diff --git a/server/modules/filter/maxrows/CMakeLists.txt b/server/modules/filter/maxrows/CMakeLists.txt new file mode 100644 index 000000000..7fc89a4f1 --- /dev/null +++ b/server/modules/filter/maxrows/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(maxrows SHARED maxrows.c) +target_link_libraries(maxrows maxscale-common) +set_target_properties(maxrows PROPERTIES VERSION "1.0.0") +set_target_properties(maxrows PROPERTIES LINK_FLAGS -Wl,-z,defs) +install_module(maxrows experimental) diff --git a/server/modules/filter/maxrows/maxrows.c b/server/modules/filter/maxrows/maxrows.c new file mode 100644 index 000000000..5c56b0c90 --- /dev/null +++ b/server/modules/filter/maxrows/maxrows.c @@ -0,0 +1,920 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +/** + * @file maxrows.c - Result set limit Filter + * @verbatim + * + * + * The filter returns a void result set if the number of rows in the result set + * from backend exceeds the max_rows parameter. + * + * Date Who Description + * 26/10/2016 Massimiliano Pinto Initial implementation + * 04/11/2016 Massimiliano Pinto Addition of SERVER_MORE_RESULTS_EXIST flag (0x0008) + * detection in handle_expecting_rows(). + * 07/11/2016 Massimiliano Pinto handle_expecting_rows renamed to handle_rows + * + * @endverbatim + */ + +#define MXS_MODULE_NAME "maxrows" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "maxrows.h" + +static char VERSION_STRING[] = "V1.0.0"; + +static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *sdata); +static void freeSession(FILTER *instance, void *sdata); +static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *downstream); +static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *upstream); +static int routeQuery(FILTER *instance, void *sdata, GWBUF *queue); +static int clientReply(FILTER *instance, void *sdata, GWBUF *queue); +static void diagnostics(FILTER *instance, void *sdata, DCB *dcb); +static uint64_t getCapabilities(void); + +/* Global symbols of the Module */ + +MODULE_INFO info = +{ + MODULE_API_FILTER, + MODULE_IN_DEVELOPMENT, + FILTER_VERSION, + "A filter that is capable of limiting the resultset number of rows." +}; + +char *version() +{ + return VERSION_STRING; +} + +/** + * The module initialization functions, called when the module has + * been loaded. + */ +void ModuleInit() +{ +} + +/** + * The module entry point function, called when the module is loaded. + * + * @return The module object. + */ +FILTER_OBJECT *GetModuleObject() +{ + static FILTER_OBJECT object = + { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostics, + getCapabilities, + NULL, // No destroyInstance + }; + + return &object; +}; + +/* Implementation */ + +typedef struct maxrows_config +{ + uint32_t max_resultset_rows; + uint32_t max_resultset_size; + uint32_t debug; +} MAXROWS_CONFIG; + +static const MAXROWS_CONFIG DEFAULT_CONFIG = +{ + MAXROWS_DEFAULT_MAX_RESULTSET_ROWS, + MAXROWS_DEFAULT_MAX_RESULTSET_SIZE, + MAXROWS_DEFAULT_DEBUG +}; + +typedef struct maxrows_instance +{ + const char *name; + MAXROWS_CONFIG config; +} MAXROWS_INSTANCE; + +typedef enum maxrows_session_state +{ + MAXROWS_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. + MAXROWS_EXPECTING_FIELDS, // A select has been sent, and we want more fields. + MAXROWS_EXPECTING_ROWS, // A select has been sent, and we want more rows. + MAXROWS_EXPECTING_NOTHING, // We are not expecting anything from the server. + MAXROWS_IGNORING_RESPONSE, // We are not interested in the data received from the server. + MAXROWS_DISCARDING_RESPONSE, // We have returned empty result set and we discard any new data. +} maxrows_session_state_t; + +typedef struct maxrows_response_state +{ + GWBUF* data; /**< Response data, possibly incomplete. */ + size_t n_totalfields; /**< The number of fields a resultset contains. */ + size_t n_fields; /**< How many fields we have received, <= n_totalfields. */ + size_t n_rows; /**< How many rows we have received. */ + size_t offset; /**< Where we are in the response buffer. */ +} MAXROWS_RESPONSE_STATE; + +static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state); + +typedef struct maxrows_session_data +{ + MAXROWS_INSTANCE *instance; /**< The maxrows instance the session is associated with. */ + DOWNSTREAM down; /**< The previous filter or equivalent. */ + UPSTREAM up; /**< The next filter or equivalent. */ + MAXROWS_RESPONSE_STATE res; /**< The response state. */ + SESSION *session; /**< The session this data is associated with. */ + char *default_db; /**< The default database. */ + char *use_db; /**< Pending default database. Needs server response. */ + maxrows_session_state_t state; +} MAXROWS_SESSION_DATA; + +static MAXROWS_SESSION_DATA *maxrows_session_data_create(MAXROWS_INSTANCE *instance, SESSION *session); +static void maxrows_session_data_free(MAXROWS_SESSION_DATA *data); + +static int handle_expecting_fields(MAXROWS_SESSION_DATA *csdata); +static int handle_expecting_nothing(MAXROWS_SESSION_DATA *csdata); +static int handle_expecting_response(MAXROWS_SESSION_DATA *csdata); +static int handle_rows(MAXROWS_SESSION_DATA *csdata); +static int handle_ignoring_response(MAXROWS_SESSION_DATA *csdata); +static bool process_params(char **options, FILTER_PARAMETER **params, MAXROWS_CONFIG* config); + +static int send_upstream(MAXROWS_SESSION_DATA *csdata); +static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata); + +/* API BEGIN */ + +/** + * Create an instance of the maxrows filter for a particular service + * within MaxScale. + * + * @param name The name of the instance (as defined in the config file). + * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter + * + * @return The instance data for this new instance + */ +static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **params) +{ + MAXROWS_INSTANCE *cinstance = NULL; + MAXROWS_CONFIG config = DEFAULT_CONFIG; + + if (process_params(options, params, &config)) + { + cinstance = MXS_CALLOC(1, sizeof(MAXROWS_INSTANCE)); + if (cinstance) + { + cinstance->name = name; + cinstance->config = config; + } + } + + return (FILTER*)cinstance; +} + +/** + * Associate a new session with this instance of the filter. + * + * @param instance The maxrows instance data + * @param session The session itself + * + * @return Session specific data for this session + */ +static void *newSession(FILTER *instance, SESSION *session) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = maxrows_session_data_create(cinstance, session); + + return csdata; +} + +/** + * A session has been closed. + * + * @param instance The maxrows instance data + * @param sdata The session data of the session being closed + */ +static void closeSession(FILTER *instance, void *sdata) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; +} + +/** + * Free the session data. + * + * @param instance The maxrows instance data + * @param sdata The session data of the session being closed + */ +static void freeSession(FILTER *instance, void *sdata) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + maxrows_session_data_free(csdata); +} + +/** + * Set the downstream component for this filter. + * + * @param instance The maxrowsinstance data + * @param sdata The session data of the session + * @param down The downstream filter or router + */ +static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *down) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + csdata->down = *down; +} + +/** + * Set the upstream component for this filter. + * + * @param instance The maxrows instance data + * @param sdata The session data of the session + * @param up The upstream filter or router + */ +static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *up) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + csdata->up = *up; +} + +/** + * A request on its way to a backend is delivered to this function. + * + * @param instance The filter instance data + * @param sdata The filter session data + * @param buffer Buffer containing an MySQL protocol packet. + */ +static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + uint8_t *data = GWBUF_DATA(packet); + + // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING + ss_dassert(GWBUF_IS_CONTIGUOUS(packet)); + ss_dassert(GWBUF_LENGTH(packet) >= MYSQL_HEADER_LEN + 1); + ss_dassert(MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN == GWBUF_LENGTH(packet)); + + maxrows_response_state_reset(&csdata->res); + csdata->state = MAXROWS_IGNORING_RESPONSE; + + switch ((int)MYSQL_GET_COMMAND(data)) + { + case MYSQL_COM_QUERY: + { + csdata->state = MAXROWS_EXPECTING_RESPONSE; + break; + + default: + break; + } + } + + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("Maxrows filter is sending data."); + } + + return csdata->down.routeQuery(csdata->down.instance, csdata->down.session, packet); +} + +/** + * A response on its way to the client is delivered to this function. + * + * @param instance The filter instance data + * @param sdata The filter session data + * @param queue The query data + */ +static int clientReply(FILTER *instance, void *sdata, GWBUF *data) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + int rv; + + if (csdata->res.data) + { + gwbuf_append(csdata->res.data, data); + } + else + { + csdata->res.data = data; + } + + if (csdata->state != MAXROWS_IGNORING_RESPONSE) + { + if (csdata->state != MAXROWS_DISCARDING_RESPONSE) + { + if (gwbuf_length(csdata->res.data) > csdata->instance->config.max_resultset_size) + { + if (csdata->instance->config.debug & MAXROWS_DEBUG_DISCARDING) + { + MXS_NOTICE("Current size %uB of resultset, at least as much " + "as maximum allowed size %uKiB. Not returning data.", + gwbuf_length(csdata->res.data), + csdata->instance->config.max_resultset_size / 1024); + } + + csdata->state = MAXROWS_DISCARDING_RESPONSE; + } + } + } + + switch (csdata->state) + { + case MAXROWS_EXPECTING_FIELDS: + rv = handle_expecting_fields(csdata); + break; + + case MAXROWS_EXPECTING_NOTHING: + rv = handle_expecting_nothing(csdata); + break; + + case MAXROWS_EXPECTING_RESPONSE: + rv = handle_expecting_response(csdata); + break; + + case MAXROWS_DISCARDING_RESPONSE: + case MAXROWS_EXPECTING_ROWS: + rv = handle_rows(csdata); + break; + + case MAXROWS_IGNORING_RESPONSE: + rv = handle_ignoring_response(csdata); + break; + + default: + MXS_ERROR("Internal filter logic broken, unexpected state: %d", csdata->state); + ss_dassert(!true); + rv = send_upstream(csdata); + maxrows_response_state_reset(&csdata->res); + csdata->state = MAXROWS_IGNORING_RESPONSE; + } + + return rv; +} + +/** + * Diagnostics routine + * + * If csdata is NULL then print diagnostics on the instance as a whole, + * otherwise print diagnostics for the particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static void diagnostics(FILTER *instance, void *sdata, DCB *dcb) +{ + MAXROWS_INSTANCE *cinstance = (MAXROWS_INSTANCE*)instance; + MAXROWS_SESSION_DATA *csdata = (MAXROWS_SESSION_DATA*)sdata; + + dcb_printf(dcb, "Maxrows filter is working\n"); +} + + +/** + * Capability routine. + * + * @return The capabilities of the filter. + */ +static uint64_t getCapabilities(void) +{ + return RCAP_TYPE_STMT_INPUT; +} + +/* API END */ + +/** + * Reset maxrows response state + * + * @param state Pointer to object. + */ +static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state) +{ + state->data = NULL; + state->n_totalfields = 0; + state->n_fields = 0; + state->n_rows = 0; + state->offset = 0; +} + +/** + * Create maxrows session data + * + * @param instance The maxrows instance this data is associated with. + * + * @return Session data or NULL if creation fails. + */ +static MAXROWS_SESSION_DATA *maxrows_session_data_create(MAXROWS_INSTANCE *instance, + SESSION* session) +{ + MAXROWS_SESSION_DATA *data = (MAXROWS_SESSION_DATA*)MXS_CALLOC(1, sizeof(MAXROWS_SESSION_DATA)); + + if (data) + { + char *default_db = NULL; + + ss_dassert(session->client_dcb); + ss_dassert(session->client_dcb->data); + MYSQL_session *mysql_session = (MYSQL_session*)session->client_dcb->data; + + if (mysql_session->db[0] != 0) + { + default_db = MXS_STRDUP(mysql_session->db); + } + + if ((mysql_session->db[0] == 0) || default_db) + { + data->instance = instance; + data->session = session; + data->state = MAXROWS_EXPECTING_NOTHING; + data->default_db = default_db; + } + else + { + MXS_FREE(data); + data = NULL; + } + } + + return data; +} + +/** + * Free maxrows session data. + * + * @param A maxrows session data previously allocated using session_data_create(). + */ +static void maxrows_session_data_free(MAXROWS_SESSION_DATA* data) +{ + if (data) + { + // In normal circumstances, only data->default_db may be non-NULL at + // this point. However, if the authentication with the backend fails + // and the session is closed, data->use_db may be non-NULL. + MXS_FREE(data->use_db); + MXS_FREE(data->default_db); + MXS_FREE(data); + } +} + +/** + * Called when resultset field information is handled. + * + * @param csdata The maxrows session data. + */ +static int handle_expecting_fields(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->state == MAXROWS_EXPECTING_FIELDS); + ss_dassert(csdata->res.data); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(csdata->res.data); + + while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MYSQL_HEADER_LEN + 1]; + gwbuf_copy_data(csdata->res.data, csdata->res.offset, MYSQL_HEADER_LEN + 1, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (csdata->res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xfe: // EOF, the one after the fields. + csdata->res.offset += packetlen; + csdata->state = MAXROWS_EXPECTING_ROWS; + rv = handle_rows(csdata); + break; + + default: // Field information. + csdata->res.offset += packetlen; + ++csdata->res.n_fields; + ss_dassert(csdata->res.n_fields <= csdata->res.n_totalfields); + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when data is received (even if nothing is expected) from the server. + * + * @param csdata The maxrows session data. + */ +static int handle_expecting_nothing(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->state == MAXROWS_EXPECTING_NOTHING); + ss_dassert(csdata->res.data); + MXS_ERROR("Received data from the backend although we were expecting nothing."); + ss_dassert(!true); + + return send_upstream(csdata); +} + +/** + * Called when a response is received from the server. + * + * @param csdata The maxrows session data. + */ +static int handle_expecting_response(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->state == MAXROWS_EXPECTING_RESPONSE); + ss_dassert(csdata->res.data); + + int rv = 1; + + size_t buflen = gwbuf_length(csdata->res.data); + + if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. + { + // Reserve enough space to accomodate for the largest length encoded integer, + // which is type field + 8 bytes. + uint8_t header[MYSQL_HEADER_LEN + 1 + 8]; + gwbuf_copy_data(csdata->res.data, 0, MYSQL_HEADER_LEN + 1, header); + + switch ((int)MYSQL_GET_COMMAND(header)) + { + case 0x00: // OK + case 0xff: // ERR + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("OK or ERR"); + } + rv = send_upstream(csdata); + csdata->state = MAXROWS_IGNORING_RESPONSE; + break; + + case 0xfb: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("GET_MORE_CLIENT_DATA"); + } + rv = send_upstream(csdata); + csdata->state = MAXROWS_IGNORING_RESPONSE; + break; + + default: + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("RESULTSET"); + } + + if (csdata->res.n_totalfields != 0) + { + // We've seen the header and have figured out how many fields there are. + csdata->state = MAXROWS_EXPECTING_FIELDS; + rv = handle_expecting_fields(csdata); + } + else + { + // leint_bytes() returns the length of the int type field + the size of the + // integer. + size_t n_bytes = leint_bytes(&header[4]); + + if (MYSQL_HEADER_LEN + n_bytes <= buflen) + { + // Now we can figure out how many fields there are, but first we + // need to copy some more data. + gwbuf_copy_data(csdata->res.data, + MYSQL_HEADER_LEN + 1, n_bytes - 1, &header[MYSQL_HEADER_LEN + 1]); + + csdata->res.n_totalfields = leint_value(&header[4]); + csdata->res.offset = MYSQL_HEADER_LEN + n_bytes; + + csdata->state = MAXROWS_EXPECTING_FIELDS; + rv = handle_expecting_fields(csdata); + } + else + { + // We need more data. We will be called again, when data is available. + } + } + break; + } + } + + return rv; +} + +/** + * Called when resultset rows are handled. + * + * @param csdata The maxrows session data. + */ +static int handle_rows(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->state == MAXROWS_EXPECTING_ROWS || csdata->state == MAXROWS_DISCARDING_RESPONSE); + ss_dassert(csdata->res.data); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(csdata->res.data); + + while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MAXROWS_EOF_PACKET_LEN]; //it holds a full EOF packet + gwbuf_copy_data(csdata->res.data, csdata->res.offset, MAXROWS_EOF_PACKET_LEN, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (csdata->res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xff: // ERR packet after the rows. + csdata->res.offset += packetlen; + ss_dassert(csdata->res.offset == buflen); + + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("Error packet seen while handling result set"); + } + /* + * This is the ERR packet that could terminate a Multi-Resultset. + */ + if (csdata->state == MAXROWS_DISCARDING_RESPONSE) + { + rv = send_ok_upstream(csdata); + } + else + { + rv = send_upstream(csdata); + } + csdata->state = MAXROWS_EXPECTING_NOTHING; + + break; + + case 0x0: // OK packet after the rows. + /* OK could the last packet in the Multi-Resultset transmission: + * handle DISCARD or send all the data. + * But it could also be sent instead of EOF from as in MySQL 5.7.5 + * if client sends CLIENT_DEPRECATE_EOF capability OK packet could + * have the SERVER_MORE_RESULTS_EXIST flag. + * Note: Flags in the OK packet are at the same offset as in EOF. + */ + case 0xfe: // EOF, the one after the rows. + csdata->res.offset += packetlen; + ss_dassert(csdata->res.offset == buflen); + + /* EOF could be the last packet in the transmission: + * check first whether SERVER_MORE_RESULTS_EXIST flag is set. + * If so more results set could come. The end of stream + * will be an OK packet. + */ + if (packetlen < MAXROWS_EOF_PACKET_LEN) + { + MXS_ERROR("EOF packet has size of %lu instead of %d", packetlen, MAXROWS_EOF_PACKET_LEN); + rv = send_ok_upstream(csdata); + csdata->state = MAXROWS_EXPECTING_NOTHING; + break; + } + + int flags = gw_mysql_get_byte2(header + MAXROWS_MYSQL_EOF_PACKET_FLAGS_OFFSET); + + if (!(flags & SERVER_MORE_RESULTS_EXIST)) + { + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("OK or EOF packet seen terminating the resultset"); + } + + if (csdata->state == MAXROWS_DISCARDING_RESPONSE) + { + rv = send_ok_upstream(csdata); + } + else + { + rv = send_upstream(csdata); + } + + csdata->state = MAXROWS_EXPECTING_NOTHING; + } + else + { + if (csdata->instance->config.debug & MAXROWS_DEBUG_DECISIONS) + { + MXS_NOTICE("EOF or OK packet seen with SERVER_MORE_RESULTS_EXIST flag: waiting for more data"); + } + } + + break; + + case 0xfb: // NULL + default: // length-encoded-string + csdata->res.offset += packetlen; + ++csdata->res.n_rows; + + if (csdata->state != MAXROWS_DISCARDING_RESPONSE) + { + if (csdata->res.n_rows > csdata->instance->config.max_resultset_rows) + { + if (csdata->instance->config.debug & MAXROWS_DEBUG_DISCARDING) + { + MXS_INFO("max_resultset_rows %lu reached, not returning the result.", csdata->res.n_rows); + } + + csdata->state = MAXROWS_DISCARDING_RESPONSE; + } + } + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when all data from the server is ignored. + * + * @param csdata The maxrows session data. + */ +static int handle_ignoring_response(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->state == MAXROWS_IGNORING_RESPONSE); + ss_dassert(csdata->res.data); + + return send_upstream(csdata); +} + +/** + * Processes the maxrows params + * + * @param options Options as passed to the filter. + * @param params Parameters as passed to the filter. + * @param config Pointer to config instance where params will be stored. + * + * @return True if all parameters could be processed, false otherwise. + */ +static bool process_params(char **options, FILTER_PARAMETER **params, MAXROWS_CONFIG* config) +{ + bool error = false; + + for (int i = 0; params[i]; ++i) + { + const FILTER_PARAMETER *param = params[i]; + + /* We could add a new parameter, max_resultset_columns: + * This way if result has more than max_resultset_columns + * we return 0 result + */ + + if (strcmp(param->name, "max_resultset_rows") == 0) + { + int v = atoi(param->value); + + if (v > 0) + { + config->max_resultset_rows = v; + } + else + { + config->max_resultset_rows = MAXROWS_DEFAULT_MAX_RESULTSET_ROWS; + } + } + else if (strcmp(param->name, "max_resultset_size") == 0) + { + int v = atoi(param->value); + + if (v > 0) + { + config->max_resultset_size = v * 1024; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", param->name); + error = true; + } + } + else if (strcmp(param->name, "debug") == 0) + { + int v = atoi(param->value); + + if ((v >= MAXROWS_DEBUG_MIN) && (v <= MAXROWS_DEBUG_MAX)) + { + config->debug = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be between %d and %d, inclusive.", + param->name, MAXROWS_DEBUG_MIN, MAXROWS_DEBUG_MAX); + error = true; + } + } + else if (!filter_standard_parameter(params[i]->name)) + { + MXS_ERROR("Unknown configuration entry '%s'.", param->name); + error = true; + } + } + + return !error; +} + +/** + * Send data upstream. + * + * @param csdata Session data + * + * @return Whatever the upstream returns. + */ +static int send_upstream(MAXROWS_SESSION_DATA *csdata) +{ + ss_dassert(csdata->res.data != NULL); + + int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, csdata->res.data); + csdata->res.data = NULL; + + return rv; +} + +/** + * Send OK packet data upstream. + * + * @param csdata Session data + * + * @return Whatever the upstream returns. + */ +static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata) +{ + /* Note: sequence id is always 01 (4th byte) */ + uint8_t ok[MAXROWS_OK_PACKET_LEN] = {07,00,00,01,00,00,00,02,00,00,00}; + GWBUF *packet = gwbuf_alloc(MAXROWS_OK_PACKET_LEN); + uint8_t *ptr = GWBUF_DATA(packet); + memcpy(ptr, &ok, MAXROWS_OK_PACKET_LEN); + + ss_dassert(csdata->res.data != NULL); + + int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, packet); + gwbuf_free(csdata->res.data); + csdata->res.data = NULL; + + return rv; +} diff --git a/server/modules/filter/maxrows/maxrows.h b/server/modules/filter/maxrows/maxrows.h new file mode 100644 index 000000000..aa15f84a5 --- /dev/null +++ b/server/modules/filter/maxrows/maxrows.h @@ -0,0 +1,43 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include + +MXS_BEGIN_DECLS + +#define MAXROWS_OK_PACKET_LEN 11 +#define MAXROWS_EOF_PACKET_LEN 9 + +/* + * The EOF packet 2 bytes flags start after: + * network header (4 bytes) + eof indicator (1) + 2 bytes warnings count) + */ +#define MAXROWS_MYSQL_EOF_PACKET_FLAGS_OFFSET (MYSQL_HEADER_LEN + 1 + 2) + +#define MAXROWS_DEBUG_NONE 0 +#define MAXROWS_DEBUG_DISCARDING 1 +#define MAXROWS_DEBUG_DECISIONS 2 + +#define MAXROWS_DEBUG_USAGE (MAXROWS_DEBUG_DECISIONS | MAXROWS_DEBUG_DISCARDING) +#define MAXROWS_DEBUG_MIN MAXROWS_DEBUG_NONE +#define MAXROWS_DEBUG_MAX MAXROWS_DEBUG_USAGE + +// Count +#define MAXROWS_DEFAULT_MAX_RESULTSET_ROWS UINT_MAX +// Bytes +#define MAXROWS_DEFAULT_MAX_RESULTSET_SIZE 64 * 1024 +// Integer value +#define MAXROWS_DEFAULT_DEBUG 0 + +MXS_END_DECLS diff --git a/server/modules/filter/mqfilter/mqfilter.c b/server/modules/filter/mqfilter/mqfilter.c index 263626c1b..f9b29d4d0 100644 --- a/server/modules/filter/mqfilter/mqfilter.c +++ b/server/modules/filter/mqfilter/mqfilter.c @@ -116,6 +116,7 @@ static FILTER_OBJECT MyObject = clientReply, diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/namedserverfilter/namedserverfilter.c b/server/modules/filter/namedserverfilter/namedserverfilter.c index 788a560d7..e31ae7632 100644 --- a/server/modules/filter/namedserverfilter/namedserverfilter.c +++ b/server/modules/filter/namedserverfilter/namedserverfilter.c @@ -69,9 +69,10 @@ static FILTER_OBJECT MyObject = setDownstream, NULL, // No Upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/qlafilter/qlafilter.c b/server/modules/filter/qlafilter/qlafilter.c index 420e63328..7997b6ed9 100644 --- a/server/modules/filter/qlafilter/qlafilter.c +++ b/server/modules/filter/qlafilter/qlafilter.c @@ -62,6 +62,10 @@ static char *version_str = "V1.1.1"; /** Formatting buffer size */ #define QLA_STRING_BUFFER_SIZE 1024 +/** Log file settings flags */ +#define CONFIG_FILE_SESSION (1 << 0) // Default value, session specific files +#define CONFIG_FILE_UNIFIED (1 << 1) // One file shared by all sessions + /* * The filter entry points */ @@ -87,6 +91,7 @@ static FILTER_OBJECT MyObject = NULL, // No client reply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** @@ -107,6 +112,10 @@ typedef struct regex_t re; /* Compiled regex text */ char *nomatch; /* Optional text to match against for exclusion */ regex_t nore; /* Compiled regex nomatch text */ + uint32_t log_mode_flags; /* Log file mode settings */ + FILE *unified_fp; /* Unified log file. The pointer needs to be shared here + * to avoid garbled printing. */ + bool flush_writes; /* Flush log file after every write */ } QLA_INSTANCE; /** @@ -125,6 +134,7 @@ typedef struct int active; char *user; char *remote; + size_t ses_id; /* The session this filter serves */ } QLA_SESSION; /** @@ -186,6 +196,9 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) my_instance->match = NULL; my_instance->nomatch = NULL; my_instance->filebase = NULL; + my_instance->log_mode_flags = 0; + my_instance->unified_fp = NULL; + my_instance->flush_writes = false; bool error = false; if (params) @@ -239,6 +252,18 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) { cflags |= REG_EXTENDED; } + else if (!strcasecmp(options[i], "session_file")) + { + my_instance->log_mode_flags |= CONFIG_FILE_SESSION; + } + else if (!strcasecmp(options[i], "unified_file")) + { + my_instance->log_mode_flags |= CONFIG_FILE_UNIFIED; + } + else if (!strcasecmp(options[i], "flush_writes")) + { + my_instance->flush_writes = true; + } else { MXS_ERROR("qlafilter: Unsupported option '%s'.", @@ -247,7 +272,11 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) } } } - + if (my_instance->log_mode_flags == 0) + { + // If nothing has been set, set a default value + my_instance->log_mode_flags = CONFIG_FILE_SESSION; + } if (my_instance->filebase == NULL) { MXS_ERROR("qlafilter: No 'filebase' parameter defined."); @@ -275,6 +304,35 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) my_instance->nomatch = NULL; error = true; } + // Try to open the unified log file + if (my_instance->log_mode_flags & CONFIG_FILE_UNIFIED && + my_instance->filebase != NULL) + { + // First calculate filename length + const char UNIFIED[] = ".unified"; + int namelen = strlen(my_instance->filebase) + sizeof(UNIFIED); + char *filename = NULL; + if ((filename = MXS_CALLOC(namelen, sizeof(char))) != NULL) + { + snprintf(filename, namelen, "%s.unified", my_instance->filebase); + // Open the file. It is only closed at program exit + my_instance->unified_fp = fopen(filename, "w"); + if (my_instance->unified_fp == NULL) + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Opening output file for qla " + "filter failed due to %d, %s", + errno, + strerror_r(errno, errbuf, sizeof(errbuf))); + error = true; + } + MXS_FREE(filename); + } + else + { + error = true; + } + } if (error) { @@ -289,6 +347,10 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) MXS_FREE(my_instance->nomatch); regfree(&my_instance->nore); } + if (my_instance->unified_fp != NULL) + { + fclose(my_instance->unified_fp); + } MXS_FREE(my_instance->filebase); MXS_FREE(my_instance->source); MXS_FREE(my_instance->userName); @@ -338,15 +400,17 @@ newSession(FILTER *instance, SESSION *session) my_session->user = userName; my_session->remote = remote; + my_session->ses_id = session->ses_id; - sprintf(my_session->filename, "%s.%d", + sprintf(my_session->filename, "%s.%lu", my_instance->filebase, - my_instance->sessions); + my_session->ses_id); // Fixed possible race condition // Multiple sessions can try to update my_instance->sessions simultaneously atomic_add(&(my_instance->sessions), 1); - if (my_session->active) + // Only open the session file if the corresponding mode setting is used + if (my_session->active && (my_instance->log_mode_flags | CONFIG_FILE_SESSION)) { my_session->fp = fopen(my_session->filename, "w"); @@ -354,7 +418,7 @@ newSession(FILTER *instance, SESSION *session) { char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Opening output file for qla " - "fileter failed due to %d, %s", + "filter failed due to %d, %s", errno, strerror_r(errno, errbuf, sizeof(errbuf))); MXS_FREE(my_session->filename); @@ -363,14 +427,6 @@ newSession(FILTER *instance, SESSION *session) } } } - else - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Memory allocation for qla filter failed due to " - "%d, %s.", - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - } return my_session; } @@ -458,8 +514,31 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) gettimeofday(&tv, NULL); localtime_r(&tv.tv_sec, &t); strftime(buffer, sizeof(buffer), "%F %T", &t); - fprintf(my_session->fp, "%s,%s@%s,%s\n", buffer, my_session->user, - my_session->remote, trim(squeeze_whitespace(ptr))); + + /** + * Loop over all the possible log file modes and write to + * the enabled files. + */ + char *sql_string = trim(squeeze_whitespace(ptr)); + if (my_instance->log_mode_flags & CONFIG_FILE_SESSION) + { + fprintf(my_session->fp, "%s,%s@%s,%s\n", buffer, my_session->user, + my_session->remote, sql_string); + if (my_instance->flush_writes) + { + fflush(my_session->fp); + } + } + if (my_instance->log_mode_flags & CONFIG_FILE_UNIFIED) + { + fprintf(my_instance->unified_fp, "S%zd,%s,%s@%s,%s\n", + my_session->ses_id, buffer, my_session->user, + my_session->remote, sql_string); + if (my_instance->flush_writes) + { + fflush(my_instance->unified_fp); + } + } } MXS_FREE(ptr); } diff --git a/server/modules/filter/regexfilter/regexfilter.c b/server/modules/filter/regexfilter/regexfilter.c index 21100fa55..8382ca497 100644 --- a/server/modules/filter/regexfilter/regexfilter.c +++ b/server/modules/filter/regexfilter/regexfilter.c @@ -71,9 +71,10 @@ static FILTER_OBJECT MyObject = setDownstream, NULL, // No Upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/tee/tee.c b/server/modules/filter/tee/tee.c index c3dd9db19..d979f2e47 100644 --- a/server/modules/filter/tee/tee.c +++ b/server/modules/filter/tee/tee.c @@ -130,6 +130,7 @@ static FILTER_OBJECT MyObject = clientReply, diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/testfilter/testfilter.c b/server/modules/filter/testfilter/testfilter.c index 5c3cb7739..fb9a451cb 100644 --- a/server/modules/filter/testfilter/testfilter.c +++ b/server/modules/filter/testfilter/testfilter.c @@ -37,7 +37,7 @@ MODULE_INFO info = "A simple query counting filter" }; -static char *version_str = "V1.0.0"; +static char *version_str = "V2.0.0"; static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **params); static void *newSession(FILTER *instance, SESSION *session); @@ -47,6 +47,7 @@ static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *down static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); static uint64_t getCapabilities(void); +static void destroyInstance(FILTER *instance); static FILTER_OBJECT MyObject = @@ -56,11 +57,12 @@ static FILTER_OBJECT MyObject = closeSession, freeSession, setDownstream, - NULL, // No upstream requirement + NULL, // No upstream requirement routeQuery, - NULL, + NULL, // No clientReply diagnostic, getCapabilities, + destroyInstance, }; /** @@ -68,7 +70,8 @@ static FILTER_OBJECT MyObject = */ typedef struct { - int sessions; + const char *name; + int sessions; } TEST_INSTANCE; /** @@ -135,6 +138,7 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) if ((my_instance = MXS_CALLOC(1, sizeof(TEST_INSTANCE))) != NULL) { my_instance->sessions = 0; + my_instance->name = name; } return (FILTER *)my_instance; } @@ -258,3 +262,15 @@ static uint64_t getCapabilities(void) { return RCAP_TYPE_NONE; } + +/** + * destroyInstance routine. + * + * @param The filter instance. + */ +static void destroyInstance(FILTER *instance) +{ + TEST_INSTANCE *cinstance = (TEST_INSTANCE *)instance; + + MXS_INFO("Destroying filter %s", cinstance->name); +} diff --git a/server/modules/filter/topfilter/topfilter.c b/server/modules/filter/topfilter/topfilter.c index 9b5d38e4f..a423d2a19 100644 --- a/server/modules/filter/topfilter/topfilter.c +++ b/server/modules/filter/topfilter/topfilter.c @@ -82,6 +82,7 @@ static FILTER_OBJECT MyObject = clientReply, diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/filter/tpmfilter/tpmfilter.c b/server/modules/filter/tpmfilter/tpmfilter.c index da2c42811..0dd59002f 100644 --- a/server/modules/filter/tpmfilter/tpmfilter.c +++ b/server/modules/filter/tpmfilter/tpmfilter.c @@ -95,6 +95,7 @@ static FILTER_OBJECT MyObject = clientReply, diagnostic, getCapabilities, + NULL, // No destroyInstance }; /** diff --git a/server/modules/include/debugcli.h b/server/modules/include/debugcli.h index a32f47374..0e384c603 100644 --- a/server/modules/include/debugcli.h +++ b/server/modules/include/debugcli.h @@ -55,7 +55,7 @@ typedef struct cli_instance * The CLI_SESSION structure. As CLI_SESSION is created for each user that logs into * the DEBUG CLI. */ -enum { CMDBUFLEN = 80 }; +#define CMDBUFLEN 2048 typedef struct cli_session { diff --git a/server/modules/monitor/galeramon/galeramon.c b/server/modules/monitor/galeramon/galeramon.c index c3b96a68e..db28a6123 100644 --- a/server/modules/monitor/galeramon/galeramon.c +++ b/server/modules/monitor/galeramon/galeramon.c @@ -681,11 +681,15 @@ static MONITOR_SERVERS *get_candidate_master(MONITOR* mon) if (handle->use_priority && (value = serverGetParameter(moitor_servers->server, "priority")) != NULL) { - currval = atoi(value); - if (currval < minval && currval > 0) + /** The server has a priority */ + if ((currval = atoi(value)) > 0) { - minval = currval; - candidate_master = moitor_servers; + /** The priority is valid */ + if (currval < minval && currval > 0) + { + minval = currval; + candidate_master = moitor_servers; + } } } else if (moitor_servers->server->node_id >= 0 && diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index 86967962d..eafd1c251 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -215,7 +215,7 @@ bool init_server_info(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) while (database) { /** Delete any existing structures and replace them with empty ones */ - hashtable_delete(handle->server_info, database->server); + hashtable_delete(handle->server_info, database->server->unique_name); if (!hashtable_add(handle->server_info, database->server->unique_name, &info)) { @@ -270,7 +270,6 @@ startMonitor(MONITOR *monitor, const CONFIG_PARAMETER* params) handle->replicationHeartbeat = 0; handle->detectStaleMaster = true; handle->detectStaleSlave = true; - handle->master = NULL; handle->script = NULL; handle->multimaster = false; handle->mysql51_replication = false; @@ -281,6 +280,9 @@ startMonitor(MONITOR *monitor, const CONFIG_PARAMETER* params) spinlock_init(&handle->lock); } + /** This should always be reset to NULL */ + handle->master = NULL; + while (params) { if (!strcmp(params->name, "detect_stale_master")) @@ -688,20 +690,9 @@ monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) MYSQL_MONITOR* handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; - char *uname = mon->user; unsigned long int server_version = 0; char *server_string; - if (database->server->monuser != NULL) - { - uname = database->server->monuser; - } - - if (uname == NULL) - { - return; - } - /* Don't probe servers in maintenance mode */ if (SERVER_IN_MAINT(database->server)) { diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 17f52cb37..89de78cc5 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -402,9 +402,6 @@ gw_do_connect_to_backend(char *host, int port, int *fd) MXS_DEBUG("%lu [gw_do_connect_to_backend] Connected to backend server " "%s:%d, fd %d.", pthread_self(), host, port, so); -#if defined(FAKE_CODE) - conn_open[so] = true; -#endif /* FAKE_CODE */ return_rv: return rv; @@ -759,6 +756,41 @@ gw_read_and_write(DCB *dcb) } } + MySQLProtocol *proto = (MySQLProtocol *)dcb->protocol; + + spinlock_acquire(&dcb->authlock); + + if (proto->ignore_reply) + { + + /** The reply to a COM_CHANGE_USER is in packet */ + GWBUF *query = proto->stored_query; + uint8_t result = *((uint8_t*)GWBUF_DATA(read_buffer) + 4); + proto->stored_query = NULL; + proto->ignore_reply = false; + gwbuf_free(read_buffer); + + spinlock_release(&dcb->authlock); + + int rval = 0; + + if (result == MYSQL_REPLY_OK) + { + rval = query ? dcb->func.write(dcb, query) : 1; + } + else if (query) + { + /** The COM_CHANGE USER failed, generate a fake hangup event to + * close the DCB and send an error to the client. */ + gwbuf_free(query); + poll_fake_hangup_event(dcb); + } + + return rval; + } + + spinlock_release(&dcb->authlock); + /** * If protocol has session command set, concatenate whole * response into one buffer. @@ -911,6 +943,49 @@ static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) CHK_DCB(dcb); spinlock_acquire(&dcb->authlock); + + if (dcb->was_persistent && dcb->state == DCB_STATE_POLLING) + { + ss_dassert(dcb->persistentstart == 0); + /** + * This is a DCB that was just taken out of the persistent connection pool. + * We need to sent a COM_CHANGE_USER query to the backend to reset the + * session state. + */ + if (backend_protocol->stored_query) + { + /** It is possible that the client DCB is closed before the COM_CHANGE_USER + * response is received. */ + gwbuf_free(backend_protocol->stored_query); + } + dcb->was_persistent = false; + backend_protocol->ignore_reply = true; + backend_protocol->stored_query = queue; + + spinlock_release(&dcb->authlock); + + GWBUF *buf = gw_create_change_user_packet(dcb->session->client_dcb->data, dcb->protocol); + return dcb_write(dcb, buf) ? 1 : 0; + } + else if (backend_protocol->ignore_reply) + { + if (MYSQL_IS_COM_QUIT((uint8_t*)GWBUF_DATA(queue))) + { + gwbuf_free(queue); + } + else + { + /** + * We're still waiting on the reply to the COM_CHANGE_USER, append the + * buffer to the stored query. This is possible if the client sends + * BLOB data on the first command. + */ + backend_protocol->stored_query = gwbuf_append(backend_protocol->stored_query, queue); + } + spinlock_release(&dcb->authlock); + return 1; + } + /** * Pick action according to state of protocol. * If auth failed, return value is 0, write and buffered write diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index bd471a26a..5104720be 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -102,6 +102,7 @@ MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd) p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; p->protocol_command.scom_nresponse_packets = 0; p->protocol_command.scom_nbytes_to_read = 0; + p->stored_query = NULL; #if defined(SS_DEBUG) p->protocol_chk_top = CHK_NUM_PROTOCOL; p->protocol_chk_tail = CHK_NUM_PROTOCOL; @@ -145,6 +146,9 @@ void mysql_protocol_done(DCB* dcb) MXS_FREE(scmd); scmd = scmd2; } + + gwbuf_free(p->stored_query); + p->protocol_state = MYSQL_PROTOCOL_DONE; retblock: diff --git a/server/modules/routing/avro/avro.c b/server/modules/routing/avro/avro.c index 54f256d3c..73a361525 100644 --- a/server/modules/routing/avro/avro.c +++ b/server/modules/routing/avro/avro.c @@ -105,7 +105,8 @@ static ROUTER_OBJECT MyObject = diagnostics, clientReply, errorReply, - getCapabilities + getCapabilities, + NULL }; static SPINLOCK instlock; @@ -223,14 +224,22 @@ bool create_tables(sqlite3* handle) return true; } -static void add_conversion_task(AVRO_INSTANCE *inst) +static bool add_conversion_task(AVRO_INSTANCE *inst) { char tasknm[strlen(avro_task_name) + strlen(inst->service->name) + 2]; snprintf(tasknm, sizeof(tasknm), "%s-%s", inst->service->name, avro_task_name); + if (inst->service->svc_do_shutdown) + { + MXS_INFO("AVRO converter task is not added due to MaxScale shutdown"); + return false; + } + MXS_INFO("Setting task for converter_func"); if (hktask_oneshot(tasknm, converter_func, inst, inst->task_delay) == 0) { MXS_ERROR("Failed to add binlog to Avro conversion task to housekeeper."); + return false; } + return true; } /** @@ -1006,7 +1015,8 @@ void converter_func(void* data) AVRO_INSTANCE* router = (AVRO_INSTANCE*) data; bool ok = true; avro_binlog_end_t binlog_end = AVRO_OK; - while (ok && binlog_end == AVRO_OK) + + while (!router->service->svc_do_shutdown && ok && binlog_end == AVRO_OK) { uint64_t start_pos = router->current_pos; if (avro_open_binlog(router->binlogdir, router->binlog_name, &router->binlog_fd)) @@ -1037,10 +1047,12 @@ void converter_func(void* data) if (binlog_end == AVRO_LAST_FILE) { router->task_delay = MXS_MIN(router->task_delay + 1, AVRO_TASK_DELAY_MAX); - add_conversion_task(router); - MXS_INFO("Stopped processing file %s at position %lu. Waiting until" - " more data is written before continuing. Next check in %d seconds.", - router->binlog_name, router->current_pos, router->task_delay); + if (add_conversion_task(router)) + { + MXS_INFO("Stopped processing file %s at position %lu. Waiting until" + " more data is written before continuing. Next check in %d seconds.", + router->binlog_name, router->current_pos, router->task_delay); + } } } diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 4248fd47c..341e1c6dd 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -51,6 +51,7 @@ * 11/07/2016 Massimiliano Pinto Added SSL backend support * 22/07/2016 Massimiliano Pinto Added semi_sync replication support * 16/08/2016 Massimiliano Pinto Addition of Start Encription Event description + * 08/11/2016 Massimiliano Pinto Added destroyInstance() * * @endverbatim */ @@ -109,6 +110,7 @@ static int blr_check_binlog(ROUTER_INSTANCE *router); int blr_read_events_all_events(ROUTER_INSTANCE *router, int fix, int debug); void blr_master_close(ROUTER_INSTANCE *); void blr_free_ssl_data(ROUTER_INSTANCE *inst); +static void destroyInstance(ROUTER *instance); /** The module object definition */ static ROUTER_OBJECT MyObject = @@ -121,7 +123,8 @@ static ROUTER_OBJECT MyObject = diagnostics, clientReply, errorReply, - getCapabilities + getCapabilities, + destroyInstance }; static void stats_func(void *); @@ -583,7 +586,8 @@ createInstance(SERVICE *service, char **options) { SERVER *server; SSL_LISTENER *ssl_cfg; - server = server_alloc("_none_", "MySQLBackend", 3306, "MySQLBackendAuth", NULL); + server = server_alloc("binlog_router_master_host", "_none_", 3306, + "MySQLBackend", "MySQLBackendAuth", NULL); if (server == NULL) { MXS_ERROR("%s: Error for server_alloc in createInstance", @@ -615,7 +619,6 @@ createInstance(SERVICE *service, char **options) server->server_ssl = ssl_cfg; /* Set server unique name */ - server_set_unique_name(server, "binlog_router_master_host"); /* Add server to service backend list */ serviceAddBackend(inst->service, server); } @@ -2293,3 +2296,65 @@ blr_free_ssl_data(ROUTER_INSTANCE *inst) inst->service->dbref->server->server_ssl = NULL; } } + +/** + * destroy binlog server instance + * + * @param service The service this router instance belongs to + */ +static void +destroyInstance(ROUTER *instance) +{ + ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; + + MXS_DEBUG("Destroying instance of router %s for service %s", + inst->service->routerModule, inst->service->name); + + /* Check whether master connection is active */ + if (inst->master) + { + if (inst->master->fd != -1 && inst->master->state == DCB_STATE_POLLING) + { + blr_master_close(inst); + } + } + + spinlock_acquire(&inst->lock); + + if (inst->master_state != BLRM_UNCONFIGURED) + { + inst->master_state = BLRM_SLAVE_STOPPED; + } + + if (inst->client) + { + if (inst->client->state == DCB_STATE_POLLING) + { + dcb_close(inst->client); + inst->client = NULL; + } + } + + /* Discard the queued residual data */ + while (inst->residual) + { + inst->residual = gwbuf_consume(inst->residual, GWBUF_LENGTH(inst->residual)); + } + inst->residual = NULL; + + MXS_INFO("%s is being stopped by MaxScale shudown. Disconnecting from master %s:%d, " + "read up to log %s, pos %lu, transaction safe pos %lu", + inst->service->name, + inst->service->dbref->server->name, + inst->service->dbref->server->port, + inst->binlog_name, inst->current_pos, inst->binlog_position); + + if (inst->trx_safe && inst->pending_transaction) + { + MXS_WARNING("%s stopped by shutdown: detected mid-transaction in binlog file %s, " + "pos %lu, incomplete transaction starts at pos %lu", + inst->service->name, inst->binlog_name, inst->current_pos, inst->binlog_position); + } + + spinlock_release(&inst->lock); +} diff --git a/server/modules/routing/binlog/test/testbinlog.c b/server/modules/routing/binlog/test/testbinlog.c index 2bb630e54..2eda6e461 100644 --- a/server/modules/routing/binlog/test/testbinlog.c +++ b/server/modules/routing/binlog/test/testbinlog.c @@ -103,12 +103,12 @@ int main(int argc, char **argv) { s = strtok_r(NULL, ",", &lasts); } set_libdir(MXS_STRDUP_A("../../../authenticator/")); - server = server_alloc("_none_", "MySQLBackend", 3306, "MySQLBackendAuth", NULL); + server = server_alloc("binlog_router_master_host", "_none_", 3306, + "MySQLBackend", "MySQLBackendAuth", NULL); if (server == NULL) { return 1; } - server_set_unique_name(server, "binlog_router_master_host"); serviceAddBackend(service, server); } diff --git a/server/modules/routing/cli/cli.c b/server/modules/routing/cli/cli.c index ee263009e..478a2dcdf 100644 --- a/server/modules/routing/cli/cli.c +++ b/server/modules/routing/cli/cli.c @@ -71,7 +71,8 @@ static ROUTER_OBJECT MyObject = diagnostics, NULL, NULL, - getCapabilities + getCapabilities, + NULL }; extern int execute_cmd(CLI_SESSION *cli); diff --git a/server/modules/routing/debugcli/debugcli.c b/server/modules/routing/debugcli/debugcli.c index 962fbe1b4..1f86c82fc 100644 --- a/server/modules/routing/debugcli/debugcli.c +++ b/server/modules/routing/debugcli/debugcli.c @@ -70,7 +70,8 @@ static ROUTER_OBJECT MyObject = diagnostics, NULL, NULL, - getCapabilities + getCapabilities, + NULL }; extern int execute_cmd(CLI_SESSION *cli); diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 950f0882a..eff2e3bef 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -70,12 +70,14 @@ #include #include #include +#include #include #include -#define MAXARGS 6 +#define MAXARGS 12 +#define ARG_TYPE_NONE 0 #define ARG_TYPE_ADDRESS 1 #define ARG_TYPE_STRING 2 #define ARG_TYPE_SERVICE 3 @@ -94,227 +96,302 @@ extern LIST_CONFIG SESSIONlist; * * These are the options that may be passed to a command */ -struct subcommand { +struct subcommand +{ char *arg1; - int n_args; + int argc_min; + int argc_max; void (*fn)(); char *help; char *devhelp; - int arg_types[3]; + int arg_types[MAXARGS]; }; +#define EMPTY_OPTION + static void telnetdShowUsers(DCB *); static void show_log_throttling(DCB *); /** * The subcommands of the show command */ -struct subcommand showoptions[] = { +struct subcommand showoptions[] = +{ #if defined(BUFFER_TRACE) - { "buffers", 0, dprintAllBuffers, - "Show all buffers with backtrace", - "Show all buffers with backtrace", - {0, 0, 0} }, + { + "buffers", 0, dprintAllBuffers, + "Show all buffers with backtrace", + "Show all buffers with backtrace", + {0, 0, 0} + }, #endif - { "dcblist", 0, dprintDCBList, - "Show statistics for the list of all descriptor control blocks", - "Show statistics for the list of all descriptor control blocks", - {0, 0, 0} }, - { "dcbs", 0, dprintAllDCBs, - "Show all descriptor control blocks (network connections)", - "Show all descriptor control blocks (network connections)", - {0, 0, 0} }, - { "dcb", 1, dprintDCB, - "Show a single descriptor control block e.g. show dcb 0x493340", - "Show a single descriptor control block e.g. show dcb 0x493340", - {ARG_TYPE_DCB, 0, 0} }, - { "dbusers", 1, dcb_usersPrint, - "Show statistics and user names for a service's user table.\n" - "\t\tExample : show dbusers ", - "Show statistics and user names for a service's user table.\n" - "\t\tExample : show dbusers |", - {ARG_TYPE_DBUSERS, 0, 0} }, - { "epoll", 0, dprintPollStats, - "Show the poll statistics", - "Show the poll statistics", - {0, 0, 0} }, - { "eventq", 0, dShowEventQ, - "Show the queue of events waiting to be processed", - "Show the queue of events waiting to be processed", - {0, 0, 0} }, - { "eventstats", 0, dShowEventStats, - "Show the event statistics", - "Show the event statistics", - {0, 0, 0} }, - { "feedbackreport", 0, moduleShowFeedbackReport, - "Show the report of MaxScale loaded modules, suitable for Notification Service", - "Show the report of MaxScale loaded modules, suitable for Notification Service", - {0, 0, 0} }, - { "filter", 1, dprintFilter, - "Show details of a filter, called with a filter name", - "Show details of a filter, called with the address of a filter", - {ARG_TYPE_FILTER, 0, 0} }, - { "filters", 0, dprintAllFilters, - "Show all filters", - "Show all filters", - {0, 0, 0} }, - { "log_throttling", 0, show_log_throttling, - "Show the current log throttling setting (count, window (ms), suppression (ms))", - "Show the current log throttling setting (count, window (ms), suppression (ms))", - {0, 0, 0} }, - { "modules", 0, dprintAllModules, - "Show all currently loaded modules", - "Show all currently loaded modules", - {0, 0, 0} }, - { "monitor", 1, monitorShow, - "Show the monitor details", - "Show the monitor details", - {ARG_TYPE_MONITOR, 0, 0} }, - { "monitors", 0, monitorShowAll, - "Show the monitors that are configured", - "Show the monitors that are configured", - {0, 0, 0} }, - { "persistent", 1, dprintPersistentDCBs, - "Show persistent pool for a named server, e.g. show persistent dbnode1", - "Show persistent pool for a server, e.g. show persistent 0x485390. " - "The address may also be replaced with the server name from the configuration file", - {ARG_TYPE_SERVER, 0, 0} }, - { "server", 1, dprintServer, - "Show details for a named server, e.g. show server dbnode1", - "Show details for a server, e.g. show server 0x485390. The address may also be " - "repalced with the server name from the configuration file", - {ARG_TYPE_SERVER, 0, 0} }, - { "servers", 0, dprintAllServers, - "Show all configured servers", - "Show all configured servers", - {0, 0, 0} }, - { "serversjson", 0, dprintAllServersJson, - "Show all configured servers in JSON format", - "Show all configured servers in JSON format", - {0, 0, 0} }, - { "services", 0, dprintAllServices, - "Show all configured services in MaxScale", - "Show all configured services in MaxScale", - {0, 0, 0} }, - { "service", 1, dprintService, - "Show a single service in MaxScale, may be passed a service name", - "Show a single service in MaxScale, may be passed a service name or address of a service object", - {ARG_TYPE_SERVICE, 0, 0} }, - { "session", 1, dprintSession, - "Show a single session in MaxScale, e.g. show session 0x284830", - "Show a single session in MaxScale, e.g. show session 0x284830", - {ARG_TYPE_SESSION, 0, 0} }, - { "sessionlist", 0, dprintSessionList, - "Show statistics for the list of all sessions", - "Show statistics for the list of all sessions", - {0, 0, 0} }, - { "sessions", 0, dprintAllSessions, - "Show all active sessions in MaxScale", - "Show all active sessions in MaxScale", - {0, 0, 0} }, - { "tasks", 0, hkshow_tasks, - "Show all active housekeeper tasks in MaxScale", - "Show all active housekeeper tasks in MaxScale", - {0, 0, 0} }, - { "threads", 0, dShowThreads, - "Show the status of the polling threads in MaxScale", - "Show the status of the polling threads in MaxScale", - {0, 0, 0} }, - { "users", 0, telnetdShowUsers, - "Show all maxadmin enabled Linux accounts and created maxadmin users", - "Show all maxadmin enabled Linux accounts and created maxadmin users", - {0, 0, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } + { + "dcblist", 0, 0, dprintDCBList, + "Show DCB statistics", + "Show statistics for the list of all DCBs(descriptor control blocks)", + {0} + }, + { + "dcbs", 0, 0, dprintAllDCBs, + "Show all DCBs", + "Show all descriptor control blocks (network connections)", + {0} + }, + { + "dcb", 1, 1, dprintDCB, + "Show a DCB", + "Show a single descriptor control block e.g. show dcb 0x493340", + {ARG_TYPE_DCB, 0, 0} + }, + { + "dbusers", 1, 1, dcb_usersPrint, + "Show user statistics", + "Show statistics and user names for a service's user table.\n" + "\t\tExample : show dbusers |", + {ARG_TYPE_DBUSERS, 0, 0} + }, + { + "epoll", 0, 0, dprintPollStats, + "Show the poll statistics", + "Show the epoll polling system statistics", + {0, 0, 0} + }, + { + "eventq", 0, 0, dShowEventQ, + "Show event queue", + "Show the queue of events waiting to be processed", + {0, 0, 0} + }, + { + "eventstats", 0, 0, dShowEventStats, + "Show event queue statistics", + "Show event queue statistics", + {0, 0, 0} + }, + { + "feedbackreport", 0, 0, moduleShowFeedbackReport, + "Show feedback report", + "Show the report of MaxScale loaded modules, suitable for Notification Service", + {0, 0, 0} + }, + { + "filter", 1, 1, dprintFilter, + "Show filter details", + "Show details of a filter, the parameter is filter name", + {ARG_TYPE_FILTER, 0, 0} + }, + { + "filters", 0, 0, dprintAllFilters, + "Show all filters", + "Show all filters that were read from the configuration file", + {0, 0, 0} + }, + { + "log_throttling", 0, 0, show_log_throttling, + "Show log throttling setting", + "Show the current log throttling setting (count, window (ms), suppression (ms))", + {0, 0, 0} + }, + { + "modules", 0, 0, dprintAllModules, + "Show loaded modules", + "Show all currently loaded modules", + {0, 0, 0} + }, + { + "monitor", 1, 1, monitorShow, + "Show monitor details", + "Show details about a specific monitor, the parameter is monitor name", + {ARG_TYPE_MONITOR, 0, 0} + }, + { + "monitors", 0, 0, monitorShowAll, + "Show all monitors", + "Show all the monitors", + {0, 0, 0} + }, + { + "persistent", 1, 1, dprintPersistentDCBs, + "Show persistent connection pool", + "Show persistent pool for a server, e.g. show persistent dbnode1. ", + {ARG_TYPE_SERVER, 0, 0} + }, + { + "server", 1, 1, dprintServer, + "Show server details", + "Show details for a server, e.g. show server dbnode1", + {ARG_TYPE_SERVER, 0, 0} + }, + { + "servers", 0, 0, dprintAllServers, + "Show all servers", + "Show all configured servers", + {0, 0, 0} + }, + { + "serversjson", 0, 0, dprintAllServersJson, + "Show all servers in JSON", + "Show all configured servers in JSON format", + {0, 0, 0} + }, + { + "services", 0, 0, dprintAllServices, + "Show all service", + "Show all configured services in MaxScale", + {0, 0, 0} + }, + { + "service", 1, 1, dprintService, + "Show service details", + "Show a single service in MaxScale, the parameter is the service name", + {ARG_TYPE_SERVICE, 0, 0} + }, + { + "session", 1, 1, dprintSession, + "Show session details", + "Show a single session in MaxScale, e.g. show session 0x284830", + {ARG_TYPE_SESSION, 0, 0} + }, + { + "sessionlist", 0, 0, dprintSessionList, + "Show session list statistics", + "Show statistics for the list of all sessions", + {0, 0, 0} + }, + { + "sessions", 0, 0, dprintAllSessions, + "Show all sessions", + "Show all active sessions in MaxScale", + {0, 0, 0} + }, + { + "tasks", 0, 0, hkshow_tasks, + "Show housekeeper tasks", + "Show all active housekeeper tasks in MaxScale", + {0, 0, 0} + }, + { + "threads", 0, 0, dShowThreads, + "Show workter thread status", + "Show the status of the worker threads in MaxScale", + {0, 0, 0} + }, + { + "users", 0, 0, telnetdShowUsers, + "Show enabled Linux accounts", + "Show all maxadmin enabled Linux accounts and created maxadmin users", + {0, 0, 0} + }, + { EMPTY_OPTION} }; /** * The subcommands of the list command */ -struct subcommand listoptions[] = { - { "clients", 0, dListClients, - "List all the client connections to MaxScale", - "List all the client connections to MaxScale", - {0, 0, 0} }, - { "dcbs", 0, dListDCBs, - "List all the DCBs active within MaxScale", - "List all the DCBs active within MaxScale", - {0, 0, 0} }, - { "filters", 0, dListFilters, - "List all the filters defined within MaxScale", - "List all the filters defined within MaxScale", - {0, 0, 0} }, - { "listeners", 0, dListListeners, - "List all the listeners defined within MaxScale", - "List all the listeners defined within MaxScale", - {0, 0, 0} }, - { "modules", 0, dprintAllModules, - "List all currently loaded modules", - "List all currently loaded modules", - {0, 0, 0} }, - { "monitors", 0, monitorList, - "List all monitors", - "List all monitors", - {0, 0, 0} }, - { "services", 0, dListServices, - "List all the services defined within MaxScale", - "List all the services defined within MaxScale", - {0, 0, 0} }, - { "servers", 0, dListServers, - "List all the servers defined within MaxScale", - "List all the servers defined within MaxScale", - {0, 0, 0} }, - { "sessions", 0, dListSessions, - "List all the active sessions within MaxScale", - "List all the active sessions within MaxScale", - {0, 0, 0} }, - { "threads", 0, dShowThreads, - "List the status of the polling threads in MaxScale", - "List the status of the polling threads in MaxScale", - {0, 0, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand listoptions[] = +{ + { + "clients", 0, 0, dListClients, + "List all clients", + "List all the client connections to MaxScale", + {0, 0, 0} + }, + { + "dcbs", 0, 0, dListDCBs, + "List all DCBs", + "List all the DCBs active within MaxScale", + {0, 0, 0} + }, + { + "filters", 0, 0, dListFilters, + "List all filters", + "List all the filters defined within MaxScale", + {0, 0, 0} + }, + { + "listeners", 0, 0, dListListeners, + "List all listeners", + "List all the listeners defined within MaxScale", + {0, 0, 0} + }, + { + "modules", 0, 0, dprintAllModules, + "List all currently loaded modules", + "List all currently loaded modules", + {0, 0, 0} + }, + { + "monitors", 0, 0, monitorList, + "List all monitors", + "List all monitors", + {0, 0, 0} + }, + { + "services", 0, 0, dListServices, + "List all the services", + "List all the services defined within MaxScale", + {0, 0, 0} + }, + { + "servers", 0, 0, dListServers, + "List all servers", + "List all the servers defined within MaxScale", + {0, 0, 0} + }, + { + "sessions", 0, 0, dListSessions, + "List all sessions", + "List all the active sessions within MaxScale", + {0, 0, 0} + }, + { + "threads", 0, 0, dShowThreads, + "List polling threads", + "List the status of the polling threads in MaxScale", + {0, 0, 0} + }, + { EMPTY_OPTION} }; -extern void shutdown_server(); +static void shutdown_server() +{ + maxscale_shutdown(); +} + static void shutdown_service(DCB *dcb, SERVICE *service); static void shutdown_monitor(DCB *dcb, MONITOR *monitor); /** * The subcommands of the shutdown command */ -struct subcommand shutdownoptions[] = { +struct subcommand shutdownoptions[] = +{ { "maxscale", - 0, + 0, 0, shutdown_server, "Shutdown MaxScale", - "Shutdown MaxScale", + "Initiate a controlled shutdown of MaxScale", {0, 0, 0} }, { "monitor", - 1, + 1, 1, shutdown_monitor, - "Shutdown a monitor, e.g. shutdown monitor 0x48381e0", - "Shutdown a monitor, e.g. shutdown monitor 0x48381e0", + "Shutdown a monitor", + "E.g. shutdown monitor db-cluster-monitor", {ARG_TYPE_MONITOR, 0, 0} }, { "service", - 1, + 1, 1, shutdown_service, - "Shutdown a service, e.g. shutdown service \"Sales Database\"", - "Shutdown a service, e.g. shutdown service 0x4838320 or shutdown service \"Sales Database\"", + "Stop a service", + "E.g. shutdown service \"Sales Database\"", {ARG_TYPE_SERVICE, 0, 0} }, { - NULL, - 0, - NULL, - NULL, - NULL, - {0, 0, 0} + EMPTY_OPTION } }; @@ -327,7 +404,7 @@ static void sync_logs(DCB *dcb) else { dcb_printf(dcb, "Failed to flush logs to disk. Read the error log for " - "more details.\n"); + "more details.\n"); } } @@ -335,19 +412,14 @@ struct subcommand syncoptions[] = { { "logs", - 0, + 0, 0, sync_logs, "Flush log files to disk", "Flush log files to disk", {0, 0, 0} }, { - NULL, - 0, - NULL, - NULL, - NULL, - {0, 0, 0} + EMPTY_OPTION } }; @@ -356,17 +428,21 @@ static void restart_monitor(DCB *dcb, MONITOR *monitor); /** * The subcommands of the restart command */ -struct subcommand restartoptions[] = { - { "monitor", 1, restart_monitor, - "Restart a monitor, e.g. restart monitor 0x48181e0", - "Restart a monitor, e.g. restart monitor 0x48181e0", - {ARG_TYPE_MONITOR, 0, 0} }, - { "service", 1, restart_service, - "Restart a service, e.g. restart service \"Test Service\"", - "Restart a service, e.g. restart service 0x4838320", - {ARG_TYPE_SERVICE, 0, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand restartoptions[] = +{ + { + "monitor", 1, 1, restart_monitor, + "Restart a monitor", + "E.g. restart monitor db-cluster-monitor", + {ARG_TYPE_MONITOR, 0, 0} + }, + { + "service", 1, 1, restart_service, + "Restart a service", + "E.g. restart service \"Sales Database\"", + {ARG_TYPE_SERVICE, 0, 0} + }, + { EMPTY_OPTION } }; static void set_server(DCB *dcb, SERVER *server, char *bit); @@ -376,38 +452,48 @@ static void set_log_throttling(DCB *dcb, int count, int window_ms, int suppress_ /** * The subcommands of the set command */ -struct subcommand setoptions[] = { - { "server", 2, set_server, - "Set the status of a server. E.g. set server dbnode4 master", - "Set the status of a server. E.g. set server 0x4838320 master", - {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} }, - { "pollsleep", 1, set_pollsleep, - "Set the maximum poll sleep period in milliseconds", - "Set the maximum poll sleep period in milliseconds", - {ARG_TYPE_NUMERIC, 0, 0} }, - { "nbpolls", 1, set_nbpoll, - "Set the number of non-blocking polls", - "Set the number of non-blocking polls", - {ARG_TYPE_NUMERIC, 0, 0} }, - { "log_throttling", 3, set_log_throttling, - "Set the log throttling configuration", - "Set the log throttling configuration", - {ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand setoptions[] = +{ + { + "server", 2, 2, set_server, + "Set the status of a server", + "Set the status of a server. E.g. set server dbnode4 master", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} + }, + { + "pollsleep", 1, 1, set_pollsleep, + "Set poll sleep period", + "Set the maximum poll sleep period in milliseconds", + {ARG_TYPE_NUMERIC, 0, 0} + }, + { + "nbpolls", 1, 1, set_nbpoll, + "Set non-blocking polls", + "Set the number of non-blocking polls", + {ARG_TYPE_NUMERIC, 0, 0} + }, + { + "log_throttling", 3, 3, set_log_throttling, + "Set log throttling", + "Set the log throttling configuration", + {ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC} + }, + { EMPTY_OPTION } }; static void clear_server(DCB *dcb, SERVER *server, char *bit); /** * The subcommands of the clear command */ -struct subcommand clearoptions[] = { - { "server", 2, clear_server, - "Clear the status of a server. E.g. clear server dbnode2 master", - "Clear the status of a server. E.g. clear server 0x4838320 master", - {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand clearoptions[] = +{ + { + "server", 2, 2, clear_server, + "Clear server status", + "Clear the status of a server. E.g. clear server dbnode2 master", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} + }, + { EMPTY_OPTION } }; static void reload_dbusers(DCB *dcb, SERVICE *service); @@ -416,17 +502,21 @@ static void reload_config(DCB *dcb); /** * The subcommands of the reload command */ -struct subcommand reloadoptions[] = { - { "config", 0, reload_config, - "Reload the configuration data for MaxScale.", - "Reload the configuration data for MaxScale.", - {0, 0, 0} }, - { "dbusers", 1, reload_dbusers, - "Reload the dbuser data for a service. E.g. reload dbusers \"splitter service\"", - "Reload the dbuser data for a service. E.g. reload dbusers 0x849420", - {ARG_TYPE_SERVICE, 0, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand reloadoptions[] = +{ + { + "config", 0, 0, reload_config, + "Reload the configuration", + "Reload the configuration data for MaxScale", + {0, 0, 0} + }, + { + "dbusers", 1, 1, reload_dbusers, + "Reload users table", + "Reload the users for a service. E.g. reload dbusers \"splitter service\"", + {ARG_TYPE_SERVICE, 0, 0} + }, + { EMPTY_OPTION } }; static void enable_log_action(DCB *, char *); @@ -453,106 +543,95 @@ static void disable_account(DCB *, char *user); /** * * The subcommands of the enable command * */ -struct subcommand enableoptions[] = { +struct subcommand enableoptions[] = +{ { "heartbeat", - 1, + 1, 1, enable_monitor_replication_heartbeat, - "Enable the monitor replication heartbeat, pass a monitor name as argument", - "Enable the monitor replication heartbeat, pass a monitor name as argument", + "Enable monitor replication heartbeat", + "Enable the monitor replication heartbeat, the parameter is the monitor name", {ARG_TYPE_MONITOR, 0, 0} }, { "log", - 1, + 1, 1, enable_log_action, - "[deprecated] Enable Log options for MaxScale, options 'trace' | 'error' | 'message'." - "E.g. 'enable log message'.", - "[deprecated] Enable Log options for MaxScale, options 'trace' | 'error' | 'message'." - "E.g. 'enable log message'.", + "[deprecated] Enable a logging level", + "Options 'trace' | 'error' | 'message'. E.g. 'enable log message'.", {ARG_TYPE_STRING, 0, 0} }, { "log-priority", - 1, + 1, 1, enable_log_priority, - "Enable a logging priority; options 'err' | 'warning' | 'notice' | 'info' | 'debug'. " - "E.g.: 'enable log-priority info'.", - "Enable a logging priority; options 'err' | 'warning' | 'notice' | 'info' | 'debug'. " + "Enable a logging priority", + "Enable a logging priority for MaxScale, parameters must be one of " + "'err', 'warning', 'notice', 'info' or 'debug'. " "E.g.: 'enable log-priority info'.", {ARG_TYPE_STRING, 0, 0} }, { "sessionlog", - 2, + 2, 2, enable_sess_log_action, - "[deprecated] Enable Log options for a single session. Usage: enable sessionlog [trace | error | " - "message | debug] \t E.g. enable sessionlog message 123.", - "[deprecated] Enable Log options for a single session. Usage: enable sessionlog [trace | error | " + "[deprecated] Enable a logging level for a single session", + "Usage: enable sessionlog [trace | error | " "message | debug] \t E.g. enable sessionlog message 123.", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { "sessionlog-priority", - 2, + 2, 2, enable_sess_log_priority, - "Enable a logging priority for a particular session. " - "Usage: enable sessionlog-priority [err | warning | notice | info | debug] " - "message | debug] \t E.g. enable sessionlog-priority info 123.", - "Enable a logging priority for a particular session. " + "Enable a logging priority for a session", "Usage: enable sessionlog-priority [err | warning | notice | info | debug] " "message | debug] \t E.g. enable sessionlog-priority info 123.", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { "root", - 1, + 1, 1, enable_service_root, - "Enable root access to a service, pass a service name to enable root access", + "Enable root user access", "Enable root access to a service, pass a service name to enable root access", {ARG_TYPE_SERVICE, 0, 0} }, { "feedback", - 0, + 0, 0, enable_feedback_action, - "Enable MaxScale modules list sending via http to notification service", + "Enable MaxScale feedback", "Enable MaxScale modules list sending via http to notification service", {0, 0, 0} }, { "syslog", - 0, + 0, 0, enable_syslog, - "Enable syslog logging", + "Enable syslog", "Enable syslog logging", {0, 0, 0} }, { "maxlog", - 0, + 0, 0, enable_maxlog, - "Enable maxlog logging", - "Enable maxlog logging", + "Enable MaxScale logging", + "Enable MaxScale logging", {0, 0, 0} }, { "account", - 1, + 1, 1, enable_account, - "Enable maxadmin usage for Linux user. E.g.:\n" - " MaxScale> enable account alice", - "Enable maxadmin usage for Linux user. E.g.:\n" + "Activate a Linux user", + "Enable maxadmin usage for a Linux user. E.g.:\n" " MaxScale> enable account alice", {ARG_TYPE_STRING, 0, 0} }, { - NULL, - 0, - NULL, - NULL, - NULL, - {0, 0, 0} + EMPTY_OPTION } }; @@ -561,188 +640,210 @@ struct subcommand enableoptions[] = { /** * * The subcommands of the disable command * */ -struct subcommand disableoptions[] = { +struct subcommand disableoptions[] = +{ { "heartbeat", - 1, + 1, 1, disable_monitor_replication_heartbeat, - "Disable the monitor replication heartbeat", + "Disable replication heartbeat", "Disable the monitor replication heartbeat", {ARG_TYPE_MONITOR, 0, 0} }, { "log", - 1, + 1, 1, disable_log_action, - "[deprecated] Disable Log for MaxScale, Options: 'debug' | 'trace' | 'error' | 'message'." - "E.g. 'disable log debug'.", - "[deprecated] Disable Log for MaxScale, Options: 'debug' | 'trace' | 'error' | 'message'." - "E.g. 'disable log debug'.", + "[deprecated] Disable log for MaxScale", + "Options: 'debug' | 'trace' | 'error' | 'message'." + "E.g. 'disable log debug'", {ARG_TYPE_STRING, 0, 0} }, { "log-priority", - 1, + 1, 1, disable_log_priority, - "Disable a logging priority; options 'err' | 'warning' | 'notice' | 'info' | 'debug'. " - "E.g.: 'disable log-priority info'.", - "Disable a logging priority; options 'err' | 'warning' | 'notice' | 'info' | 'debug'. " - "E.g.: 'disable log-priority info'.", + "Disable a logging priority", + "Options 'err' | 'warning' | 'notice' | 'info' | 'debug'. " + "E.g.: 'disable log-priority info'", {ARG_TYPE_STRING, 0, 0} }, { "sessionlog", - 2, + 2, 2, disable_sess_log_action, - "[deprecated] Disable Log options for a single session. Usage: disable sessionlog [trace | error | " - "message | debug] \t E.g. disable sessionlog message 123.", - "[deprecated] Disable Log options for a single session. Usage: disable sessionlog [trace | error | " - "message | debug] \t E.g. disable sessionlog message 123.", + "[deprecated] Disable log options", + "Disable Log options for a single session. Usage: disable sessionlog [trace | error | " + "message | debug] \t E.g. disable sessionlog message 123", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { "sessionlog-priority", - 2, + 2, 2, disable_sess_log_priority, - "Disable a logging priority for a particular session. " + "Disable a logging priority for a particular session", "Usage: disable sessionlog-priority [err | warning | notice | info | debug] " - "message | debug] \t E.g. enable sessionlog-priority info 123.", - "Enable a logging priority for a particular session. " - "Usage: disable sessionlog-priority [err | warning | notice | info | debug] " - "message | debug] \t E.g. enable sessionlog-priority info 123.", + "message | debug] \t E.g. enable sessionlog-priority info 123", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { "root", - 1, + 1, 1, disable_service_root, - "Disable root access to a service", + "Disable root access", "Disable root access to a service", {ARG_TYPE_SERVICE, 0, 0} }, { "feedback", - 0, + 0, 0, disable_feedback_action, - "Disable MaxScale modules list sending via http to notification service", + "Disable feedback", "Disable MaxScale modules list sending via http to notification service", {0, 0, 0} }, { "syslog", - 0, + 0, 0, disable_syslog, - "Disable syslog logging", + "Disable syslog", "Disable syslog logging", {0, 0, 0} }, { "maxlog", - 0, + 0, 0, disable_maxlog, - "Disable maxlog logging", - "Disable maxlog logging", + "Disable MaxScale logging", + "Disable MaxScale logging", {0, 0, 0} }, { "account", - 1, + 1, 1, disable_account, - "Disable maxadmin usage for Linux user. E.g.:\n" - " MaxScale> disable account alice", + "Disable Linux user", "Disable maxadmin usage for Linux user. E.g.:\n" " MaxScale> disable account alice", {ARG_TYPE_STRING, 0, 0} }, { - NULL, - 0, - NULL, - NULL, - NULL, - {0, 0, 0} + EMPTY_OPTION } }; -#if defined(FAKE_CODE) - -static void fail_backendfd(void); -static void fail_clientfd(void); -static void fail_accept(DCB* dcb, char* arg1, char* arg2); -/** - * * The subcommands of the fail command - * */ -struct subcommand failoptions[] = { - { - "backendfd", - 0, - fail_backendfd, - "Fail backend socket for next operation.", - "Fail backend socket for next operation.", - {ARG_TYPE_STRING, 0, 0} - }, - { - "clientfd", - 0, - fail_clientfd, - "Fail client socket for next operation.", - "Fail client socket for next operation.", - {ARG_TYPE_STRING, 0, 0} - }, - { - "accept", - 2, - fail_accept, - "Fail to accept next client connection.", - "Fail to accept next client connection.", - {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} - }, - { - NULL, - 0, - NULL, - NULL, - NULL, - {0, 0, 0} - } -}; -#endif /* FAKE_CODE */ - static void telnetdAddUser(DCB *, char *user, char *password); +static void cmd_AddServer(DCB *dcb, void *a, void *b) +{ + SERVER *server = (SERVER*)a; + char *name = (char*)b; + + SERVICE *service = service_find(name); + MONITOR *monitor = monitor_find(name); + + if (service || monitor) + { + ss_dassert(service == NULL || monitor == NULL); + + if (service) + { + serviceAddBackend(service, server); + } + else if (monitor) + { + monitorAddServer(monitor, server); + } + + const char *target = service ? "service" : "monitor"; + + MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, target, name); + dcb_printf(dcb, "Added server '%s' to %s '%s'\n", server->unique_name, target, name); + } + else + { + dcb_printf(dcb, "No service or monitor with the name '%s'\n", name); + } +} + /** * The subcommands of the add command */ -struct subcommand addoptions[] = { - { "user", 2, telnetdAddUser, - "Add insecure account for using maxadmin over the network. E.g.:\n" - " MaxScale> add user bob somepass", - "Add insecure account for using maxadmin over the network. E.g.:\n" - " MaxScale> add user bob somepass", - {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, - { NULL, 0, NULL, NULL, NULL, - {0, 0, 0} } +struct subcommand addoptions[] = +{ + { + "user", 2, 2, telnetdAddUser, + "Add account for maxadmin", + "Add insecure account for using maxadmin over the network. E.g.:\n" + " MaxScale> add user bob somepass", + {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} + }, + { + "server", 2, 2, cmd_AddServer, + "Add a new server to a service", + "Usage: add server SERVER TARGET\n" + "The TARGET must be either a service or a monitor", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} + }, + { EMPTY_OPTION} }; static void telnetdRemoveUser(DCB *, char *user, char *password); +static void cmd_RemoveServer(DCB *dcb, void *a, void *b) +{ + SERVER *server = (SERVER*)a; + char *name = (char*)b; + SERVICE *service = service_find(name); + MONITOR *monitor = monitor_find(name); + + if (service || monitor) + { + ss_dassert(service == NULL || monitor == NULL); + + if (service) + { + serviceRemoveBackend(service, server); + } + else if (monitor) + { + monitorRemoveServer(monitor, server); + } + + const char *target = service ? "service" : "monitor"; + MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, target, name); + dcb_printf(dcb, "Removed server '%s' from %s '%s'\n", server->unique_name, target, name); + } + else + { + dcb_printf(dcb, "No service or monitor with the name '%s'\n", name); + } +} + /** * The subcommands of the remove command */ -struct subcommand removeoptions[] = { +struct subcommand removeoptions[] = +{ { "user", - 2, + 2, 2, telnetdRemoveUser, + "Remove account from maxadmin", "Remove account for using maxadmin over the network. E.g.:\n" " MaxAdmin> remove user bob somepass", - "Remove account for using maxadmin over the network. E.g.:\n" - " MaxAdmin> remove user bob somepass", - {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} + {ARG_TYPE_STRING, ARG_TYPE_STRING} }, { - NULL, 0, NULL, NULL, NULL, {0, 0, 0} + "server", 2, 2, cmd_RemoveServer, + "Remove a server from a service or a monitor", + "Usage: remove server SERVER TARGET\n" + "The TARGET must be either a service or a monitor", + {ARG_TYPE_SERVER, ARG_TYPE_STRING} + }, + { + EMPTY_OPTION } }; @@ -815,51 +916,282 @@ flushlogs(DCB *pdcb) /** * The subcommands of the flush command */ -struct subcommand flushoptions[] = { +struct subcommand flushoptions[] = +{ { "log", - 1, + 1, 1, flushlog, - "Flush the content of a log file, close that log, rename it and open a new log file", + "Flush log files", "Flush the content of a log file, close that log, rename it and open a new log file", {ARG_TYPE_STRING, 0, 0} }, { "logs", - 0, + 0, 0, flushlogs, - "Flush the content of all log files, close those logs, rename them and open a new log files", + "Flush log files", "Flush the content of all log files, close those logs, rename them and open a new log files", {0, 0, 0} }, { - NULL, 0, NULL, NULL, NULL, {0, 0, 0} + EMPTY_OPTION } }; +/** This is used to prevent concurrent creation or removal of servers */ +static SPINLOCK server_mod_lock = SPINLOCK_INIT; + +/** + * Create a new server + * + * @param dcb Client DCB + * @param name Server name + * @param address Server network address + * @param port Server port + * @param protocol Protocol, NULL for default (MySQLBackend) + * @param authenticator Authenticator module, NULL for default (MySQLBackendAuth) + * @param authenticator_options Authenticator options, NULL for no options + */ +static void createServer(DCB *dcb, char *name, char *address, char *port, + char *protocol, char *authenticator, char *authenticator_options) +{ + spinlock_acquire(&server_mod_lock); + + if (server_find_by_unique_name(name) == NULL) + { + if (server_create(name, address, port, protocol, authenticator, authenticator_options)) + { + dcb_printf(dcb, "Created server '%s'\n", name); + } + else + { + dcb_printf(dcb, "Failed to create new server, see log file for more details\n"); + } + } + else + { + dcb_printf(dcb, "Server '%s' already exists.\n", name); + } + + spinlock_release(&server_mod_lock); +} + +struct subcommand createoptions[] = +{ + { + "server", 3, 6, createServer, + "Create a new server", + "Usage: create server NAME HOST PORT [PROTOCOL] [AUTHENTICATOR] [OPTIONS]\n" + "Create a new server from the following parameters.\n" + "NAME Server name\n" + "HOST Server host address\n" + "PORT Server port\n" + "PROTOCOL Server protocol (default MySQLBackend)\n" + "AUTHENTICATOR Authenticator module name (default MySQLAuth)\n" + "OPTIONS Options for the authenticator module\n\n" + "The first three parameters are required, the others are optional.\n", + { + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING + } + }, + { + EMPTY_OPTION + } +}; + +static void destroyServer(DCB *dcb, SERVER *server) +{ + /** Do this so that we don't directly access the server. Currently, the + * destruction of a server does not free any memory and the server stays + * valid. */ + char name[strlen(server->unique_name) + 1]; + strcpy(name, server->unique_name); + + if (server_destroy(server)) + { + dcb_printf(dcb, "Destroyed server '%s'\n", name); + } + else + { + dcb_printf(dcb, "Failed to destroy server '%s', see log file for more details\n", name); + } +} + +struct subcommand destroyoptions[] = +{ + { + "server", 1, 1, destroyServer, + "Destroy a server", + "Usage: destroy server NAME", + {ARG_TYPE_SERVER} + }, + { + EMPTY_OPTION + } +}; + +static void alterServer(DCB *dcb, SERVER *server, char *key, char *value) +{ + bool unknown = false; + + if (strcmp(key, "address") == 0) + { + server_update_address(server, value); + } + else if (strcmp(key, "port") == 0) + { + server_update_port(server, atoi(value)); + } + else if (strcmp(key, "monuser") == 0) + { + server_update_credentials(server, value, server->monpw); + } + else if (strcmp(key, "monpw") == 0) + { + server_update_credentials(server, server->monuser, value); + } + else if (server_is_ssl_parameter(key)) + { + server_update_ssl(server, key, value); + } + else + { + unknown = true; + } + + if (unknown) + { + dcb_printf(dcb, "Unknown parameter '%s'", key); + } +} + +/** + * @brief Convert a string value to a positive integer + * + * If the value is not a positive integer, an error is printed to @c dcb. + * + * @param dcb Client DCB + * @param value String value + * @return 0 on error, otherwise a positive integer + */ +static long get_positive_int(DCB *dcb, const char *value) +{ + char *endptr; + long ival = strtol(value, &endptr, 10); + + if (*endptr == '\0' && ival > 0) + { + return ival; + } + + dcb_printf(dcb, "Invalid value: %s", value); + return 0; +} + +static void alterMonitor(DCB *dcb, MONITOR *monitor, char *key, char *value) +{ + bool unknown = false; + if (strcmp(key, "user") == 0) + { + monitorAddUser(monitor, value, monitor->password); + } + else if (strcmp(key, "password") == 0) + { + monitorAddUser(monitor, monitor->user, value); + } + else if (strcmp(key, "monitor_interval") == 0) + { + long ival = get_positive_int(dcb, value); + if (ival) + { + monitorSetInterval(monitor, ival); + } + } + else if (strcmp(key, "backend_connect_timeout") == 0) + { + long ival = get_positive_int(dcb, value); + if (ival) + { + monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_write_timeout") == 0) + { + long ival = get_positive_int(dcb, value); + if (ival) + { + monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_read_timeout") == 0) + { + long ival = get_positive_int(dcb, value); + if (ival) + { + monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); + } + } + else + { + unknown = true; + } + + if (unknown) + { + dcb_printf(dcb, "Unknown parameter '%s'", key); + } +} + +struct subcommand alteroptions[] = +{ + { + "server", 3, 3, alterServer, + "Alter server parameters", + "Usage: alter server NAME KEY VALUE\n" + "This will alter an existing parameter of a server. The accepted values\n" + "for KEY are: 'address', 'port', 'monuser', 'monpw'", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING} + }, + { + "monitor", 3, 3, alterMonitor, + "Alter monitor parameters", + "Usage: alter monitor NAME KEY VALUE\n" + "This will alter an existing parameter of a monitor. The accepted values\n" + "for KEY are: 'user', 'password', 'monitor_interval',\n" + "'backend_connect_timeout', 'backend_write_timeout', 'backend_read_timeout'", + {ARG_TYPE_MONITOR, ARG_TYPE_STRING, ARG_TYPE_STRING} + }, + { + EMPTY_OPTION + } +}; /** * The debug command table */ -static struct { +static struct +{ char *cmd; struct subcommand *options; -} cmds[] = { +} cmds[] = +{ { "add", addoptions }, + { "remove", removeoptions }, + { "create", createoptions }, + { "destroy", destroyoptions }, + { "alter", alteroptions }, + { "set", setoptions }, { "clear", clearoptions }, { "disable", disableoptions }, { "enable", enableoptions }, -#if defined(FAKE_CODE) - { "fail", failoptions }, -#endif /* FAKE_CODE */ { "flush", flushoptions }, { "list", listoptions }, { "reload", reloadoptions }, - { "remove", removeoptions }, { "restart", restartoptions }, - { "set", setoptions }, - { "show", showoptions }, { "shutdown", shutdownoptions }, + { "show", showoptions }, { "sync", syncoptions }, { NULL, NULL } }; @@ -882,63 +1214,63 @@ convert_arg(int mode, char *arg, int arg_type) switch (arg_type) { - case ARG_TYPE_ADDRESS: - return (unsigned long)strtol(arg, NULL, 0); - case ARG_TYPE_STRING: - return (unsigned long)arg; - case ARG_TYPE_SERVICE: - if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) - { - rval = (unsigned long)service_find(arg); - } - return rval; - case ARG_TYPE_SERVER: - if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) - { - rval = (unsigned long)server_find_by_unique_name(arg); - } - return rval; - case ARG_TYPE_DBUSERS: - if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) - { - service = service_find(arg); - if (service) + case ARG_TYPE_ADDRESS: + return (unsigned long)strtol(arg, NULL, 0); + case ARG_TYPE_STRING: + return (unsigned long)arg; + case ARG_TYPE_SERVICE: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) { - return (unsigned long)(service->ports->users); + rval = (unsigned long)service_find(arg); } - else + return rval; + case ARG_TYPE_SERVER: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) { - return 0; + rval = (unsigned long)server_find_by_unique_name(arg); } - } - return rval; - case ARG_TYPE_DCB: - rval = (unsigned long)strtol(arg, NULL, 0); - if (mode == CLIM_USER && dcb_isvalid((DCB *)rval) == 0) - { - rval = 0; - } - return rval; - case ARG_TYPE_SESSION: - rval = (unsigned long)strtol(arg, NULL, 0); - if (mode == CLIM_USER && session_isvalid((SESSION *)rval) == 0) - { - rval = 0; - } - return rval; - case ARG_TYPE_MONITOR: - if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) - { - rval = (unsigned long)monitor_find(arg); - } - return rval; - case ARG_TYPE_FILTER: - if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) - { - rval = (unsigned long)filter_find(arg); - } - return rval; - case ARG_TYPE_NUMERIC: + return rval; + case ARG_TYPE_DBUSERS: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + { + service = service_find(arg); + if (service) + { + return (unsigned long)(service->ports->users); + } + else + { + return 0; + } + } + return rval; + case ARG_TYPE_DCB: + rval = (unsigned long)strtol(arg, NULL, 0); + if (mode == CLIM_USER && dcb_isvalid((DCB *)rval) == 0) + { + rval = 0; + } + return rval; + case ARG_TYPE_SESSION: + rval = (unsigned long)strtol(arg, NULL, 0); + if (mode == CLIM_USER && session_isvalid((SESSION *)rval) == 0) + { + rval = 0; + } + return rval; + case ARG_TYPE_MONITOR: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + { + rval = (unsigned long)monitor_find(arg); + } + return rval; + case ARG_TYPE_FILTER: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + { + rval = (unsigned long)filter_find(arg); + } + return rval; + case ARG_TYPE_NUMERIC: { int i; for (i = 0; arg[i]; i++) @@ -974,7 +1306,6 @@ execute_cmd(CLI_SESSION *cli) DCB *dcb = cli->session->client_dcb; int argc, i, j, found = 0; char *args[MAXARGS + 1]; - unsigned long arg1, arg2, arg3; int in_quotes = 0, escape_next = 0; char *ptr, *lptr; bool in_space = false; @@ -1097,8 +1428,9 @@ execute_cmd(CLI_SESSION *cli) dcb_printf(dcb, "Available options to the %s command:\n", args[1]); for (j = 0; cmds[i].options[j].arg1; j++) { - dcb_printf(dcb, " %-12s %s\n", cmds[i].options[j].arg1, - cmds[i].options[j].help); + dcb_printf(dcb, "'%s' - %s\n\n\t%s\n\n", cmds[i].options[j].arg1, + cmds[i].options[j].help, cmds[i].options[j].devhelp); + } } } @@ -1124,74 +1456,89 @@ execute_cmd(CLI_SESSION *cli) if (strcasecmp(args[1], cmds[i].options[j].arg1) == 0) { found = 1; /**< command and sub-command match */ - if (argc != cmds[i].options[j].n_args) + if (argc < cmds[i].options[j].argc_min) { - dcb_printf(dcb, "Incorrect number of arguments: %s %s expects %d arguments\n", + dcb_printf(dcb, "Incorrect number of arguments: %s %s expects at least %d arguments\n", cmds[i].cmd, cmds[i].options[j].arg1, - cmds[i].options[j].n_args); + cmds[i].options[j].argc_min); } else { - switch (cmds[i].options[j].n_args) - { - case 0: - cmds[i].options[j].fn(dcb); - break; - case 1: - arg1 = convert_arg(cli->mode, args[2], cmds[i].options[j].arg_types[0]); + unsigned long arg_list[MAXARGS] = {}; - if (arg1) + for (int k = 0; k < cmds[i].options[j].argc_max && k < argc; k++) + { + arg_list[k] = convert_arg(cli->mode, args[k + 2], cmds[i].options[j].arg_types[k]); + if (arg_list[k] == 0) { - cmds[i].options[j].fn(dcb, arg1); - } - else - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[2]); - } - break; - case 2: - arg1 = convert_arg(cli->mode, args[2], cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(cli->mode, args[3], cmds[i].options[j].arg_types[1]); - if (arg1 && arg2) - { - cmds[i].options[j].fn(dcb, arg1, arg2); - } - else if (arg1 == 0) - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[2]); - } - else - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[3]); - } - break; - case 3: - arg1 = convert_arg(cli->mode, args[2], cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(cli->mode, args[3], cmds[i].options[j].arg_types[1]); - arg3 = convert_arg(cli->mode, args[4], cmds[i].options[j].arg_types[2]); - if (arg1 && arg2 && arg3) - { - cmds[i].options[j].fn(dcb, arg1, arg2, arg3); - } - else if (arg1 == 0) - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[2]); - } - else if (arg2 == 0) - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[3]); - } - else if (arg3 == 0) - { - dcb_printf(dcb, "Invalid argument: %s\n", - args[4]); + dcb_printf(dcb, "Invalid argument: %s\n", args[k + 2]); + return 0; } } + + switch (cmds[i].options[j].argc_max) + { + case 0: + cmds[i].options[j].fn(dcb); + break; + case 1: + cmds[i].options[j].fn(dcb, arg_list[0]); + break; + case 2: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1]); + break; + case 3: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2]); + break; + case 4: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3]); + break; + case 5: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4]); + break; + case 6: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5]); + break; + case 7: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6]); + break; + case 8: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6], arg_list[7]); + break; + case 9: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6], arg_list[7], arg_list[8]); + break; + case 10: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6], arg_list[7], arg_list[8], + arg_list[9]); + break; + case 11: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6], arg_list[7], arg_list[8], + arg_list[9], arg_list[10]); + break; + case 12: + cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2], + arg_list[3], arg_list[4], arg_list[5], + arg_list[6], arg_list[7], arg_list[8], + arg_list[9], arg_list[10], arg_list[11]); + break; + default: + dcb_printf(dcb, "Error: Maximum argument count is %d.\n", MAXARGS); + break; + } } } } @@ -1504,11 +1851,11 @@ struct log_action_entry static bool get_log_action(const char* name, struct log_action_entry* entryp) { static const struct log_action_entry entries[] = - { - { "debug", LOG_DEBUG, "debug" }, - { "trace", LOG_INFO, "info" }, - { "message", LOG_NOTICE, "notice" }, - }; + { + { "debug", LOG_DEBUG, "debug" }, + { "trace", LOG_INFO, "info" }, + { "message", LOG_NOTICE, "notice" }, + }; const int n_entries = sizeof(entries) / sizeof(entries[0]); bool found = false; @@ -1619,13 +1966,13 @@ static int compare_log_priority_entries(const void* l, const void* r) static int string_to_priority(const char* name) { static const struct log_priority_entry LOG_PRIORITY_ENTRIES[] = - { - // NOTE: If you make changes to this array, ensure that it remains alphabetically ordered. - { "debug", LOG_DEBUG }, - { "info", LOG_INFO }, - { "notice", LOG_NOTICE }, - { "warning", LOG_WARNING }, - }; + { + // NOTE: If you make changes to this array, ensure that it remains alphabetically ordered. + { "debug", LOG_DEBUG }, + { "info", LOG_INFO }, + { "notice", LOG_NOTICE }, + { "warning", LOG_WARNING }, + }; const size_t N_LOG_PRIORITY_ENTRIES = sizeof(LOG_PRIORITY_ENTRIES) / sizeof(LOG_PRIORITY_ENTRIES[0]); @@ -1945,49 +2292,3 @@ disable_account(DCB *dcb, char *user) dcb_printf(dcb, "Failed to disable the Linux user %s: %s\n", user, err); } } - -#if defined(FAKE_CODE) -static void fail_backendfd(void) -{ - fail_next_backend_fd = true; -} - -static void fail_clientfd(void) -{ - fail_next_client_fd = true; -} - -static void fail_accept( - DCB* dcb, - char* arg1, - char* arg2) -{ - int failcount = MXS_MIN(atoi(arg2), 100); - fail_accept_errno = atoi(arg1); - char errbuf[MXS_STRERROR_BUFLEN]; - - switch(fail_accept_errno) { - case EAGAIN: -// case EWOULDBLOCK: - case EBADF: - case EINTR: - case EINVAL: - case EMFILE: - case ENFILE: - case ENOTSOCK: - case EOPNOTSUPP: - case ENOBUFS: - case ENOMEM: - case EPROTO: - fail_next_accept = failcount; - break; - - default: - dcb_printf(dcb, - "[%d, %s] is not valid errno for accept.\n", - fail_accept_errno, - strerror_r(fail_accept_errno, errbuf, sizeof(errbuf))); - return ; - } -} -#endif /* FAKE_CODE */ diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 135de44f0..05eaa8d4d 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -96,7 +96,8 @@ static ROUTER_OBJECT MyObject = diagnostics, NULL, handleError, - getCapabilities + getCapabilities, + NULL }; static SPINLOCK instlock; diff --git a/server/modules/routing/readconnroute/readconnection.h b/server/modules/routing/readconnroute/readconnection.h index 13b4fa2ad..dfcf153fd 100644 --- a/server/modules/routing/readconnroute/readconnection.h +++ b/server/modules/routing/readconnroute/readconnection.h @@ -32,18 +32,6 @@ MXS_BEGIN_DECLS -/** - * Internal structure used to define the set of backend servers we are routing - * connections to. This provides the storage for routing module specific data - * that is required for each of the backend servers. - */ -typedef struct backend -{ - SERVER *server; /*< The server itself */ - int current_connection_count; /*< Number of connections to the server */ - int weight; /*< Desired routing weight */ -} BACKEND; - /** * The client session structure used within this router. */ @@ -55,7 +43,7 @@ typedef struct router_client_session SPINLOCK rses_lock; /*< protects rses_deleted */ int rses_versno; /*< even = no active update, else odd */ bool rses_closed; /*< true when closeSession is called */ - BACKEND *backend; /*< Backend used by the client session */ + SERVER_REF *backend; /*< Backend used by the client session */ DCB *backend_dcb; /*< DCB Connection to the backend */ DCB *client_dcb; /**< Client DCB */ struct router_client_session *next; @@ -79,9 +67,7 @@ typedef struct typedef struct router_instance { SERVICE *service; /*< Pointer to the service using this router */ - ROUTER_CLIENT_SES *connections; /*< Link list of all the client connections */ SPINLOCK lock; /*< Spinlock for the instance data */ - BACKEND **servers; /*< List of backend servers */ unsigned int bitmask; /*< Bitmask to apply to server->status */ unsigned int bitvalue; /*< Required value of server->status */ ROUTER_STATS stats; /*< Statistics for this router */ diff --git a/server/modules/routing/readconnroute/readconnroute.c b/server/modules/routing/readconnroute/readconnroute.c index 6f78663a6..45294044d 100644 --- a/server/modules/routing/readconnroute/readconnroute.c +++ b/server/modules/routing/readconnroute/readconnroute.c @@ -125,14 +125,15 @@ static ROUTER_OBJECT MyObject = diagnostics, clientReply, handleError, - getCapabilities + getCapabilities, + NULL }; static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses); static void rses_end_locked_router_action(ROUTER_CLIENT_SES* rses); -static BACKEND *get_root_master(BACKEND **servers); +static SERVER_REF *get_root_master(SERVER_REF *servers); static int handle_state_switch(DCB* dcb, DCB_REASON reason, void * routersession); static SPINLOCK instlock; static ROUTER_INSTANCE *instances; @@ -178,14 +179,6 @@ static inline void free_readconn_instance(ROUTER_INSTANCE *router) { if (router) { - if (router->servers) - { - for (int i = 0; router->servers[i]; i++) - { - MXS_FREE(router->servers[i]); - } - } - MXS_FREE(router->servers); MXS_FREE(router); } } @@ -203,11 +196,8 @@ static ROUTER * createInstance(SERVICE *service, char **options) { ROUTER_INSTANCE *inst; - SERVER *server; SERVER_REF *sref; int i, n; - BACKEND *backend; - char *weightby; if ((inst = MXS_CALLOC(1, sizeof(ROUTER_INSTANCE))) == NULL) { @@ -217,103 +207,6 @@ createInstance(SERVICE *service, char **options) inst->service = service; spinlock_init(&inst->lock); - /* - * We need an array of the backend servers in the instance structure so - * that we can maintain a count of the number of connections to each - * backend server. - */ - for (sref = service->dbref, n = 0; sref; sref = sref->next) - { - n++; - } - - inst->servers = (BACKEND **) MXS_CALLOC(n + 1, sizeof(BACKEND *)); - if (!inst->servers) - { - free_readconn_instance(inst); - return NULL; - } - - for (sref = service->dbref, n = 0; sref; sref = sref->next) - { - if ((inst->servers[n] = MXS_MALLOC(sizeof(BACKEND))) == NULL) - { - free_readconn_instance(inst); - return NULL; - } - inst->servers[n]->server = sref->server; - inst->servers[n]->current_connection_count = 0; - inst->servers[n]->weight = 1000; - n++; - } - inst->servers[n] = NULL; - - if ((weightby = serviceGetWeightingParameter(service)) != NULL) - { - int total = 0; - - for (int n = 0; inst->servers[n]; n++) - { - BACKEND *backend = inst->servers[n]; - char *param = serverGetParameter(backend->server, weightby); - if (param) - { - total += atoi(param); - } - } - if (total == 0) - { - MXS_WARNING("Weighting Parameter for service '%s' " - "will be ignored as no servers have values " - "for the parameter '%s'.", - service->name, weightby); - } - else if (total < 0) - { - MXS_ERROR("Sum of weighting parameter '%s' for service '%s' exceeds " - "maximum value of %d. Weighting will be ignored.", - weightby, service->name, INT_MAX); - } - else - { - for (int n = 0; inst->servers[n]; n++) - { - BACKEND *backend = inst->servers[n]; - char *param = serverGetParameter(backend->server, weightby); - if (param) - { - int wght = atoi(param); - int perc = (wght * 1000) / total; - - if (perc == 0) - { - perc = 1; - MXS_ERROR("Weighting parameter '%s' with a value of %d for" - " server '%s' rounds down to zero with total weight" - " of %d for service '%s'. No queries will be " - "routed to this server.", weightby, wght, - backend->server->unique_name, total, - service->name); - } - else if (perc < 0) - { - MXS_ERROR("Weighting parameter '%s' for server '%s' is too large, " - "maximum value is %d. No weighting will be used for this server.", - weightby, backend->server->unique_name, INT_MAX / 1000); - perc = 1000; - } - backend->weight = perc; - } - else - { - MXS_WARNING("Server '%s' has no parameter '%s' used for weighting" - " for service '%s'.", backend->server->unique_name, - weightby, service->name); - } - } - } - } - /* * Process the options */ @@ -398,9 +291,9 @@ newSession(ROUTER *instance, SESSION *session) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; ROUTER_CLIENT_SES *client_rses; - BACKEND *candidate = NULL; + SERVER_REF *candidate = NULL; int i; - BACKEND *master_host = NULL; + SERVER_REF *master_host = NULL; MXS_DEBUG("%lu [newSession] new router session with session " "%p, and inst %p.", @@ -408,7 +301,6 @@ newSession(ROUTER *instance, SESSION *session) session, inst); - client_rses = (ROUTER_CLIENT_SES *) MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES)); if (client_rses == NULL) @@ -425,7 +317,7 @@ newSession(ROUTER *instance, SESSION *session) /** * Find the Master host from available servers */ - master_host = get_root_master(inst->servers); + master_host = get_root_master(inst->service->dbref); /** * Find a backend server to connect to. This is the extent of the @@ -445,52 +337,43 @@ newSession(ROUTER *instance, SESSION *session) * become the new candidate. This has the effect of spreading the * connections over different servers during periods of very low load. */ - for (i = 0; inst->servers[i]; i++) + for (SERVER_REF *ref = inst->service->dbref; ref; ref = ref->next) { - if (inst->servers[i]) + if (!SERVER_REF_IS_ACTIVE(ref) || SERVER_IN_MAINT(ref->server) || ref->weight == 0) + { + continue; + } + else { MXS_DEBUG("%lu [newSession] Examine server in port %d with " "%d connections. Status is %s, " "inst->bitvalue is %d", pthread_self(), - inst->servers[i]->server->port, - inst->servers[i]->current_connection_count, - STRSRVSTATUS(inst->servers[i]->server), + ref->server->port, + ref->connections, + STRSRVSTATUS(ref->server), inst->bitmask); } - if (SERVER_IN_MAINT(inst->servers[i]->server)) - { - continue; - } - - if (inst->servers[i]->weight == 0) - { - continue; - } - /* Check server status bits against bitvalue from router_options */ - if (inst->servers[i] && - SERVER_IS_RUNNING(inst->servers[i]->server) && - (inst->servers[i]->server->status & inst->bitmask & inst->bitvalue)) + if (ref && SERVER_IS_RUNNING(ref->server) && + (ref->server->status & inst->bitmask & inst->bitvalue)) { if (master_host) { - if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_SLAVE)) + if (ref == master_host && (inst->bitvalue & SERVER_SLAVE)) { - /* skip root Master here, as it could also be slave of an external server - * that is not in the configuration. - * Intermediate masters (Relay Servers) are also slave and will be selected - * as Slave(s) + /* Skip root master here, as it could also be slave of an external server that + * is not in the configuration. Intermediate masters (Relay Servers) are also + * slave and will be selected as Slave(s) */ continue; } - if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_MASTER)) + if (ref == master_host && (inst->bitvalue & SERVER_MASTER)) { - /* If option is "master" return only the root Master as there - * could be intermediate masters (Relay Servers) - * and they must not be selected. + /* If option is "master" return only the root Master as there could be + * intermediate masters (Relay Servers) and they must not be selected. */ candidate = master_host; @@ -499,8 +382,7 @@ newSession(ROUTER *instance, SESSION *session) } else { - /* master_host is NULL, no master server. - * If requested router_option is 'master' + /* Master_host is NULL, no master server. If requested router_option is 'master' * candidate wll be NULL. */ if (inst->bitvalue & SERVER_MASTER) @@ -510,40 +392,31 @@ newSession(ROUTER *instance, SESSION *session) } } - /* If no candidate set, set first running server as - our initial candidate server */ + /* If no candidate set, set first running server as our initial candidate server */ if (candidate == NULL) { - candidate = inst->servers[i]; + candidate = ref; } - else if (((inst->servers[i]->current_connection_count + 1) - * 1000) / inst->servers[i]->weight < - ((candidate->current_connection_count + 1) * - 1000) / candidate->weight) + else if (((ref->connections + 1) * 1000) / ref->weight < + ((candidate->connections + 1) * 1000) / candidate->weight) { - /* This running server has fewer - connections, set it as a new candidate */ - candidate = inst->servers[i]; + /* This running server has fewer connections, set it as a new candidate */ + candidate = ref; } - else if (((inst->servers[i]->current_connection_count + 1) - * 1000) / inst->servers[i]->weight == - ((candidate->current_connection_count + 1) * - 1000) / candidate->weight && - inst->servers[i]->server->stats.n_connections < - candidate->server->stats.n_connections) + else if (((ref->connections + 1) * 1000) / ref->weight == + ((candidate->connections + 1) * 1000) / candidate->weight && + ref->server->stats.n_connections < candidate->server->stats.n_connections) { - /* This running server has the same number - of connections currently as the candidate - but has had fewer connections over time - than candidate, set this server to candidate*/ - candidate = inst->servers[i]; + /* This running server has the same number of connections currently as the candidate + but has had fewer connections over time than candidate, set this server to + candidate*/ + candidate = ref; } } } - /* There is no candidate server here! - * With router_option=slave a master_host could be set, so route traffic there. - * Otherwise, just clean up and return NULL + /* If we haven't found a proper candidate yet but a master server is available, we'll pick that + * with the assumption that it is "better" than a slave. */ if (!candidate) { @@ -553,9 +426,8 @@ newSession(ROUTER *instance, SESSION *session) } else { - MXS_ERROR("Failed to create new routing session. " - "Couldn't find eligible candidate server. Freeing " - "allocated resources."); + MXS_ERROR("Failed to create new routing session. Couldn't find eligible" + " candidate server. Freeing allocated resources."); MXS_FREE(client_rses); return NULL; } @@ -565,48 +437,32 @@ newSession(ROUTER *instance, SESSION *session) * We now have the server with the least connections. * Bump the connection count for this server */ - atomic_add(&candidate->current_connection_count, 1); client_rses->backend = candidate; - MXS_DEBUG("%lu [newSession] Selected server in port %d. " - "Connections : %d\n", - pthread_self(), - candidate->server->port, - candidate->current_connection_count); - /* - * Open a backend connection, putting the DCB for this - * connection in the client_rses->backend_dcb - */ - client_rses->backend_dcb = dcb_connect(candidate->server, - session, + /** Open the backend connection */ + client_rses->backend_dcb = dcb_connect(candidate->server, session, candidate->server->protocol); + if (client_rses->backend_dcb == NULL) { - atomic_add(&candidate->current_connection_count, -1); + /** The failure is reported in dcb_connect() */ MXS_FREE(client_rses); return NULL; } - dcb_add_callback( - client_rses->backend_dcb, + + atomic_add(&candidate->connections, 1); + + // TODO: Remove this as it is never called + dcb_add_callback(client_rses->backend_dcb, DCB_REASON_NOT_RESPONDING, &handle_state_switch, client_rses); inst->stats.n_sessions++; - /** - * Add this session to the list of active sessions. - */ - spinlock_acquire(&inst->lock); - client_rses->next = inst->connections; - inst->connections = client_rses; - spinlock_release(&inst->lock); - CHK_CLIENT_RSES(client_rses); - MXS_INFO("Readconnroute: New session for server %s. " - "Connections : %d", - candidate->server->unique_name, - candidate->current_connection_count); + MXS_INFO("Readconnroute: New session for server %s. Connections : %d", + candidate->server->unique_name, candidate->connections); return(void *) client_rses; } @@ -631,42 +487,11 @@ newSession(ROUTER *instance, SESSION *session) static void freeSession(ROUTER* router_instance, void* router_client_ses) { ROUTER_INSTANCE* router = (ROUTER_INSTANCE *) router_instance; - ROUTER_CLIENT_SES* router_cli_ses = - (ROUTER_CLIENT_SES *) router_client_ses; + ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *) router_client_ses; - ss_debug(int prev_val = ) atomic_add(&router_cli_ses->backend->current_connection_count, -1); + ss_debug(int prev_val = ) atomic_add(&router_cli_ses->backend->connections, -1); ss_dassert(prev_val > 0); - spinlock_acquire(&router->lock); - - if (router->connections == router_cli_ses) - { - router->connections = router_cli_ses->next; - } - else - { - ROUTER_CLIENT_SES *ptr = router->connections; - - while (ptr != NULL && ptr->next != router_cli_ses) - { - ptr = ptr->next; - } - - if (ptr != NULL) - { - ptr->next = router_cli_ses->next; - } - } - spinlock_release(&router->lock); - - MXS_DEBUG("%lu [freeSession] Unlinked router_client_session %p from " - "router %p and from server on port %d. Connections : %d. ", - pthread_self(), - router_cli_ses, - router, - router_cli_ses->backend->server->port, - prev_val - 1); - MXS_FREE(router_cli_ses); } @@ -708,6 +533,29 @@ closeSession(ROUTER *instance, void *router_session) } } +/** Log routing failure due to closed session */ +static void log_closed_session(mysql_server_cmd_t mysql_command, bool is_closed, + SERVER_REF *ref) +{ + char msg[MAX_SERVER_NAME_LEN + 200] = ""; // Extra space for message + + if (is_closed) + { + sprintf(msg, "Session is closed."); + } + else if (SERVER_IS_DOWN(ref->server)) + { + sprintf(msg, "Server '%s' is down.", ref->server->unique_name); + } + else if (!SERVER_REF_IS_ACTIVE(ref)) + { + sprintf(msg, "Server '%s' was removed from the service.", ref->server->unique_name); + } + + MXS_ERROR("Failed to route MySQL command %d to backend server. %s", + mysql_command, msg); +} + /** * We have data from the client, we must route it to the backend. * This is simply a case of sending it to the connection that was @@ -723,7 +571,7 @@ routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *) router_session; - int rc; + int rc = 0; DCB* backend_dcb; MySQLProtocol *proto = (MySQLProtocol*)router_cli_ses->client_dcb->protocol; mysql_server_cmd_t mysql_command = proto->current_command; @@ -752,16 +600,11 @@ routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) } if (rses_is_closed || backend_dcb == NULL || + !SERVER_REF_IS_ACTIVE(router_cli_ses->backend) || SERVER_IS_DOWN(router_cli_ses->backend->server)) { - MXS_ERROR("Failed to route MySQL command %d to backend " - "server.%s", - mysql_command, rses_is_closed ? " Session is closed." : ""); - rc = 0; - while ((queue = GWBUF_CONSUME_ALL(queue)) != NULL) - { - ; - } + log_closed_session(mysql_command, rses_is_closed, router_cli_ses->backend); + gwbuf_free(queue); goto return_rc; } @@ -806,23 +649,12 @@ static void diagnostics(ROUTER *router, DCB *dcb) { ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *) router; - ROUTER_CLIENT_SES *session; - int i = 0; - BACKEND *backend; char *weightby; - spinlock_acquire(&router_inst->lock); - session = router_inst->connections; - while (session) - { - i++; - session = session->next; - } - spinlock_release(&router_inst->lock); - dcb_printf(dcb, "\tNumber of router sessions: %d\n", router_inst->stats.n_sessions); - dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", i); + dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", + 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)) @@ -833,15 +665,13 @@ diagnostics(ROUTER *router, DCB *dcb) weightby); dcb_printf(dcb, "\t\tServer Target %% Connections\n"); - for (i = 0; router_inst->servers[i]; i++) + for (SERVER_REF *ref = router_inst->service->dbref; ref; ref = ref->next) { - backend = router_inst->servers[i]; dcb_printf(dcb, "\t\t%-20s %3.1f%% %d\n", - backend->server->unique_name, - (float) backend->weight / 10, - backend->current_connection_count); + ref->server->unique_name, + (float) ref->weight / 10, + ref->connections); } - } } @@ -1002,28 +832,28 @@ static uint64_t getCapabilities(void) * */ -static BACKEND *get_root_master(BACKEND **servers) +static SERVER_REF *get_root_master(SERVER_REF *servers) { int i = 0; - BACKEND *master_host = NULL; + SERVER_REF *master_host = NULL; - for (i = 0; servers[i]; i++) + for (SERVER_REF *ref = servers; ref; ref = ref->next) { - if (servers[i] && (servers[i]->server->status & (SERVER_MASTER | SERVER_MAINT)) == SERVER_MASTER) + if (ref->active && SERVER_IS_MASTER(ref->server)) { if (master_host == NULL) { - master_host = servers[i]; + master_host = ref; } - else if (servers[i]->server->depth < master_host->server->depth || - (servers[i]->server->depth == master_host->server->depth && - servers[i]->weight > master_host->weight)) + else if (ref->server->depth < master_host->server->depth || + (ref->server->depth == master_host->server->depth && + ref->weight > master_host->weight)) { /** * This master has a lower depth than the candidate master or * the depths are equal but this master has a higher weight */ - master_host = servers[i]; + master_host = ref; } } } diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 9ddf0f4f5..aab1775f5 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -43,7 +43,7 @@ MODULE_INFO info = * by the entry point functions. Some of these are used by functions in other * modules of the read write split router, others are used only within this * module. - * + * * @verbatim * Revision History * @@ -65,6 +65,9 @@ MODULE_INFO info = * @endverbatim */ +/** Maximum number of slaves */ +#define MAX_SLAVE_COUNT 255 + static char *version_str = "V1.1.0"; /* @@ -101,16 +104,10 @@ static ROUTER_OBJECT MyObject = diagnostics, clientReply, handleError, - getCapabilities + getCapabilities, + NULL }; -/* - * A couple of static variables that are used throughout the router - */ - -static SPINLOCK instlock; -static ROUTER_INSTANCE *instances; - /* * Declaration of functions that are used only within this module, and are * not part of the API. @@ -126,10 +123,9 @@ static void handle_error_reply_client(SESSION *ses, ROUTER_CLIENT_SES *rses, static bool handle_error_new_connection(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES **rses, DCB *backend_dcb, GWBUF *errmsg); -static int router_get_servercount(ROUTER_INSTANCE *inst); -static bool have_enough_servers(ROUTER_CLIENT_SES **p_rses, const int min_nsrv, +static bool have_enough_servers(ROUTER_CLIENT_SES *rses, const int min_nsrv, int router_nsrv, ROUTER_INSTANCE *router); - +static bool create_backends(ROUTER_CLIENT_SES *rses, backend_ref_t** dest, int* n_backend); /** * Implementation of the mandatory version entry point * @@ -147,8 +143,6 @@ char *version() void ModuleInit() { MXS_NOTICE("Initializing statement-based read/write split router module."); - spinlock_init(&instlock); - instances = NULL; } /** @@ -170,7 +164,7 @@ ROUTER_OBJECT *GetModuleObject() /** * @brief Create an instance of the read/write router (API). - * + * * Create an instance of read/write statement router within the MaxScale. One * instance of the router is required for each service that is defined in the * configuration as using this router. One instance of the router will handle @@ -183,12 +177,7 @@ ROUTER_OBJECT *GetModuleObject() static ROUTER *createInstance(SERVICE *service, char **options) { ROUTER_INSTANCE *router; - SERVER *server; - SERVER_REF *sref; - int nservers; - int i; CONFIG_PARAMETER *param; - char *weightby; if ((router = MXS_CALLOC(1, sizeof(ROUTER_INSTANCE))) == NULL) { @@ -197,131 +186,11 @@ static ROUTER *createInstance(SERVICE *service, char **options) router->service = service; spinlock_init(&router->lock); - /** Calculate number of servers */ - sref = service->dbref; - nservers = 0; - - while (sref != NULL) - { - nservers++; - sref = sref->next; - } - router->servers = (BACKEND **)MXS_CALLOC(nservers + 1, sizeof(BACKEND *)); - - if (router->servers == NULL) - { - free_rwsplit_instance(router); - return NULL; - } - /** - * Create an array of the backend servers in the router structure to - * maintain a count of the number of connections to each - * backend server. - */ - - sref = service->dbref; - nservers = 0; - - while (sref != NULL) - { - if ((router->servers[nservers] = MXS_MALLOC(sizeof(BACKEND))) == NULL) - { - free_rwsplit_instance(router); - return NULL; - } - router->servers[nservers]->backend_server = sref->server; - router->servers[nservers]->backend_conn_count = 0; - router->servers[nservers]->be_valid = false; - router->servers[nservers]->weight = 1000; -#if defined(SS_DEBUG) - router->servers[nservers]->be_chk_top = CHK_NUM_BACKEND; - router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND; -#endif - nservers += 1; - sref = sref->next; - } - router->servers[nservers] = NULL; - /* * Until we know otherwise assume we have some available slaves. */ router->available_slaves = true; - /* - * If server weighting has been defined calculate the percentage - * of load that will be sent to each server. This is only used for - * calculating the least connections, either globally or within a - * service, or the number of current operations on a server. - */ - if ((weightby = serviceGetWeightingParameter(service)) != NULL) - { - int total = 0; - - for (int n = 0; router->servers[n]; n++) - { - BACKEND *backend = router->servers[n]; - char *param = serverGetParameter(backend->backend_server, weightby); - if (param) - { - total += atoi(param); - } - } - if (total == 0) - { - MXS_WARNING("Weighting Parameter for service '%s' " - "will be ignored as no servers have values " - "for the parameter '%s'.", - service->name, weightby); - } - else if (total < 0) - { - MXS_ERROR("Sum of weighting parameter '%s' for service '%s' exceeds " - "maximum value of %d. Weighting will be ignored.", - weightby, service->name, INT_MAX); - } - else - { - for (int n = 0; router->servers[n]; n++) - { - BACKEND *backend = router->servers[n]; - char *param = serverGetParameter(backend->backend_server, weightby); - if (param) - { - int wght = atoi(param); - int perc = (wght * 1000) / total; - - if (perc == 0) - { - MXS_ERROR("Weighting parameter '%s' with a value of %d for" - " server '%s' rounds down to zero with total weight" - " of %d for service '%s'. No queries will be " - "routed to this server as long as a server with" - " positive weight is available.", - weightby, wght, backend->backend_server->unique_name, - total, service->name); - } - else if (perc < 0) - { - MXS_ERROR("Weighting parameter '%s' for server '%s' is too large, " - "maximum value is %d. No weighting will be used for this " - "server.", - weightby, backend->backend_server->unique_name, - INT_MAX / 1000); - perc = 1000; - } - backend->weight = perc; - } - else - { - MXS_WARNING("Server '%s' has no parameter '%s' used for weighting" - " for service '%s'.", - backend->backend_server->unique_name, weightby, - service->name); - } - } - } - } - /** Enable strict multistatement handling by default */ router->rwsplit_config.rw_strict_multi_stmt = true; @@ -348,7 +217,7 @@ static ROUTER *createInstance(SERVICE *service, char **options) * LEAST_CURRENT_OPERATIONS allows us to balance evenly across all the * configured slaves. */ - router->rwsplit_config.rw_max_slave_conn_count = nservers; + router->rwsplit_config.rw_max_slave_conn_count = MAX_SLAVE_COUNT; if (router->rwsplit_config.rw_slave_select_criteria == UNDEFINED_CRITERIA) { @@ -384,15 +253,6 @@ static ROUTER *createInstance(SERVICE *service, char **options) { refreshInstance(router, param); } - /** - * We have completed the creation of the router data, so now - * insert this router into the linked list of routers - * that have been created with this module. - */ - spinlock_acquire(&instlock); - router->next = instances; - instances = router; - spinlock_release(&instlock); return (ROUTER *)router; } @@ -400,14 +260,14 @@ static ROUTER *createInstance(SERVICE *service, char **options) /** * @brief Associate a new session with this instance of the router (API). * - * The session is used to store all the data required by the router for a - * particular client connection. The instance of the router that relates to a + * The session is used to store all the data required by the router for a + * particular client connection. The instance of the router that relates to a * particular service is passed as the first parameter. The second parameter is * the session that has been created in response to the request from a client * for a connection. The passed session contains generic information; this * function creates the session structure that holds router specific data. * There is often a one to one relationship between sessions and router - * sessions, although it is possible to create configurations where a + * sessions, although it is possible to create configurations where a * connection is handled by multiple routers, one after another. * * @param instance The router instance data @@ -416,24 +276,12 @@ static ROUTER *createInstance(SERVICE *service, char **options) */ static void *newSession(ROUTER *router_inst, SESSION *session) { - backend_ref_t - *backend_ref; /*< array of backend references (DCB,BACKEND,cursor) */ - backend_ref_t *master_ref = NULL; /*< pointer to selected master */ - ROUTER_CLIENT_SES *client_rses = NULL; ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_inst; - bool succp; - int router_nservers = 0; /*< # of servers in total */ - int max_nslaves; /*< max # of slaves used in this session */ - int max_slave_rlag; /*< max allowed replication lag for any slave */ - int i; - const int min_nservers = 1; /*< hard-coded for now */ - - client_rses = (ROUTER_CLIENT_SES *)MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES)); + ROUTER_CLIENT_SES *client_rses = (ROUTER_CLIENT_SES *)MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES)); if (client_rses == NULL) { - ss_dassert(false); - goto return_rses; + return NULL; } #if defined(SS_DEBUG) client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; @@ -442,117 +290,57 @@ static void *newSession(ROUTER *router_inst, SESSION *session) client_rses->router = router; client_rses->client_dcb = session->client_dcb; - /** - * If service config has been changed, reload config from service to - * router instance first. - */ - spinlock_acquire(&router->lock); - - if (router->service->svc_config_version > router->rwsplit_version) - { - /** re-read all parameters to rwsplit config structure */ - refreshInstance(router, NULL); /*< scan through all parameters */ - /** increment rwsplit router's config version number */ - router->rwsplit_version = router->service->svc_config_version; - /** Read options */ - rwsplit_process_router_options(router, router->service->routerOptions); - } - /** Copy config struct from router instance */ - memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(rwsplit_config_t)); - - spinlock_release(&router->lock); - /** - * Set defaults to session variables. - */ client_rses->rses_autocommit_enabled = true; client_rses->rses_transaction_active = false; client_rses->have_tmp_tables = false; client_rses->forced_node = NULL; + spinlock_init(&client_rses->rses_lock); + memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(client_rses->rses_config)); - router_nservers = router_get_servercount(router); + int router_nservers = router->service->n_dbref; + const int min_nservers = 1; /*< hard-coded for now */ - if (!have_enough_servers(&client_rses, min_nservers, router_nservers, router)) + if (!have_enough_servers(client_rses, min_nservers, router_nservers, router)) { - goto return_rses; + MXS_FREE(client_rses); + return NULL; } + /** * Create backend reference objects for this session. */ - backend_ref = (backend_ref_t *)MXS_CALLOC(1, router_nservers * sizeof(backend_ref_t)); + backend_ref_t *backend_ref; - if (backend_ref == NULL) + if (!create_backends(client_rses, &backend_ref, &router_nservers)) { - /** log this */ MXS_FREE(client_rses); - MXS_FREE(backend_ref); - client_rses = NULL; - goto return_rses; + return NULL; } - /** - * Initialize backend references with BACKEND ptr. - * Initialize session command cursors for each backend reference. - */ - for (i = 0; i < router_nservers; i++) - { -#if defined(SS_DEBUG) - backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; -#endif - backend_ref[i].bref_state = 0; - backend_ref[i].bref_backend = router->servers[i]; - /** store pointers to sescmd list to both cursors */ - backend_ref[i].bref_sescmd_cur.scmd_cur_rses = client_rses; - backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; - backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = - &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; - } - max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); - max_slave_rlag = rses_get_max_replication_lag(client_rses); - spinlock_init(&client_rses->rses_lock); + int max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); + int max_slave_rlag = rses_get_max_replication_lag(client_rses); + client_rses->rses_backend_ref = backend_ref; + client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ - /** - * Find a backend servers to connect to. - * This command requires that rsession's lock is held. - */ - - succp = rses_begin_locked_router_action(client_rses); - - if (!succp) + backend_ref_t *master_ref = NULL; /*< pointer to selected master */ + if (!select_connect_backend_servers(&master_ref, backend_ref, router_nservers, + max_nslaves, max_slave_rlag, + client_rses->rses_config.rw_slave_select_criteria, + session, router)) { + /** + * Master and at least slaves must be found if the router is + * in the strict mode. If sessions without master are allowed, only + * slaves must be found. + */ MXS_FREE(client_rses->rses_backend_ref); MXS_FREE(client_rses); - client_rses = NULL; - goto return_rses; - } - succp = select_connect_backend_servers(&master_ref, backend_ref, router_nservers, - max_nslaves, max_slave_rlag, - client_rses->rses_config.rw_slave_select_criteria, - session, router); - - rses_end_locked_router_action(client_rses); - - /** - * Master and at least slaves must be found if the router is - * in the strict mode. If sessions without master are allowed, only - * slaves must be found. - */ - if (!succp) - { - MXS_FREE(client_rses->rses_backend_ref); - MXS_FREE(client_rses); - client_rses = NULL; - goto return_rses; + return NULL; } /** Copy backend pointers to router session. */ client_rses->rses_master_ref = master_ref; - client_rses->rses_backend_ref = backend_ref; - client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ if (client_rses->rses_config.rw_max_slave_conn_percent) { @@ -564,35 +352,15 @@ static void *newSession(ROUTER *router_inst, SESSION *session) router->stats.n_sessions += 1; - /** - * Version is bigger than zero once initialized. - */ - atomic_add(&client_rses->rses_versno, 2); - ss_dassert(client_rses->rses_versno == 2); - /** - * Add this session to end of the list of active sessions in router. - */ - spinlock_acquire(&router->lock); - client_rses->next = router->connections; - router->connections = client_rses; - spinlock_release(&router->lock); - -return_rses: -#if defined(SS_DEBUG) - if (client_rses != NULL) - { - CHK_CLIENT_RSES(client_rses); - } -#endif return (void *)client_rses; } /** * @brief Close a router session (API). - * - * Close a session with the router, this is the mechanism by which a router - * may cleanup data structure etc. The instance of the router that relates to - * the relevant service is passed, along with the router session that is to + * + * Close a session with the router, this is the mechanism by which a router + * may cleanup data structure etc. The instance of the router that relates to + * the relevant service is passed, along with the router session that is to * be closed. Typically the function is used in conjunction with freeSession * which will release the resources used by a router session (see below). * @@ -601,67 +369,39 @@ return_rses: */ static void closeSession(ROUTER *instance, void *router_session) { - ROUTER_CLIENT_SES *router_cli_ses; - backend_ref_t *backend_ref; - - MXS_DEBUG("%lu [RWSplit:closeSession]", pthread_self()); - - /** - * router session can be NULL if newSession failed and it is discarding - * its connections and DCB's. - */ - if (router_session == NULL) - { - return; - } - router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); - backend_ref = router_cli_ses->rses_backend_ref; - /** - * Lock router client session for secure read and update. - */ - if (!router_cli_ses->rses_closed && - rses_begin_locked_router_action(router_cli_ses)) + if (!router_cli_ses->rses_closed && rses_begin_locked_router_action(router_cli_ses)) { - int i; /** - * This sets router closed. Nobody is allowed to use router - * without checking this first. + * Mark router session as closed. @c rses_closed is checked at the start + * of every API function to quickly stop the processing of closed sessions. */ router_cli_ses->rses_closed = true; - for (i = 0; i < router_cli_ses->rses_nbackends; i++) + for (int i = 0; i < router_cli_ses->rses_nbackends; i++) { - backend_ref_t *bref = &backend_ref[i]; - DCB *dcb = bref->bref_dcb; - /** Close those which had been connected */ + backend_ref_t *bref = &router_cli_ses->rses_backend_ref[i]; + if (BREF_IS_IN_USE(bref)) { + /** This backend is in use and it needs to be closed */ + DCB *dcb = bref->bref_dcb; CHK_DCB(dcb); -#if defined(SS_DEBUG) - /** - * session must be moved to SESSION_STATE_STOPPING state before - * router session is closed. - */ - if (dcb->session != NULL) - { - ss_dassert(dcb->session->state == SESSION_STATE_STOPPING); - } -#endif - /** Clean operation counter in bref and in SERVER */ + ss_dassert(dcb->session->state == SESSION_STATE_STOPPING); + if (BREF_IS_WAITING_RESULT(bref)) { + /** This backend was executing a query when the session was closed */ bref_clear_state(bref, BREF_WAITING_RESULT); } bref_clear_state(bref, BREF_IN_USE); bref_set_state(bref, BREF_CLOSED); - /** - * closes protocol and dcb - */ dcb_close(dcb); - /** decrease server current connection counters */ - atomic_add(&bref->bref_backend->backend_conn_count, -1); + + /** Decrease server reference connection count */ + atomic_add(&bref->ref->connections, -1); } else { @@ -677,15 +417,15 @@ static void closeSession(ROUTER *instance, void *router_session) } } } - /** Unlock */ + rses_end_locked_router_action(router_cli_ses); } } /** * @brief Free a router session (API). - * - * When a router session has been closed, freeSession can be called to free + * + * When a router session has been closed, freeSession can be called to free * allocated resources. * * @param router_instance The router instance the session belongs to @@ -694,40 +434,13 @@ static void closeSession(ROUTER *instance, void *router_session) */ static void freeSession(ROUTER *router_instance, void *router_client_session) { - ROUTER_CLIENT_SES *router_cli_ses; - ROUTER_INSTANCE *router; - int i; - - router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; - router = (ROUTER_INSTANCE *)router_instance; - - spinlock_acquire(&router->lock); - - if (router->connections == router_cli_ses) - { - router->connections = router_cli_ses->next; - } - else - { - ROUTER_CLIENT_SES *ptr = router->connections; - - while (ptr && ptr->next != router_cli_ses) - { - ptr = ptr->next; - } - - if (ptr) - { - ptr->next = router_cli_ses->next; - } - } - spinlock_release(&router->lock); + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; /** * For each property type, walk through the list, finalize properties * and free the allocated memory. */ - for (i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) + for (int i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) { rses_property_t *p = router_cli_ses->rses_properties[i]; rses_property_t *q = p; @@ -739,11 +452,7 @@ static void freeSession(ROUTER *router_instance, void *router_client_session) p = q; } } - /* - * We are no longer in the linked list, free - * all the memory and other resources associated - * to the client session. - */ + MXS_FREE(router_cli_ses->rses_backend_ref); MXS_FREE(router_cli_ses); return; @@ -801,21 +510,8 @@ static int routeQuery(ROUTER *instance, void *router_session, GWBUF *querybuf) */ static void diagnostics(ROUTER *instance, DCB *dcb) { - ROUTER_CLIENT_SES *router_cli_ses; ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; - int i = 0; - BACKEND *backend; char *weightby; - - spinlock_acquire(&router->lock); - router_cli_ses = router->connections; - while (router_cli_ses) - { - i++; - router_cli_ses = router_cli_ses->next; - } - spinlock_release(&router->lock); - double master_pct = 0.0, slave_pct = 0.0, all_pct = 0.0; if (router->stats.n_queries > 0) @@ -827,7 +523,8 @@ static void diagnostics(ROUTER *instance, DCB *dcb) dcb_printf(dcb, "\tNumber of router sessions: %d\n", router->stats.n_sessions); - dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", i); + dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", + router->service->stats.n_current); dcb_printf(dcb, "\tNumber of queries forwarded: %d\n", router->stats.n_queries); dcb_printf(dcb, "\tNumber of queries forwarded to master: %d (%.2f%%)\n", @@ -845,13 +542,12 @@ static void diagnostics(ROUTER *instance, DCB *dcb) dcb_printf(dcb, "\t\tServer Target %% Connections " "Operations\n"); dcb_printf(dcb, "\t\t Global Router\n"); - for (i = 0; router->servers[i]; i++) + for (SERVER_REF *ref = router->service->dbref; ref; ref = ref->next) { - backend = router->servers[i]; dcb_printf(dcb, "\t\t%-20s %3.1f%% %-6d %-6d %d\n", - backend->backend_server->unique_name, (float)backend->weight / 10, - backend->backend_server->stats.n_current, backend->backend_conn_count, - backend->backend_server->stats.n_current_ops); + ref->server->unique_name, (float)ref->weight / 10, + ref->server->stats.n_current, ref->connections, + ref->server->stats.n_current_ops); } } } @@ -885,7 +581,7 @@ static void clientReply(ROUTER *instance, void *router_session, GWBUF *writebuf, */ if (!rses_begin_locked_router_action(router_cli_ses)) { - print_error_packet(router_cli_ses, writebuf, backend_dcb); + gwbuf_free(writebuf); goto lock_failed; } /** Holding lock ensures that router session remains open */ @@ -936,7 +632,7 @@ static void clientReply(ROUTER *instance, void *router_session, GWBUF *writebuf, if (sescmd_cursor_is_active(scur)) { check_session_command_reply(writebuf, scur, bref); - + if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf)) { /** @@ -998,17 +694,15 @@ static void clientReply(ROUTER *instance, void *router_session, GWBUF *writebuf, { bool succp; - MXS_INFO("Backend %s:%d processed reply and starts to execute " - "active cursor.", bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + MXS_INFO("Backend %s:%d processed reply and starts to execute active cursor.", + bref->ref->server->name, bref->ref->server->port); succp = execute_sescmd_in_backend(bref); if (!succp) { MXS_INFO("Backend %s:%d failed to execute session command.", - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->ref->server->name, bref->ref->server->port); } } else if (bref->bref_pending_cmd != NULL) /*< non-sescmd is waiting to be routed */ @@ -1018,7 +712,7 @@ static void clientReply(ROUTER *instance, void *router_session, GWBUF *writebuf, CHK_GWBUF(bref->bref_pending_cmd); if ((ret = bref->bref_dcb->func.write(bref->bref_dcb, - gwbuf_clone(bref->bref_pending_cmd))) == 1) + gwbuf_clone(bref->bref_pending_cmd))) == 1) { ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; atomic_add(&inst->stats.n_queries, 1); @@ -1134,12 +828,12 @@ void rses_end_locked_router_action(ROUTER_CLIENT_SES *rses) /* * @brief Clear one or more bits in the backend reference state - * - * The router session holds details of the backend servers that are - * involved in the routing for this particular service. Each backend - * server has a state bit string, and this function (along with + * + * The router session holds details of the backend servers that are + * involved in the routing for this particular service. Each backend + * server has a state bit string, and this function (along with * bref_set_state) is used to manage the state. - * + * * @param bref The backend reference to be modified * @param state A bit string where the 1 bits indicate bits that should * be turned off in the bref state. @@ -1167,13 +861,13 @@ void bref_clear_state(backend_ref_t *bref, bref_state_t state) else { /** Decrease global operation count */ - prev2 = atomic_add(&bref->bref_backend->backend_server->stats.n_current_ops, -1); + prev2 = atomic_add(&bref->ref->server->stats.n_current_ops, -1); ss_dassert(prev2 > 0); if (prev2 <= 0) { MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", - __FUNCTION__, bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + __FUNCTION__, bref->ref->server->name, + bref->ref->server->port); } } } @@ -1183,12 +877,12 @@ void bref_clear_state(backend_ref_t *bref, bref_state_t state) /* * @brief Set one or more bits in the backend reference state - * - * The router session holds details of the backend servers that are - * involved in the routing for this particular service. Each backend - * server has a state bit string, and this function (along with + * + * The router session holds details of the backend servers that are + * involved in the routing for this particular service. Each backend + * server has a state bit string, and this function (along with * bref_clear_state) is used to manage the state. - * + * * @param bref The backend reference to be modified * @param state A bit string where the 1 bits indicate bits that should * be turned on in the bref state. @@ -1212,19 +906,16 @@ void bref_set_state(backend_ref_t *bref, bref_state_t state) if (prev1 < 0) { MXS_ERROR("[%s] Error: negative number of connections waiting for " - "results in backend %s:%u", - __FUNCTION__, bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + "results in backend %s:%u", __FUNCTION__, + bref->ref->server->name, bref->ref->server->port); } /** Increase global operation count */ - prev2 = - atomic_add(&bref->bref_backend->backend_server->stats.n_current_ops, 1); + prev2 = atomic_add(&bref->ref->server->stats.n_current_ops, 1); ss_dassert(prev2 >= 0); if (prev2 < 0) { MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", - __FUNCTION__, bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + __FUNCTION__, bref->ref->server->name, bref->ref->server->port); } } @@ -1233,9 +924,9 @@ void bref_set_state(backend_ref_t *bref, bref_state_t state) /** * @brief Free resources belonging to a property - * + * * Property is freed at the end of router client session. - * + * * @param prop The property whose resources are to be released */ void rses_property_done(rses_property_t *prop) @@ -1269,16 +960,16 @@ void rses_property_done(rses_property_t *prop) /** * @brief Get count of backend servers that are slaves. - * + * * Find out the number of read backend servers. * Depending on the configuration value type, either copy direct count * of slave connections or calculate the count from percentage value. - * + * * @param rses Router client session * @param router_nservers The number of backend servers in total */ int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses, - int router_nservers) + int router_nservers) { int conf_max_nslaves; int max_nslaves; @@ -1300,7 +991,7 @@ int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses, /* * @brief Get the maximum replication lag for this router - * + * * @param rses Router client session * @return Replication lag from configuration or very large number */ @@ -1325,10 +1016,10 @@ int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses) /** * @brief Find a back end reference that matches the given DCB - * + * * Finds out if there is a backend reference pointing at the DCB given as * parameter. - * + * * @param rses router client session * @param dcb DCB * @@ -1362,14 +1053,14 @@ backend_ref_t *get_bref_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb) /** * @brief Call hang up function - * + * * Calls hang-up function for DCB if it is not both running and in * master/slave/joined/ndb role. Called by DCB's callback routine. - * + * * @param dcb DCB relating to a backend server * @param reason The reason for the state change * @param data Data is a backend reference structure belonging to this router - * + * * @return 1 for success, 0 for failure */ int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data) @@ -1391,7 +1082,7 @@ int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data) bref = (backend_ref_t *)data; CHK_BACKEND_REF(bref); - srv = bref->bref_backend->backend_server; + srv = bref->ref->server; if (SERVER_IS_RUNNING(srv) && SERVER_IS_IN_CLUSTER(srv)) { @@ -1469,10 +1160,10 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router, if (c == UNDEFINED_CRITERIA) { MXS_ERROR("Unknown slave selection criteria \"%s\". " - "Allowed values are LEAST_GLOBAL_CONNECTIONS, " - "LEAST_ROUTER_CONNECTIONS, LEAST_BEHIND_MASTER," - "and LEAST_CURRENT_OPERATIONS.", - STRCRITERIA(router->rwsplit_config.rw_slave_select_criteria)); + "Allowed values are LEAST_GLOBAL_CONNECTIONS, " + "LEAST_ROUTER_CONNECTIONS, LEAST_BEHIND_MASTER," + "and LEAST_CURRENT_OPERATIONS.", + STRCRITERIA(router->rwsplit_config.rw_slave_select_criteria)); success = false; } else @@ -1530,9 +1221,9 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router, /** * @brief Router error handling routine (API) - * + * * Error Handler routine to resolve _backend_ failures. If it succeeds then - * there are enough operative backends available and connected. Otherwise it + * there are enough operative backends available and connected. Otherwise it * fails, and session is terminated. * * @param instance The router instance @@ -1595,8 +1286,8 @@ static void handleError(ROUTER *instance, void *router_session, if (!rses_begin_locked_router_action(rses)) { close_dcb = false; /* With the assumption that if the router session is closed, - * then so is the dcb. - */ + * then so is the dcb. + */ *succp = false; break; } @@ -1606,9 +1297,9 @@ static void handleError(ROUTER *instance, void *router_session, * handled so that session could continue. */ if (rses->rses_master_ref && rses->rses_master_ref->bref_dcb == problem_dcb && - !SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server)) + !SERVER_IS_MASTER(rses->rses_master_ref->ref->server)) { - SERVER *srv = rses->rses_master_ref->bref_backend->backend_server; + SERVER *srv = rses->rses_master_ref->ref->server; backend_ref_t *bref; bref = get_bref_from_dcb(rses, problem_dcb); if (bref != NULL) @@ -1693,8 +1384,8 @@ static void handleError(ROUTER *instance, void *router_session, if (close_dcb) { - dcb_close(problem_dcb); -} + dcb_close(problem_dcb); + } } /** @@ -1780,7 +1471,6 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, { ROUTER_CLIENT_SES *myrses; SESSION *ses; - int router_nservers; int max_nslaves; int max_slave_rlag; backend_ref_t *bref; @@ -1834,8 +1524,8 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, */ dcb_remove_callback(backend_dcb, DCB_REASON_NOT_RESPONDING, &router_handle_state_switch, (void *)bref); - router_nservers = router_get_servercount(inst); - max_nslaves = rses_get_max_slavecount(myrses, router_nservers); + + max_nslaves = rses_get_max_slavecount(myrses, myrses->rses_nbackends); max_slave_rlag = rses_get_max_replication_lag(myrses); /** * Try to get replacement slave or at least the minimum @@ -1843,13 +1533,13 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, */ if (inst->rwsplit_config.rw_disable_sescmd_hist) { - succp = have_enough_servers(&myrses, 1, router_nservers, inst) ? true : false; + succp = have_enough_servers(myrses, 1, myrses->rses_nbackends, inst) ? true : false; } else { succp = select_connect_backend_servers(&myrses->rses_master_ref, myrses->rses_backend_ref, - router_nservers, + myrses->rses_nbackends, max_nslaves, max_slave_rlag, myrses->rses_config.rw_slave_select_criteria, ses, inst); @@ -1859,26 +1549,6 @@ return_succp: return succp; } -/** - * @brief Calculate the number of backend servers - * - * @param inst Router instance - * - * @return int - count of servers - */ -static int router_get_servercount(ROUTER_INSTANCE *inst) -{ - int router_nservers = 0; - BACKEND **b = inst->servers; - /** count servers */ - while (*(b++) != NULL) - { - router_nservers++; - } - - return router_nservers; -} - /** * @brief Calculate whether we have enough servers to route a query * @@ -1889,16 +1559,16 @@ static int router_get_servercount(ROUTER_INSTANCE *inst) * * @return bool - whether enough, side effect is error logging */ -static bool have_enough_servers(ROUTER_CLIENT_SES **p_rses, const int min_nsrv, +static bool have_enough_servers(ROUTER_CLIENT_SES *rses, const int min_nsrv, int router_nsrv, ROUTER_INSTANCE *router) { bool succp; /** With too few servers session is not created */ if (router_nsrv < min_nsrv || - MXS_MAX((*p_rses)->rses_config.rw_max_slave_conn_count, - (router_nsrv * (*p_rses)->rses_config.rw_max_slave_conn_percent) / - 100) < min_nsrv) + MXS_MAX((rses)->rses_config.rw_max_slave_conn_count, + (router_nsrv * (rses)->rses_config.rw_max_slave_conn_percent) / + 100) < min_nsrv) { if (router_nsrv < min_nsrv) { @@ -1909,16 +1579,16 @@ static bool have_enough_servers(ROUTER_CLIENT_SES **p_rses, const int min_nsrv, } else { - int pct = (*p_rses)->rses_config.rw_max_slave_conn_percent / 100; + int pct = (rses)->rses_config.rw_max_slave_conn_percent / 100; int nservers = router_nsrv * pct; - if ((*p_rses)->rses_config.rw_max_slave_conn_count < min_nsrv) + if ((rses)->rses_config.rw_max_slave_conn_count < min_nsrv) { MXS_ERROR("Unable to start %s service. There are " "too few backend servers configured in " "MaxScale.cnf. Found %d when %d is required.", router->service->name, - (*p_rses)->rses_config.rw_max_slave_conn_count, min_nsrv); + (rses)->rses_config.rw_max_slave_conn_count, min_nsrv); } if (nservers < min_nsrv) { @@ -1928,11 +1598,9 @@ static bool have_enough_servers(ROUTER_CLIENT_SES **p_rses, const int min_nsrv, "MaxScale.cnf. Found %d%% when at least %.0f%% " "would be required.", router->service->name, - (*p_rses)->rses_config.rw_max_slave_conn_percent, dbgpct); + (rses)->rses_config.rw_max_slave_conn_percent, dbgpct); } } - MXS_FREE(*p_rses); - *p_rses = NULL; succp = false; } else @@ -1942,62 +1610,11 @@ static bool have_enough_servers(ROUTER_CLIENT_SES **p_rses, const int min_nsrv, return succp; } -#if defined(PREP_STMT_CACHING) -#define MAX_STMT_LEN 1024 - -static prep_stmt_t *prep_stmt_init(prep_stmt_type_t type, void *id) -{ - prep_stmt_t *pstmt; - - pstmt = (prep_stmt_t *)MXS_CALLOC(1, sizeof(prep_stmt_t)); - - if (pstmt != NULL) - { -#if defined(SS_DEBUG) - pstmt->pstmt_chk_top = CHK_NUM_PREP_STMT; - pstmt->pstmt_chk_tail = CHK_NUM_PREP_STMT; -#endif - pstmt->pstmt_state = PREP_STMT_ALLOC; - pstmt->pstmt_type = type; - - if (type == PREP_STMT_NAME) - { - pstmt->pstmt_id.name = strndup((char *)id, MAX_STMT_LEN); - } - else - { - pstmt->pstmt_id.seq = 0; - } - } - CHK_PREP_STMT(pstmt); - return pstmt; -} - -static void prep_stmt_done(prep_stmt_t *pstmt) -{ - CHK_PREP_STMT(pstmt); - - if (pstmt->pstmt_type == PREP_STMT_NAME) - { - MXS_FREE(pstmt->pstmt_id.name); - } - MXS_FREE(pstmt); -} - -static bool prep_stmt_drop(prep_stmt_t *pstmt) -{ - CHK_PREP_STMT(pstmt); - - pstmt->pstmt_state = PREP_STMT_DROPPED; - return true; -} -#endif /*< PREP_STMT_CACHING */ - /** * @brief Refresh the instance by the given parameter value. * * Used by createInstance and newSession - * + * * @param router Router instance * @param singleparam Parameter fo be reloaded * @@ -2097,67 +1714,75 @@ static void refreshInstance(ROUTER_INSTANCE *router, } param = param->next; } - -#if defined(NOT_USED) /*< can't read monitor config parameters */ - if ((*router->servers)->backend_server->rlag == -2) - { - rlag_enabled = false; - } - else - { - rlag_enabled = true; - } - /** - * If replication lag detection is not enabled the measure can't be - * used in slave selection. - */ - if (!rlag_enabled) - { - if (rlag_limited) - { - MXS_WARNING("Configuration Failed, max_slave_replication_lag " - "is set to %d,\n\t\t but detect_replication_lag " - "is not enabled. Replication lag will not be checked.", - router->rwsplit_config.rw_max_slave_replication_lag); - } - - if (router->rwsplit_config.rw_slave_select_criteria == - LEAST_BEHIND_MASTER) - { - MXS_WARNING("Configuration Failed, router option " - "\n\t\t slave_selection_criteria=LEAST_BEHIND_MASTER " - "is specified, but detect_replication_lag " - "is not enabled.\n\t\t " - "slave_selection_criteria=%s will be used instead.", - STRCRITERIA(DEFAULT_CRITERIA)); - - router->rwsplit_config.rw_slave_select_criteria = DEFAULT_CRITERIA; - } - } -#endif /*< NOT_USED */ } /* * @brief Release resources when createInstance fails to complete - * + * * Internal to createInstance - * + * * @param router Router instance - * + * */ static void free_rwsplit_instance(ROUTER_INSTANCE *router) { if (router) { - if (router->servers) - { - for (int i = 0; router->servers[i]; i++) - { - MXS_FREE(router->servers[i]); - } - } - MXS_FREE(router->servers); MXS_FREE(router); } } +/** + * @brief Create backend server references + * + * This creates a new set of backend references for the client session. Currently + * this is only used on startup but it could be used to dynamically change the + * set of used servers. + * + * @param rses Client router session + * @param dest Destination where the array of backens is stored + * @param n_backend Number of items in the array + * @return True on success, false on error + */ +static bool create_backends(ROUTER_CLIENT_SES *rses, backend_ref_t** dest, int* n_backend) +{ + backend_ref_t *backend_ref = (backend_ref_t *)MXS_CALLOC(1, *n_backend * sizeof(backend_ref_t)); + + if (backend_ref == NULL) + { + return false; + } + + int i = 0; + + for (SERVER_REF *sref = rses->router->service->dbref; sref && i < *n_backend; sref = sref->next) + { + if (sref->active) + { +#if defined(SS_DEBUG) + backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; +#endif + backend_ref[i].bref_state = 0; + backend_ref[i].ref = sref; + /** store pointers to sescmd list to both cursors */ + backend_ref[i].bref_sescmd_cur.scmd_cur_rses = rses; + backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; + backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = + &rses->rses_properties[RSES_PROP_TYPE_SESCMD]; + backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; + i++; + } + } + + if (i < *n_backend) + { + MXS_INFO("The service reported %d servers but only took %d into use.", *n_backend, i); + *n_backend = i; + } + + *dest = backend_ref; + return true; +} diff --git a/server/modules/routing/readwritesplit/readwritesplit.h b/server/modules/routing/readwritesplit/readwritesplit.h index 98020b667..5e24a4162 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.h +++ b/server/modules/routing/readwritesplit/readwritesplit.h @@ -32,26 +32,6 @@ MXS_BEGIN_DECLS -#undef PREP_STMT_CACHING - -#if defined(PREP_STMT_CACHING) - -typedef enum prep_stmt_type -{ - PREP_STMT_NAME, - PREP_STMT_ID -} prep_stmt_type_t; - -typedef enum prep_stmt_state -{ - PREP_STMT_ALLOC, - PREP_STMT_SENT, - PREP_STMT_RECV, - PREP_STMT_DROPPED -} prep_stmt_state_t; - -#endif /*< PREP_STMT_CACHING */ - typedef enum bref_state { BREF_IN_USE = 0x01, @@ -199,27 +179,6 @@ typedef struct sescmd_cursor_st #endif } sescmd_cursor_t; -/** - * Internal structure used to define the set of backend servers we are routing - * connections to. This provides the storage for routing module specific data - * that is required for each of the backend servers. - * - * Owned by router_instance, referenced by each routing session. - */ -typedef struct backend_st -{ -#if defined(SS_DEBUG) - skygw_chk_t be_chk_top; -#endif - SERVER* backend_server; /*< The server itself */ - int backend_conn_count; /*< Number of connections to the server */ - bool be_valid; /*< Valid when belongs to the router's configuration */ - int weight; /*< Desired weighting on the load. Expressed in .1% increments */ -#if defined(SS_DEBUG) - skygw_chk_t be_chk_tail; -#endif -} BACKEND; - /** * Reference to BACKEND. * @@ -230,7 +189,7 @@ typedef struct backend_ref_st #if defined(SS_DEBUG) skygw_chk_t bref_chk_top; #endif - BACKEND* bref_backend; + SERVER_REF* ref; DCB* bref_dcb; bref_state_t bref_state; int bref_num_result_wait; @@ -302,7 +261,6 @@ struct router_client_session skygw_chk_t rses_chk_top; #endif SPINLOCK rses_lock; /*< protects rses_deleted */ - int rses_versno; /*< even = no active update, else odd. not used 4/14 */ bool rses_closed; /*< true when closeSession is called */ rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; /*< Properties listed by their type */ backend_ref_t* rses_master_ref; @@ -346,14 +304,10 @@ typedef struct typedef struct router_instance { SERVICE* service; /*< Pointer to service */ - ROUTER_CLIENT_SES* connections; /*< List of client connections */ SPINLOCK lock; /*< Lock for the instance data */ - BACKEND** servers; /*< Backend servers */ - BACKEND* master; /*< NULL or pointer */ rwsplit_config_t rwsplit_config; /*< expanded config info from SERVICE */ int rwsplit_version; /*< version number for router's config */ ROUTER_STATS stats; /*< Statistics for this router */ - struct router_instance* next; /*< Next router on the list */ bool available_slaves; /*< The router has some slaves avialable */ } ROUTER_INSTANCE; diff --git a/server/modules/routing/readwritesplit/rwsplit_mysql.c b/server/modules/routing/readwritesplit/rwsplit_mysql.c index dbad88573..6f1b31410 100644 --- a/server/modules/routing/readwritesplit/rwsplit_mysql.c +++ b/server/modules/routing/readwritesplit/rwsplit_mysql.c @@ -366,66 +366,6 @@ void live_session_reply(GWBUF **querybuf, ROUTER_CLIENT_SES *rses) } } -/* - * Uses MySQL specific mechanisms - */ -/** - * @brief Write an error message to the log for session lock failure - * - * This happens when processing a client reply and the session cannot be - * locked. - * - * @param rses Router session - * @param buf Query buffer containing reply data - * @param dcb The backend DCB that sent the reply - */ -void print_error_packet(ROUTER_CLIENT_SES *rses, GWBUF *buf, DCB *dcb) -{ -#if defined(SS_DEBUG) - if (GWBUF_IS_TYPE_MYSQL(buf)) - { - while (gwbuf_length(buf) > 0) - { - /** - * This works with MySQL protocol only ! - * Protocol specific packet print functions would be nice. - */ - uint8_t *ptr = GWBUF_DATA(buf); - size_t len = MYSQL_GET_PACKET_LEN(ptr); - - if (MYSQL_GET_COMMAND(ptr) == 0xff) - { - SERVER *srv = NULL; - backend_ref_t *bref = rses->rses_backend_ref; - int i; - char *bufstr; - - for (i = 0; i < rses->rses_nbackends; i++) - { - if (bref[i].bref_dcb == dcb) - { - srv = bref[i].bref_backend->backend_server; - } - } - ss_dassert(srv != NULL); - char *str = (char *)&ptr[7]; - bufstr = strndup(str, len - 3); - - MXS_ERROR("Backend server %s:%d responded with " - "error : %s", - srv->name, srv->port, bufstr); - MXS_FREE(bufstr); - } - buf = gwbuf_consume(buf, len + 4); - } - } - else - { - gwbuf_free(buf); - } -#endif /*< SS_DEBUG */ -} - /* * Uses MySQL specific mechanisms */ @@ -453,8 +393,8 @@ void check_session_command_reply(GWBUF *writebuf, sescmd_cursor_t *scur, backend ss_dassert(len + 4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf)); MXS_ERROR("Failed to execute session command in %s:%d. Error was: %s %s", - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port, err, replystr); + bref->ref->server->name, + bref->ref->server->port, err, replystr); MXS_FREE(err); MXS_FREE(replystr); } diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c index 73f0120f0..298358b4f 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c @@ -84,7 +84,7 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, * transaction is committed and autocommit is enabled again. */ if (rses->rses_autocommit_enabled && - QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) { rses->rses_autocommit_enabled = false; @@ -94,7 +94,7 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, } } else if (!rses->rses_transaction_active && - QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX)) + qc_query_is_type(qtype, QUERY_TYPE_BEGIN_TRX)) { rses->rses_transaction_active = true; } @@ -102,13 +102,13 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, * Explicit COMMIT and ROLLBACK, implicit COMMIT. */ if (rses->rses_autocommit_enabled && rses->rses_transaction_active && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_COMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_ROLLBACK))) + (qc_query_is_type(qtype, QUERY_TYPE_COMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_ROLLBACK))) { rses->rses_transaction_active = false; } else if (!rses->rses_autocommit_enabled && - QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) { rses->rses_autocommit_enabled = true; rses->rses_transaction_active = false; @@ -253,10 +253,10 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, BREF_IS_IN_USE((&backend_ref[i]))) { MXS_INFO("Route query to %s \t%s:%d%s", - (SERVER_IS_MASTER(backend_ref[i].bref_backend->backend_server) + (SERVER_IS_MASTER(backend_ref[i].ref->server) ? "master" : "slave"), - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port, + backend_ref[i].ref->server->name, + backend_ref[i].ref->server->port, (i + 1 == router_cli_ses->rses_nbackends ? " <" : " ")); } @@ -368,10 +368,10 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { MXS_INFO("Route query to %s \t%s:%d%s", - (SERVER_IS_MASTER(backend_ref[i].bref_backend->backend_server) + (SERVER_IS_MASTER(backend_ref[i].ref->server) ? "master" : "slave"), - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port, + backend_ref[i].ref->server->name, + backend_ref[i].ref->server->port, (i + 1 == router_cli_ses->rses_nbackends ? " <" : " ")); } @@ -391,8 +391,8 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, { nsucc += 1; MXS_INFO("Backend %s:%d already executing sescmd.", - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port); + backend_ref[i].ref->server->name, + backend_ref[i].ref->server->port); } else { @@ -403,8 +403,8 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, else { MXS_ERROR("Failed to execute session command in %s:%d", - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port); + backend_ref[i].ref->server->name, + backend_ref[i].ref->server->port); } } } @@ -533,9 +533,9 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, for (i = 0; i < rses->rses_nbackends; i++) { - BACKEND *b = backend_ref[i].bref_backend; + SERVER_REF *b = backend_ref[i].ref; SERVER server; - server.status = backend_ref[i].bref_backend->backend_server->status; + server.status = b->server->status; /** * To become chosen: * backend must be in use, name must match, @@ -543,7 +543,8 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, * server, or master. */ if (BREF_IS_IN_USE((&backend_ref[i])) && - (strncasecmp(name, b->backend_server->unique_name, PATH_MAX) == 0) && + SERVER_REF_IS_ACTIVE(b) && + (strncasecmp(name, b->server->unique_name, PATH_MAX) == 0) && (SERVER_IS_SLAVE(&server) || SERVER_IS_RELAY_SERVER(&server) || SERVER_IS_MASTER(&server))) { @@ -569,15 +570,15 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, for (i = 0; i < rses->rses_nbackends; i++) { - BACKEND *b = (&backend_ref[i])->bref_backend; + SERVER_REF *b = backend_ref[i].ref; SERVER server; SERVER candidate; - server.status = backend_ref[i].bref_backend->backend_server->status; + server.status = b->server->status; /** * Unused backend or backend which is not master nor * slave can't be used */ - if (!BREF_IS_IN_USE(&backend_ref[i]) || + if (!BREF_IS_IN_USE(&backend_ref[i]) || !SERVER_REF_IS_ACTIVE(b) || (!SERVER_IS_MASTER(&server) && !SERVER_IS_SLAVE(&server))) { continue; @@ -596,7 +597,7 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, { /** found master */ candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->bref_backend->backend_server->status; + candidate.status = candidate_bref->ref->server->status; succp = true; } /** @@ -605,12 +606,12 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, * maximum allowed replication lag. */ else if (max_rlag == MAX_RLAG_UNDEFINED || - (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag)) + (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->server->rlag <= max_rlag)) { /** found slave */ candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->bref_backend->backend_server->status; + candidate.status = candidate_bref->ref->server->status; succp = true; } } @@ -620,13 +621,13 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, */ else if (SERVER_IS_MASTER(&candidate) && SERVER_IS_SLAVE(&server) && (max_rlag == MAX_RLAG_UNDEFINED || - (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag)) && + (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->server->rlag <= max_rlag)) && !rses->rses_config.rw_master_reads) { /** found slave */ candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->bref_backend->backend_server->status; + candidate.status = candidate_bref->ref->server->status; succp = true; } /** @@ -637,21 +638,17 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, else if (SERVER_IS_SLAVE(&server)) { if (max_rlag == MAX_RLAG_UNDEFINED || - (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag)) + (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->server->rlag <= max_rlag)) { - candidate_bref = - check_candidate_bref(candidate_bref, &backend_ref[i], - rses->rses_config.rw_slave_select_criteria); - candidate.status = - candidate_bref->bref_backend->backend_server->status; + candidate_bref = check_candidate_bref(candidate_bref, &backend_ref[i], + rses->rses_config.rw_slave_select_criteria); + candidate.status = candidate_bref->ref->server->status; } else { - MXS_INFO("Server %s:%d is too much behind the " - "master, %d s. and can't be chosen.", - b->backend_server->name, b->backend_server->port, - b->backend_server->rlag); + MXS_INFO("Server %s:%d is too much behind the master, %d s. and can't be chosen.", + b->server->name, b->server->port, b->server->rlag); } } } /*< for */ @@ -669,27 +666,37 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, */ if (btype == BE_MASTER) { - if (master_bref) + if (master_bref && SERVER_REF_IS_ACTIVE(master_bref->ref)) { /** It is possible for the server status to change at any point in time * so copying it locally will make possible error messages * easier to understand */ SERVER server; - server.status = master_bref->bref_backend->backend_server->status; - if (BREF_IS_IN_USE(master_bref) && SERVER_IS_MASTER(&server)) + server.status = master_bref->ref->server->status; + + if (BREF_IS_IN_USE(master_bref)) { - *p_dcb = master_bref->bref_dcb; - succp = true; - /** if bref is in use DCB should not be closed */ - ss_dassert(master_bref->bref_dcb->state != DCB_STATE_ZOMBIE); + if (SERVER_IS_MASTER(&server)) + { + *p_dcb = master_bref->bref_dcb; + succp = true; + /** if bref is in use DCB should not be closed */ + ss_dassert(master_bref->bref_dcb->state != DCB_STATE_ZOMBIE); + } + else + { + MXS_ERROR("Server '%s' should be master but " + "is %s instead and can't be chosen as the master.", + master_bref->ref->server->unique_name, + STRSRVSTATUS(&server)); + succp = false; + } } else { - MXS_ERROR("Server at %s:%d should be master but " - "is %s instead and can't be chosen to master.", - master_bref->bref_backend->backend_server->name, - master_bref->bref_backend->backend_server->port, - STRSRVSTATUS(&server)); + MXS_ERROR("Server '%s' is not in use and can't be " + "chosen as the master.", + master_bref->ref->server->unique_name); succp = false; } } @@ -727,13 +734,13 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, * These queries are not affected by hints */ else if (!load_active && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || + (qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) || /** Configured to allow writing variables to all nodes */ (use_sql_variables_in == TYPE_ALL && - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE)) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE)) || /** enable or disable autocommit are always routed to all */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT))) + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT))) { /** * This is problematic query because it would be routed to all @@ -750,9 +757,9 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, * the execution of the prepared statements to the right server would be * an easy one. Currently this is not supported. */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && - !(QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT))) + if (qc_query_is_type(qtype, QUERY_TYPE_READ) && + !(qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT))) { MXS_WARNING("The query can't be routed to all " "backend servers because it includes SELECT and " @@ -770,40 +777,40 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, * Hints may affect on routing of the following queries */ else if (!trx_active && !load_active && - !QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || /*< any SELECT */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ - QUERY_IS_TYPE(qtype, - QUERY_TYPE_USERVAR_READ) || /*< read user var */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || /*< read sys var */ - QUERY_IS_TYPE(qtype, - QUERY_TYPE_EXEC_STMT) || /*< prepared stmt exec */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || - QUERY_IS_TYPE(qtype, - QUERY_TYPE_GSYSVAR_READ))) /*< read global sys var */ + !qc_query_is_type(qtype, QUERY_TYPE_WRITE) && + (qc_query_is_type(qtype, QUERY_TYPE_READ) || /*< any SELECT */ + qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ + qc_query_is_type(qtype, + QUERY_TYPE_USERVAR_READ) || /*< read user var */ + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || /*< read sys var */ + qc_query_is_type(qtype, + QUERY_TYPE_EXEC_STMT) || /*< prepared stmt exec */ + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || + qc_query_is_type(qtype, + QUERY_TYPE_GSYSVAR_READ))) /*< read global sys var */ { /** First set expected targets before evaluating hints */ - if (!QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ + if (!qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) && + (qc_query_is_type(qtype, QUERY_TYPE_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ /** Configured to allow reading variables from slaves */ (use_sql_variables_in == TYPE_ALL && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ))))) + (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ))))) { target = TARGET_SLAVE; } - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_EXEC_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || + if (qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) || + qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || /** Configured not to allow reading variables from slaves */ (use_sql_variables_in == TYPE_MASTER && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ)))) + (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ)))) { target = TARGET_MASTER; } @@ -818,26 +825,26 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, { /** hints don't affect on routing */ ss_dassert(trx_active || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) && + (qc_query_is_type(qtype, QUERY_TYPE_WRITE) || + qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) || + (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) && use_sql_variables_in == TYPE_MASTER) || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) && + (qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) && use_sql_variables_in == TYPE_MASTER) || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ) && + (qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ) && use_sql_variables_in == TYPE_MASTER) || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE) && + (qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) && use_sql_variables_in == TYPE_MASTER) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_ROLLBACK) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_COMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_EXEC_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_CREATE_TMP_TABLE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_READ_TMP_TABLE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_UNKNOWN))); + qc_query_is_type(qtype, QUERY_TYPE_BEGIN_TRX) || + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_ROLLBACK) || + qc_query_is_type(qtype, QUERY_TYPE_COMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_CREATE_TMP_TABLE) || + qc_query_is_type(qtype, QUERY_TYPE_READ_TMP_TABLE) || + qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN))); target = TARGET_MASTER; } @@ -895,9 +902,6 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, hint = hint->next; } /*< while (hint != NULL) */ -#if defined(SS_EXTRA_DEBUG) - MXS_INFO("Selected target \"%s\"", STRTARGET(target)); -#endif return target; } @@ -1102,9 +1106,6 @@ bool handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, */ if (rwsplit_get_dcb(target_dcb, rses, BE_SLAVE, NULL, rlag_max)) { -#if defined(SS_EXTRA_DEBUG) - MXS_INFO("Found DCB for slave."); -#endif atomic_add(&inst->stats.n_slave, 1); return true; } @@ -1191,9 +1192,8 @@ handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, ss_dassert(target_dcb != NULL); MXS_INFO("Route query to %s \t%s:%d <", - (SERVER_IS_MASTER(bref->bref_backend->backend_server) ? "master" - : "slave"), bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + (SERVER_IS_MASTER(bref->ref->server) ? "master" + : "slave"), bref->ref->server->name, bref->ref->server->port); /** * Store current stmt if execution of previous session command * haven't completed yet. @@ -1372,14 +1372,13 @@ static backend_ref_t *get_root_master_bref(ROUTER_CLIENT_SES *rses) if (bref == rses->rses_master_ref) { /** Store master state for better error reporting */ - master.status = bref->bref_backend->backend_server->status; + master.status = bref->ref->server->status; } - if (bref->bref_backend->backend_server->status & SERVER_MASTER) + if (SERVER_IS_MASTER(bref->ref->server)) { if (candidate_bref == NULL || - (bref->bref_backend->backend_server->depth < - candidate_bref->bref_backend->backend_server->depth)) + (bref->ref->server->depth < candidate_bref->ref->server->depth)) { candidate_bref = bref; } diff --git a/server/modules/routing/readwritesplit/rwsplit_select_backends.c b/server/modules/routing/readwritesplit/rwsplit_select_backends.c index 1198dec52..35875be65 100644 --- a/server/modules/routing/readwritesplit/rwsplit_select_backends.c +++ b/server/modules/routing/readwritesplit/rwsplit_select_backends.c @@ -38,7 +38,7 @@ static bool connect_server(backend_ref_t *bref, SESSION *session, bool execute_h static void log_server_connections(select_criteria_t select_criteria, backend_ref_t *backend_ref, int router_nservers); -static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers); +static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers); static int bref_cmp_global_conn(const void *bref1, const void *bref2); @@ -103,10 +103,11 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, } /* get the root Master */ - BACKEND *master_host = get_root_master(backend_ref, router_nservers); + SERVER_REF *master_host = get_root_master(backend_ref, router_nservers); if (router->rwsplit_config.rw_master_failure_mode == RW_FAIL_INSTANTLY && - (master_host == NULL || SERVER_IS_DOWN(master_host->backend_server))) + (master_host == NULL || !SERVER_REF_IS_ACTIVE(master_host) || + SERVER_IS_DOWN(master_host->server))) { MXS_ERROR("Couldn't find suitable Master from %d candidates.", router_nservers); return false; @@ -145,9 +146,11 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, for (int i = 0; i < router_nservers && (slaves_connected < max_nslaves || !master_connected); i++) { - SERVER *serv = backend_ref[i].bref_backend->backend_server; + SERVER *serv = backend_ref[i].ref->server; - if (!BREF_HAS_FAILED(&backend_ref[i]) && SERVER_IS_RUNNING(serv)) + if (!BREF_HAS_FAILED(&backend_ref[i]) && + SERVER_REF_IS_ACTIVE(backend_ref[i].ref) && + SERVER_IS_RUNNING(serv)) { /* check also for relay servers and don't take the master_host */ if (slaves_found < max_nslaves && @@ -155,7 +158,7 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, (serv->rlag != MAX_RLAG_NOT_AVAILABLE && serv->rlag <= max_slave_rlag)) && (SERVER_IS_SLAVE(serv) || SERVER_IS_RELAY_SERVER(serv)) && - (master_host == NULL || (serv != master_host->backend_server))) + (master_host == NULL || (serv != master_host->server))) { slaves_found += 1; @@ -166,7 +169,7 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, } } /* take the master_host for master */ - else if (master_host && (serv == master_host->backend_server)) + else if (master_host && (serv == master_host->server)) { /** p_master_ref must be assigned with this backend_ref pointer * because its original value may have been lost when backend @@ -205,9 +208,9 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, if (BREF_IS_IN_USE((&backend_ref[i]))) { MXS_INFO("Selected %s in \t%s:%d", - STRSRVSTATUS(backend_ref[i].bref_backend->backend_server), - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port); + STRSRVSTATUS(backend_ref[i].ref->server), + backend_ref[i].ref->server->name, + backend_ref[i].ref->server->port); } } /* for */ } @@ -226,12 +229,12 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, { if (BREF_IS_IN_USE((&backend_ref[i]))) { - ss_dassert(backend_ref[i].bref_backend->backend_conn_count > 0); + ss_dassert(backend_ref[i].ref->connections > 0); /** disconnect opened connections */ bref_clear_state(&backend_ref[i], BREF_IN_USE); /** Decrease backend's connection counter. */ - atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); + atomic_add(&backend_ref[i].ref->connections, -1); dcb_close(backend_ref[i].bref_dcb); } } @@ -243,13 +246,12 @@ bool select_connect_backend_servers(backend_ref_t **p_master_ref, /** Compare number of connections from this router in backend servers */ static int bref_cmp_router_conn(const void *bref1, const void *bref2) { - BACKEND *b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND *b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; + SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; if (b1->weight == 0 && b2->weight == 0) { - return b1->backend_server->stats.n_current - - b2->backend_server->stats.n_current; + return b1->connections - b2->connections; } else if (b1->weight == 0) { @@ -260,20 +262,20 @@ static int bref_cmp_router_conn(const void *bref1, const void *bref2) return -1; } - return ((1000 + 1000 * b1->backend_conn_count) / b1->weight) - - ((1000 + 1000 * b2->backend_conn_count) / b2->weight); + return ((1000 + 1000 * b1->connections) / b1->weight) - + ((1000 + 1000 * b2->connections) / b2->weight); } /** Compare number of global connections in backend servers */ static int bref_cmp_global_conn(const void *bref1, const void *bref2) { - BACKEND *b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND *b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; + SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; if (b1->weight == 0 && b2->weight == 0) { - return b1->backend_server->stats.n_current - - b2->backend_server->stats.n_current; + return b1->server->stats.n_current - + b2->server->stats.n_current; } else if (b1->weight == 0) { @@ -284,32 +286,29 @@ static int bref_cmp_global_conn(const void *bref1, const void *bref2) return -1; } - return ((1000 + 1000 * b1->backend_server->stats.n_current) / b1->weight) - - ((1000 + 1000 * b2->backend_server->stats.n_current) / b2->weight); + return ((1000 + 1000 * b1->server->stats.n_current) / b1->weight) - + ((1000 + 1000 * b2->server->stats.n_current) / b2->weight); } /** Compare replication lag between backend servers */ static int bref_cmp_behind_master(const void *bref1, const void *bref2) { - BACKEND *b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND *b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; + SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; - return ((b1->backend_server->rlag < b2->backend_server->rlag) ? -1 - : ((b1->backend_server->rlag > b2->backend_server->rlag) ? 1 : 0)); + return b1->server->rlag - b2->server->rlag; } /** Compare number of current operations in backend servers */ static int bref_cmp_current_load(const void *bref1, const void *bref2) { - SERVER *s1 = ((backend_ref_t *)bref1)->bref_backend->backend_server; - SERVER *s2 = ((backend_ref_t *)bref2)->bref_backend->backend_server; - BACKEND *b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND *b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; + SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; if (b1->weight == 0 && b2->weight == 0) { - return b1->backend_server->stats.n_current - - b2->backend_server->stats.n_current; + // TODO: Fix this so that operations are used instead of connections + return b1->server->stats.n_current - b2->server->stats.n_current; } else if (b1->weight == 0) { @@ -320,8 +319,8 @@ static int bref_cmp_current_load(const void *bref1, const void *bref2) return -1; } - return ((1000 * s1->stats.n_current_ops) - b1->weight) - - ((1000 * s2->stats.n_current_ops) - b2->weight); + return ((1000 * b1->server->stats.n_current_ops) - b1->weight) - + ((1000 * b2->server->stats.n_current_ops) - b2->weight); } /** @@ -338,7 +337,7 @@ static int bref_cmp_current_load(const void *bref1, const void *bref2) */ static bool connect_server(backend_ref_t *bref, SESSION *session, bool execute_history) { - SERVER *serv = bref->bref_backend->backend_server; + SERVER *serv = bref->ref->server; bool rval = false; bref->bref_dcb = dcb_connect(serv, session, serv->protocol); @@ -354,16 +353,16 @@ static bool connect_server(backend_ref_t *bref, SESSION *session, bool execute_h &router_handle_state_switch, (void *) bref); bref->bref_state = 0; bref_set_state(bref, BREF_IN_USE); - atomic_add(&bref->bref_backend->backend_conn_count, 1); + atomic_add(&bref->ref->connections, 1); rval = true; } else { MXS_ERROR("Failed to execute session command in %s (%s:%d). See earlier " "errors for more details.", - bref->bref_backend->backend_server->unique_name, - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->ref->server->unique_name, + bref->ref->server->name, + bref->ref->server->port); dcb_close(bref->bref_dcb); bref->bref_dcb = NULL; } @@ -398,33 +397,33 @@ static void log_server_connections(select_criteria_t select_criteria, for (int i = 0; i < router_nservers; i++) { - BACKEND *b = backend_ref[i].bref_backend; + SERVER_REF *b = backend_ref[i].ref; switch (select_criteria) { case LEAST_GLOBAL_CONNECTIONS: MXS_INFO("MaxScale connections : %d in \t%s:%d %s", - b->backend_server->stats.n_current, b->backend_server->name, - b->backend_server->port, STRSRVSTATUS(b->backend_server)); + b->server->stats.n_current, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); break; case LEAST_ROUTER_CONNECTIONS: MXS_INFO("RWSplit connections : %d in \t%s:%d %s", - b->backend_conn_count, b->backend_server->name, - b->backend_server->port, STRSRVSTATUS(b->backend_server)); + b->connections, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); break; case LEAST_CURRENT_OPERATIONS: MXS_INFO("current operations : %d in \t%s:%d %s", - b->backend_server->stats.n_current_ops, - b->backend_server->name, b->backend_server->port, - STRSRVSTATUS(b->backend_server)); + b->server->stats.n_current_ops, + b->server->name, b->server->port, + STRSRVSTATUS(b->server)); break; case LEAST_BEHIND_MASTER: MXS_INFO("replication lag : %d in \t%s:%d %s", - b->backend_server->rlag, b->backend_server->name, - b->backend_server->port, STRSRVSTATUS(b->backend_server)); + b->server->rlag, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); default: break; } @@ -445,27 +444,26 @@ static void log_server_connections(select_criteria_t select_criteria, * @return The Master found * */ -static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers) +static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers) { int i = 0; - BACKEND *master_host = NULL; + SERVER_REF *master_host = NULL; for (i = 0; i < router_nservers; i++) { - BACKEND *b; - - if (servers[i].bref_backend == NULL) + if (servers[i].ref == NULL) { + /** This should not happen */ + ss_dassert(false); continue; } - b = servers[i].bref_backend; + SERVER_REF *b = servers[i].ref; - if ((b->backend_server->status & (SERVER_MASTER | SERVER_MAINT)) == - SERVER_MASTER) + if (SERVER_IS_MASTER(b->server)) { if (master_host == NULL || - (b->backend_server->depth < master_host->backend_server->depth)) + (b->server->depth < master_host->server->depth)) { master_host = b; } diff --git a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c b/server/modules/routing/readwritesplit/rwsplit_session_cmd.c index b37951543..f06bf96e0 100644 --- a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c +++ b/server/modules/routing/readwritesplit/rwsplit_session_cmd.c @@ -173,7 +173,7 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, { MXS_ERROR("Slave server '%s': response differs from master's response. " "Closing connection due to inconsistent session state.", - bref->bref_backend->backend_server->unique_name); + bref->ref->server->unique_name); sescmd_cursor_set_active(scur, false); bref_clear_state(bref, BREF_QUERY_ACTIVE); bref_clear_state(bref, BREF_IN_USE); @@ -205,7 +205,7 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, scmd->reply_cmd = *((unsigned char *)replybuf->start + 4); MXS_INFO("Server '%s' responded to a session command, sending the response " - "to the client.", bref->bref_backend->backend_server->unique_name); + "to the client.", bref->ref->server->unique_name); for (int i = 0; i < ses->rses_nbackends; i++) { @@ -226,8 +226,8 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, *reconnect = true; MXS_INFO("Disabling slave %s:%d, result differs from " "master's result. Master: %d Slave: %d", - ses->rses_backend_ref[i].bref_backend->backend_server->name, - ses->rses_backend_ref[i].bref_backend->backend_server->port, + ses->rses_backend_ref[i].ref->server->name, + ses->rses_backend_ref[i].ref->server->port, bref->reply_cmd, ses->rses_backend_ref[i].reply_cmd); } } @@ -237,11 +237,11 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, else { MXS_INFO("Slave '%s' responded before master to a session command. Result: %d", - bref->bref_backend->backend_server->unique_name, + bref->ref->server->unique_name, (int)bref->reply_cmd); if (bref->reply_cmd == 0xff) { - SERVER *serv = bref->bref_backend->backend_server; + SERVER *serv = bref->ref->server; MXS_ERROR("Slave '%s' (%s:%u) failed to execute session command.", serv->unique_name, serv->name, serv->port); } diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c index a82945982..e008ed031 100644 --- a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c +++ b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c @@ -147,11 +147,11 @@ qc_query_type_t is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, dbname = (char *)data->db; - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_LOCAL_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) + if (qc_query_is_type(qtype, QUERY_TYPE_READ) || + qc_query_is_type(qtype, QUERY_TYPE_LOCAL_READ) || + qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) { tbl = qc_get_table_names(querybuf, &tsize, false); @@ -199,7 +199,7 @@ qc_query_type_t is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, void check_create_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, GWBUF *querybuf, qc_query_type_t type) { - if (!QUERY_IS_TYPE(type, QUERY_TYPE_CREATE_TMP_TABLE)) + if (!qc_query_is_type(type, QUERY_TYPE_CREATE_TMP_TABLE)) { return; } diff --git a/server/modules/routing/schemarouter/schemarouter.c b/server/modules/routing/schemarouter/schemarouter.c index 7ef6d8681..6d802fe5a 100644 --- a/server/modules/routing/schemarouter/schemarouter.c +++ b/server/modules/routing/schemarouter/schemarouter.c @@ -84,8 +84,6 @@ static void handleError(ROUTER* instance, DCB* backend_dcb, error_action_t action, bool* succp); - -static int router_get_servercount(ROUTER_INSTANCE* router); static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb); static route_target_t get_shard_route_target(qc_query_type_t qtype, @@ -113,7 +111,8 @@ static ROUTER_OBJECT MyObject = diagnostic, clientReply, handleError, - getCapabilities + getCapabilities, + NULL }; static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses); @@ -306,7 +305,7 @@ char* get_lenenc_str(void* data) showdb_response_t parse_showdb_response(ROUTER_CLIENT_SES* rses, backend_ref_t* bref, GWBUF** buffer) { unsigned char* ptr; - char* target = bref->bref_backend->backend_server->unique_name; + char* target = bref->bref_backend->server->unique_name; GWBUF* buf; bool duplicate_found = false; showdb_response_t rval = SHOWDB_PARTIAL_RESPONSE; @@ -393,12 +392,12 @@ showdb_response_t parse_showdb_response(ROUTER_CLIENT_SES* rses, backend_ref_t* { atomic_add(&bref->n_mapping_eof, 1); MXS_INFO("schemarouter: SHOW DATABASES fully received from %s.", - bref->bref_backend->backend_server->unique_name); + bref->bref_backend->server->unique_name); } else { MXS_INFO("schemarouter: SHOW DATABASES partially received from %s.", - bref->bref_backend->backend_server->unique_name); + bref->bref_backend->server->unique_name); } gwbuf_free(buf); @@ -453,13 +452,13 @@ int gen_databaselist(ROUTER_INSTANCE* inst, ROUTER_CLIENT_SES* session) { if (BREF_IS_IN_USE(&session->rses_backend_ref[i]) && !BREF_IS_CLOSED(&session->rses_backend_ref[i]) & - SERVER_IS_RUNNING(session->rses_backend_ref[i].bref_backend->backend_server)) + SERVER_IS_RUNNING(session->rses_backend_ref[i].bref_backend->server)) { clone = gwbuf_clone(buffer); dcb = session->rses_backend_ref[i].bref_dcb; rval |= !dcb->func.write(dcb, clone); MXS_DEBUG("schemarouter: Wrote SHOW DATABASES to %s for session %p: returned %d", - session->rses_backend_ref[i].bref_backend->backend_server->unique_name, + session->rses_backend_ref[i].bref_backend->server->unique_name, session->rses_client_dcb->session, rval); } @@ -524,7 +523,7 @@ char* get_shard_target_name(ROUTER_INSTANCE* router, /* Check if the query is a show tables query with a specific database */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES)) + if (qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES)) { query = modutil_get_SQL(buffer); if ((tmp = strcasestr(query, "from"))) @@ -560,7 +559,7 @@ char* get_shard_target_name(ROUTER_INSTANCE* router, for (i = 0; i < client->rses_nbackends; i++) { - char *srvnm = client->rses_backend_ref[i].bref_backend->backend_server->unique_name; + char *srvnm = client->rses_backend_ref[i].bref_backend->server->unique_name; if (strcmp(srvnm, buffer->hint->data) == 0) { rval = srvnm; @@ -596,21 +595,16 @@ char* get_shard_target_name(ROUTER_INSTANCE* router, */ bool check_shard_status(ROUTER_INSTANCE* router, char* shard) { - int i; - bool rval = false; - - for (i = 0; router->servers[i]; i++) + for (SERVER_REF *ref = router->service->dbref; ref; ref = ref->next) { - if (strcmp(router->servers[i]->backend_server->unique_name, shard) == 0) + if (strcmp(ref->server->unique_name, shard) == 0 && + SERVER_IS_RUNNING(ref->server)) { - if (SERVER_IS_RUNNING(router->servers[i]->backend_server)) - { - rval = true; - } - break; + return true; } } - return rval; + + return false; } /** @@ -694,22 +688,21 @@ ROUTER_OBJECT* GetModuleObject() static ROUTER* createInstance(SERVICE *service, char **options) { ROUTER_INSTANCE* router; - SERVER_REF* server; CONFIG_PARAMETER* conf; - int nservers; - int i; CONFIG_PARAMETER* param; if ((router = MXS_CALLOC(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; } + if ((router->ignored_dbs = hashtable_alloc(SCHEMAROUTER_HASHSIZE, hashkeyfun, hashcmpfun)) == NULL) { MXS_ERROR("Memory allocation failed when allocating schemarouter database ignore list."); MXS_FREE(router); return NULL; } + hashtable_memory_fns(router->ignored_dbs, hashtable_item_strdup, NULL, hashtable_item_free, NULL); if ((router->shard_maps = hashtable_alloc(SCHEMAROUTER_USERHASH_SIZE, hashkeyfun, hashcmpfun)) == NULL) @@ -739,10 +732,6 @@ static ROUTER* createInstance(SERVICE *service, char **options) router->stats.ses_shortest = (double)((unsigned long)(~0)); spinlock_init(&router->lock); - /** Calculate number of servers */ - server = service->dbref; - nservers = 0; - conf = service->svc_config_param; if ((config_get_param(conf, "auth_all_servers")) == NULL) { @@ -800,17 +789,20 @@ static ROUTER* createInstance(SERVICE *service, char **options) bool failure = false; - for (i = 0; options && options[i]; i++) + for (int i = 0; options && options[i]; i++) { - char* value; - if ((value = strchr(options[i], '=')) == NULL) + char* value = strchr(options[i], '='); + + if (value == NULL) { MXS_ERROR("Unknown router options for Schemarouter: %s", options[i]); failure = true; break; } + *value = '\0'; value++; + if (strcmp(options[i], "max_sescmd_history") == 0) { router->schemarouter_config.max_sescmd_hist = atoi(value); @@ -848,94 +840,9 @@ static ROUTER* createInstance(SERVICE *service, char **options) if (failure) { MXS_FREE(router); - return NULL; + router = NULL; } - while (server != NULL) - { - nservers++; - server = server->next; - } - router->servers = (BACKEND **)MXS_CALLOC(nservers + 1, sizeof(BACKEND *)); - - if (router->servers == NULL) - { - MXS_FREE(router); - return NULL; - } - /** - * Create an array of the backend servers in the router structure to - * maintain a count of the number of connections to each - * backend server. - */ - server = service->dbref; - nservers = 0; - - while (server != NULL) - { - if ((router->servers[nservers] = MXS_MALLOC(sizeof(BACKEND))) == NULL) - { - goto clean_up; - } - router->servers[nservers]->backend_server = server->server; - router->servers[nservers]->backend_conn_count = 0; - router->servers[nservers]->weight = 1; - router->servers[nservers]->be_valid = false; - router->servers[nservers]->stats.queries = 0; - if (server->server->monuser == NULL && service->credentials.name != NULL) - { - router->servers[nservers]->backend_server->monuser = - MXS_STRDUP_A(service->credentials.name); - } - if (server->server->monpw == NULL && service->credentials.authdata != NULL) - { - router->servers[nservers]->backend_server->monpw = - MXS_STRDUP_A(service->credentials.authdata); - } -#if defined(SS_DEBUG) - router->servers[nservers]->be_chk_top = CHK_NUM_BACKEND; - router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND; -#endif - nservers += 1; - server = server->next; - } - router->servers[nservers] = NULL; - - /** - * Process the options - */ - router->bitmask = 0; - router->bitvalue = 0; - - /** - * Read config version number from service to inform what configuration - * is used if any. - */ - router->schemarouter_version = service->svc_config_version; - - /** - * We have completed the creation of the router data, so now - * insert this router into the linked list of routers - * that have been created with this module. - */ - - spinlock_acquire(&instlock); - router->next = instances; - instances = router; - spinlock_release(&instlock); - goto retblock; - -clean_up: - /** clean up */ - for (i = 0; i < nservers; i++) - { - MXS_FREE(router->servers[i]); - } - MXS_FREE(router->servers); - MXS_FREE(router); - router = NULL; - /** Fallthrough */ -retblock: return (ROUTER *)router; } @@ -975,15 +882,12 @@ static void* newSession(ROUTER* router_inst, SESSION* session) ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_inst; bool succp; int router_nservers = 0; /*< # of servers in total */ - int i; - char db[MYSQL_DATABASE_MAXLEN + 1]; + char db[MYSQL_DATABASE_MAXLEN + 1] = ""; MySQLProtocol* protocol = session->client_dcb->protocol; MYSQL_session* data = session->client_dcb->data; bool using_db = false; bool have_db = false; - *db = 0; - spinlock_acquire(&session->ses_lock); /* To enable connecting directly to a sharded database we first need @@ -1010,8 +914,7 @@ static void* newSession(ROUTER* router_inst, SESSION* session) if (client_rses == NULL) { - ss_dassert(false); - goto return_rses; + return NULL; } #if defined(SS_DEBUG) client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; @@ -1079,47 +982,57 @@ static void* newSession(ROUTER* router_inst, SESSION* session) * responding server. */ - router_nservers = router_get_servercount(router); + router_nservers = router->service->n_dbref; /** * Create backend reference objects for this session. */ - backend_ref = (backend_ref_t *)MXS_CALLOC(1, router_nservers * sizeof(backend_ref_t)); + backend_ref = (backend_ref_t *)MXS_CALLOC(router_nservers, sizeof(backend_ref_t)); if (backend_ref == NULL) { - /** log this */ MXS_FREE(client_rses); - MXS_FREE(backend_ref); - client_rses = NULL; - goto return_rses; + return NULL; } /** * Initialize backend references with BACKEND ptr. * Initialize session command cursors for each backend reference. */ - for (i = 0; i < router_nservers; i++) + + int i = 0; + + for (SERVER_REF *ref = router->service->dbref; ref; ref = ref->next) { + if (ref->active) + { #if defined(SS_DEBUG) - backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; + backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; #endif - backend_ref[i].bref_state = 0; - backend_ref[i].n_mapping_eof = 0; - backend_ref[i].map_queue = NULL; - backend_ref[i].bref_backend = router->servers[i]; - /** store pointers to sescmd list to both cursors */ - backend_ref[i].bref_sescmd_cur.scmd_cur_rses = client_rses; - backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; - backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = - &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; + backend_ref[i].bref_state = 0; + backend_ref[i].n_mapping_eof = 0; + backend_ref[i].map_queue = NULL; + backend_ref[i].bref_backend = ref; + /** store pointers to sescmd list to both cursors */ + backend_ref[i].bref_sescmd_cur.scmd_cur_rses = client_rses; + backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; + backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = + &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; + backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; + i++; + } + } + + if (i < router_nservers) + { + router_nservers = i; } spinlock_init(&client_rses->rses_lock); client_rses->rses_backend_ref = backend_ref; + client_rses->rses_nbackends = router_nservers; /** * Find a backend servers to connect to. @@ -1129,43 +1042,23 @@ static void* newSession(ROUTER* router_inst, SESSION* session) { MXS_FREE(client_rses->rses_backend_ref); MXS_FREE(client_rses); - client_rses = NULL; - goto return_rses; + return NULL; } /** * Connect to all backend servers */ - succp = connect_backend_servers(backend_ref, - router_nservers, - session, - router); + succp = connect_backend_servers(backend_ref, router_nservers, session, router); rses_end_locked_router_action(client_rses); - /** - * Master and at least slaves must be found - */ - if (!succp) + if (!succp || !(succp = rses_begin_locked_router_action(client_rses))) { MXS_FREE(client_rses->rses_backend_ref); MXS_FREE(client_rses); - client_rses = NULL; - goto return_rses; - } - /** Copy backend pointers to router session. */ - client_rses->rses_backend_ref = backend_ref; - client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ - - if (!(succp = rses_begin_locked_router_action(client_rses))) - { - MXS_FREE(client_rses->rses_backend_ref); - MXS_FREE(client_rses); - - client_rses = NULL; - goto return_rses; + return NULL; } - if (db[0] != 0x0) + if (db[0]) { /* Store the database the client is connecting to */ snprintf(client_rses->connect_db, MYSQL_DATABASE_MAXLEN + 1, "%s", db); @@ -1175,26 +1068,6 @@ static void* newSession(ROUTER* router_inst, SESSION* session) atomic_add(&router->stats.sessions, 1); - /** - * Version is bigger than zero once initialized. - */ - atomic_add(&client_rses->rses_versno, 2); - ss_dassert(client_rses->rses_versno == 2); - /** - * Add this session to end of the list of active sessions in router. - */ - spinlock_acquire(&router->lock); - client_rses->next = router->connections; - router->connections = client_rses; - spinlock_release(&router->lock); - -return_rses: -#if defined(SS_DEBUG) - if (client_rses != NULL) - { - CHK_CLIENT_RSES(client_rses); - } -#endif return (void *)client_rses; } @@ -1270,7 +1143,7 @@ static void closeSession(ROUTER* instance, void* router_session) */ dcb_close(dcb); /** decrease server current connection counters */ - atomic_add(&bref->bref_backend->backend_conn_count, -1); + atomic_add(&bref->bref_backend->connections, -1); } } @@ -1315,51 +1188,18 @@ static void closeSession(ROUTER* instance, void* router_session) static void freeSession(ROUTER* router_instance, void* router_client_session) { - ROUTER_CLIENT_SES* router_cli_ses; - ROUTER_INSTANCE* router; - int i; - backend_ref_t* bref; + ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; - router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; - router = (ROUTER_INSTANCE *)router_instance; - - for (i = 0; i < router_cli_ses->rses_nbackends; i++) + for (int i = 0; i < router_cli_ses->rses_nbackends; i++) { - bref = &router_cli_ses->rses_backend_ref[i]; - while (bref->bref_pending_cmd && - (bref->bref_pending_cmd = gwbuf_consume( - bref->bref_pending_cmd, gwbuf_length(bref->bref_pending_cmd)))) - { - ; - } + gwbuf_free(router_cli_ses->rses_backend_ref[i].bref_pending_cmd); } - spinlock_acquire(&router->lock); - - if (router->connections == router_cli_ses) - { - router->connections = router_cli_ses->next; - } - else - { - ROUTER_CLIENT_SES* ptr = router->connections; - - while (ptr && ptr->next != router_cli_ses) - { - ptr = ptr->next; - } - - if (ptr) - { - ptr->next = router_cli_ses->next; - } - } - spinlock_release(&router->lock); /** * For each property type, walk through the list, finalize properties * and free the allocated memory. */ - for (i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) + for (int i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) { rses_property_t* p = router_cli_ses->rses_properties[i]; rses_property_t* q = p; @@ -1415,15 +1255,15 @@ static bool get_shard_dcb(DCB** p_dcb, for (i = 0; i < rses->rses_nbackends; i++) { - BACKEND* b = backend_ref[i].bref_backend; + SERVER_REF* b = backend_ref[i].bref_backend; /** * To become chosen: * backend must be in use, name must match, and * the backend state must be RUNNING */ if (BREF_IS_IN_USE((&backend_ref[i])) && - (strncasecmp(name, b->backend_server->unique_name, PATH_MAX) == 0) && - SERVER_IS_RUNNING(b->backend_server)) + (strncasecmp(name, b->server->unique_name, PATH_MAX) == 0) && + SERVER_IS_RUNNING(b->server)) { *p_dcb = backend_ref[i].bref_dcb; succp = true; @@ -1457,19 +1297,19 @@ static route_target_t get_shard_route_target(qc_query_type_t qtype, /** * These queries are not affected by hints */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE) || + if (qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) || /** enable or disable autocommit are always routed to all */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) { /** hints don't affect on routing */ target = TARGET_ALL; } - else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) + else if (qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) { target = TARGET_ANY; } @@ -1559,11 +1399,11 @@ qc_query_type_t is_read_tmp_table(ROUTER* instance, rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; dbname = router_cli_ses->current_db; - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_LOCAL_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) + if (qc_query_is_type(qtype, QUERY_TYPE_READ) || + qc_query_is_type(qtype, QUERY_TYPE_LOCAL_READ) || + qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) { tbl = qc_get_table_names(querybuf, &tsize, false); @@ -1633,7 +1473,7 @@ void check_create_tmp_table(ROUTER* instance, rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; dbname = router_cli_ses->current_db; - if (QUERY_IS_TYPE(type, QUERY_TYPE_CREATE_TMP_TABLE)) + if (qc_query_is_type(type, QUERY_TYPE_CREATE_TMP_TABLE)) { bool is_temp = true; char* tblname = NULL; @@ -2081,7 +1921,7 @@ static int routeQuery(ROUTER* instance, } /** Create the response to the SHOW DATABASES from the mapped databases */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_DATABASES)) + if (qc_query_is_type(qtype, QUERY_TYPE_SHOW_DATABASES)) { if (send_database_list(inst, router_cli_ses)) { @@ -2225,14 +2065,13 @@ static int routeQuery(ROUTER* instance, if (TARGET_IS_ANY(route_target)) { - int z; - - for (z = 0; inst->servers[z]; z++) + for (int i = 0; i < router_cli_ses->rses_nbackends; i++) { - if (SERVER_IS_RUNNING(inst->servers[z]->backend_server)) + SERVER *server = router_cli_ses->rses_backend_ref[i].bref_backend->server; + if (SERVER_IS_RUNNING(server)) { route_target = TARGET_NAMED_SERVER; - targetserver = MXS_STRDUP_A(inst->servers[z]->backend_server->unique_name); + targetserver = MXS_STRDUP_A(server->unique_name); break; } } @@ -2240,9 +2079,7 @@ static int routeQuery(ROUTER* instance, if (TARGET_IS_ANY(route_target)) { /**No valid backends alive*/ - MXS_INFO("schemarouter: No backends are running"); - MXS_ERROR("Schemarouter: Failed to route query, " - "no backends are available."); + MXS_ERROR("Schemarouter: Failed to route query, no backends are available."); rses_end_locked_router_action(router_cli_ses); ret = 0; goto retblock; @@ -2280,8 +2117,8 @@ static int routeQuery(ROUTER* instance, scur = &bref->bref_sescmd_cur; MXS_INFO("Route query to \t%s:%d <", - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->bref_backend->server->name, + bref->bref_backend->server->port); /** * Store current stmt if execution of previous session command * haven't completed yet. Note that according to MySQL protocol @@ -2310,7 +2147,6 @@ static int routeQuery(ROUTER* instance, bref = get_bref_from_dcb(router_cli_ses, target_dcb); bref_set_state(bref, BREF_QUERY_ACTIVE); bref_set_state(bref, BREF_WAITING_RESULT); - atomic_add(&bref->bref_backend->stats.queries, 1); } else { @@ -2400,17 +2236,6 @@ static void diagnostic(ROUTER *instance, DCB *dcb) 100.0 * ((double)router->stats.n_sescmd / (double)router->stats.n_queries) : 0.0; - dcb_printf(dcb, "\33[1;4m%-16s%-16s%-16s\33[0m\n", "Server", "Queries", "State"); - for (i = 0; router->servers[i]; i++) - { - dcb_printf(dcb, "%-16s%-16d%-16s\n", - router->servers[i]->backend_server->unique_name, - router->servers[i]->stats.queries, - SERVER_IS_RUNNING(router->servers[i]->backend_server) ? - "\33[30;42mRUNNING\33[0m" : - "\33[30;41mDOWN\33[0m"); - } - /** Session command statistics */ dcb_printf(dcb, "\n\33[1;4mSession Commands\33[0m\n"); dcb_printf(dcb, "Total number of queries: %d\n", @@ -2514,7 +2339,7 @@ static void clientReply(ROUTER* instance, MXS_DEBUG("schemarouter: Reply from [%s] session [%p]" " mapping [%s] queries queued [%s]", - bref->bref_backend->backend_server->unique_name, + bref->bref_backend->server->unique_name, router_cli_ses->rses_client_dcb->session, router_cli_ses->init & INIT_MAPPING ? "true" : "false", router_cli_ses->queue == NULL ? "none" : @@ -2641,8 +2466,8 @@ static void clientReply(ROUTER* instance, MXS_ERROR("Failed to execute %s in %s:%d. %s %s", cmdstr, - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port, + bref->bref_backend->server->name, + bref->bref_backend->server->port, err, replystr); @@ -2709,8 +2534,8 @@ static void clientReply(ROUTER* instance, MXS_INFO("Backend %s:%d processed reply and starts to execute " "active cursor.", - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->bref_backend->server->name, + bref->bref_backend->server->port); execute_sescmd_in_backend(bref); } @@ -2757,44 +2582,41 @@ static void clientReply(ROUTER* instance, /** Compare number of connections from this router in backend servers */ int bref_cmp_router_conn(const void* bref1, const void* bref2) { - BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF* b1 = ((backend_ref_t *)bref1)->bref_backend; + SERVER_REF* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((1000 * b1->backend_conn_count) / b1->weight) - - ((1000 * b2->backend_conn_count) / b2->weight); + return ((1000 * b1->connections) / b1->weight) + - ((1000 * b2->connections) / b2->weight); } /** Compare number of global connections in backend servers */ int bref_cmp_global_conn(const void* bref1, const void* bref2) { - BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF* b1 = ((backend_ref_t *)bref1)->bref_backend; + SERVER_REF* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((1000 * b1->backend_server->stats.n_current) / b1->weight) - - ((1000 * b2->backend_server->stats.n_current) / b2->weight); + return ((1000 * b1->server->stats.n_current) / b1->weight) + - ((1000 * b2->server->stats.n_current) / b2->weight); } /** Compare replication lag between backend servers */ int bref_cmp_behind_master(const void* bref1, const void* bref2) { - BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF* b1 = ((backend_ref_t *)bref1)->bref_backend; + SERVER_REF* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((b1->backend_server->rlag < b2->backend_server->rlag) ? -1 : - ((b1->backend_server->rlag > b2->backend_server->rlag) ? 1 : 0)); + return b1->server->rlag - b2->server->rlag; } /** Compare number of current operations in backend servers */ int bref_cmp_current_load(const void* bref1, const void* bref2) { - SERVER* s1 = ((backend_ref_t *)bref1)->bref_backend->backend_server; - SERVER* s2 = ((backend_ref_t *)bref2)->bref_backend->backend_server; - BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; - BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + SERVER_REF* b1 = ((backend_ref_t *)bref1)->bref_backend; + SERVER_REF* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((1000 * s1->stats.n_current_ops) - b1->weight) - - ((1000 * s2->stats.n_current_ops) - b2->weight); + return ((1000 * b1->server->stats.n_current_ops) - b1->weight) + - ((1000 * b2->server->stats.n_current_ops) - b2->weight); } static void bref_clear_state(backend_ref_t* bref, bref_state_t state) @@ -2823,14 +2645,14 @@ static void bref_clear_state(backend_ref_t* bref, bref_state_t state) else { /** Decrease global operation count */ - prev2 = atomic_add(&bref->bref_backend->backend_server->stats.n_current_ops, -1); + prev2 = atomic_add(&bref->bref_backend->server->stats.n_current_ops, -1); ss_dassert(prev2 > 0); if (prev2 <= 0) { MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", __FUNCTION__, - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->bref_backend->server->name, + bref->bref_backend->server->port); } } } @@ -2860,18 +2682,18 @@ static void bref_set_state(backend_ref_t* bref, bref_state_t state) MXS_ERROR("[%s] Error: negative number of connections waiting " "for results in backend %s:%u", __FUNCTION__, - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->bref_backend->server->name, + bref->bref_backend->server->port); } /** Increase global operation count */ - prev2 = atomic_add(&bref->bref_backend->backend_server->stats.n_current_ops, 1); + prev2 = atomic_add(&bref->bref_backend->server->stats.n_current_ops, 1); ss_dassert(prev2 >= 0); if (prev2 < 0) { MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", __FUNCTION__, - bref->bref_backend->backend_server->name, - bref->bref_backend->backend_server->port); + bref->bref_backend->server->name, + bref->bref_backend->server->port); } } } @@ -2941,14 +2763,14 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, for (i = 0; i < router_nservers; i++) { - BACKEND* b = backend_ref[i].bref_backend; + SERVER_REF* b = backend_ref[i].bref_backend; MXS_INFO("MaxScale connections : %d (%d) in \t%s:%d %s", - b->backend_conn_count, - b->backend_server->stats.n_current, - b->backend_server->name, - b->backend_server->port, - STRSRVSTATUS(b->backend_server)); + b->connections, + b->server->stats.n_current, + b->server->name, + b->server->port, + STRSRVSTATUS(b->server)); } } /*< log only */ /** @@ -2957,9 +2779,9 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, */ for (i = 0; i < router_nservers; i++) { - BACKEND* b = backend_ref[i].bref_backend; + SERVER_REF* b = backend_ref[i].bref_backend; - if (SERVER_IS_RUNNING(b->backend_server)) + if (SERVER_IS_RUNNING(b->server)) { servers_found += 1; @@ -2971,9 +2793,9 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, /** New server connection */ else { - backend_ref[i].bref_dcb = dcb_connect(b->backend_server, + backend_ref[i].bref_dcb = dcb_connect(b->server, session, - b->backend_server->protocol); + b->server->protocol); if (backend_ref[i].bref_dcb != NULL) { @@ -3000,7 +2822,7 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, * But decreased in the calling function * of dcb_close. */ - atomic_add(&b->backend_conn_count, 1); + atomic_add(&b->connections, 1); dcb_add_callback(backend_ref[i].bref_dcb, DCB_REASON_NOT_RESPONDING, @@ -3012,8 +2834,8 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, succp = false; MXS_ERROR("Unable to establish " "connection with slave %s:%d", - b->backend_server->name, - b->backend_server->port); + b->server->name, + b->server->port); /* handle connect error */ break; } @@ -3047,14 +2869,14 @@ static bool connect_backend_servers(backend_ref_t* backend_ref, { for (i = 0; i < router_nservers; i++) { - BACKEND* b = backend_ref[i].bref_backend; + SERVER_REF* b = backend_ref[i].bref_backend; if (BREF_IS_IN_USE((&backend_ref[i]))) { MXS_INFO("Connected %s in \t%s:%d", - STRSRVSTATUS(b->backend_server), - b->backend_server->name, - b->backend_server->port); + STRSRVSTATUS(b->server), + b->server->name, + b->server->port); } } /* for */ } @@ -3437,28 +3259,7 @@ static bool execute_sescmd_in_backend(backend_ref_t* backend_ref) /** Cursor is left active when function returns. */ sescmd_cursor_set_active(scur, true); } -#if defined(SS_DEBUG) - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - tracelog_routed_query(scur->scmd_cur_rses, - "execute_sescmd_in_backend", - backend_ref, - sescmd_cursor_clone_querybuf(scur)); - } - { - GWBUF* tmpbuf = sescmd_cursor_clone_querybuf(scur); - uint8_t* ptr = GWBUF_DATA(tmpbuf); - unsigned char cmd = MYSQL_GET_COMMAND(ptr); - - MXS_DEBUG("%lu [execute_sescmd_in_backend] Just before write, fd " - "%d : cmd %s.", - pthread_self(), - dcb->fd, - STRPACKETTYPE(cmd)); - gwbuf_free(tmpbuf); - } -#endif /*< SS_DEBUG */ switch (scur->scmd_cur_cmd->my_sescmd_packet_type) { case MYSQL_COM_CHANGE_USER: @@ -3567,75 +3368,6 @@ static rses_property_t* mysql_sescmd_get_property(mysql_sescmd_t* scmd) return scmd->my_sescmd_prop; } -static void tracelog_routed_query(ROUTER_CLIENT_SES* rses, - char* funcname, - backend_ref_t* bref, - GWBUF* buf) -{ - uint8_t* packet = GWBUF_DATA(buf); - unsigned char packet_type = packet[4]; - size_t len; - size_t buflen = GWBUF_LENGTH(buf); - char* querystr; - char* startpos = (char *)&packet[5]; - - CHK_BACKEND_REF(bref); - ss_debug(BACKEND *b = bref->bref_backend); - CHK_BACKEND(b); - ss_debug(DCB *dcb = bref->bref_dcb); - CHK_DCB(dcb); - - ss_debug(backend_type_t be_type = BACKEND_TYPE(b)); - - if (GWBUF_IS_TYPE_MYSQL(buf)) - { - len = packet[0]; - len += 256 * packet[1]; - len += 256 * 256 * packet[2]; - - if (packet_type == '\x03') - { - querystr = (char *)MXS_MALLOC(len); - MXS_ABORT_IF_NULL(querystr); - memcpy(querystr, startpos, len - 1); - querystr[len - 1] = '\0'; - MXS_DEBUG("%lu [%s] %d bytes long buf, \"%s\" -> %s:%d %s dcb %p", - pthread_self(), - funcname, - (int)buflen, - querystr, - b->backend_server->name, - b->backend_server->port, - STRBETYPE(be_type), - dcb); - MXS_FREE(querystr); - } - else if (packet_type == '\x22' || - packet_type == 0x22 || - packet_type == '\x26' || - packet_type == 0x26 || - true) - { - querystr = (char *)MXS_MALLOC(len); - MXS_ABORT_IF_NULL(querystr); - memcpy(querystr, startpos, len - 1); - querystr[len - 1] = '\0'; - MXS_DEBUG("%lu [%s] %d bytes long buf, \"%s\" -> %s:%d %s dcb %p", - pthread_self(), - funcname, - (int)buflen, - querystr, - b->backend_server->name, - b->backend_server->port, - STRBETYPE(be_type), - dcb); - MXS_FREE(querystr); - } - } - gwbuf_free(buf); -} - - /** * Return RCAP_TYPE_STMT_INPUT. */ @@ -3699,17 +3431,16 @@ static bool route_session_write(ROUTER_CLIENT_SES* router_cli_ses, if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { MXS_INFO("Route query to %s\t%s:%d%s", - (SERVER_IS_MASTER(backend_ref[i].bref_backend->backend_server) ? + (SERVER_IS_MASTER(backend_ref[i].bref_backend->server) ? "master" : "slave"), - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port, + backend_ref[i].bref_backend->server->name, + backend_ref[i].bref_backend->server->port, (i + 1 == router_cli_ses->rses_nbackends ? " <" : "")); } if (BREF_IS_IN_USE((&backend_ref[i]))) { rc = dcb->func.write(dcb, gwbuf_clone(querybuf)); - atomic_add(&backend_ref[i].bref_backend->stats.queries, 1); if (rc != 1) { succp = false; @@ -3807,10 +3538,10 @@ static bool route_session_write(ROUTER_CLIENT_SES* router_cli_ses, if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { MXS_INFO("Route query to %s\t%s:%d%s", - (SERVER_IS_MASTER(backend_ref[i].bref_backend->backend_server) ? + (SERVER_IS_MASTER(backend_ref[i].bref_backend->server) ? "master" : "slave"), - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port, + backend_ref[i].bref_backend->server->name, + backend_ref[i].bref_backend->server->port, (i + 1 == router_cli_ses->rses_nbackends ? " <" : "")); } @@ -3832,8 +3563,8 @@ static bool route_session_write(ROUTER_CLIENT_SES* router_cli_ses, succp = true; MXS_INFO("Backend %s:%d already executing sescmd.", - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port); + backend_ref[i].bref_backend->server->name, + backend_ref[i].bref_backend->server->port); } else { @@ -3843,12 +3574,8 @@ static bool route_session_write(ROUTER_CLIENT_SES* router_cli_ses, { MXS_ERROR("Failed to execute session " "command in %s:%d", - backend_ref[i].bref_backend->backend_server->name, - backend_ref[i].bref_backend->backend_server->port); - } - else - { - atomic_add(&backend_ref[i].bref_backend->stats.queries, 1); + backend_ref[i].bref_backend->server->name, + backend_ref[i].bref_backend->server->port); } } } @@ -4030,7 +3757,6 @@ static bool handle_error_new_connection(ROUTER_INSTANCE* inst, GWBUF* errmsg) { SESSION* ses; - int router_nservers, i; unsigned char cmd = *((unsigned char*)errmsg->start + 4); backend_ref_t* bref; @@ -4087,13 +3813,12 @@ static bool handle_error_new_connection(ROUTER_INSTANCE* inst, &router_handle_state_switch, (void *)bref); - router_nservers = router_get_servercount(inst); /** * Try to get replacement slave or at least the minimum * number of slave connections for router session. */ succp = connect_backend_servers(rses->rses_backend_ref, - router_nservers, + rses->rses_nbackends, ses, inst); @@ -4108,24 +3833,6 @@ return_succp: return succp; } -/** - * Count the number of servers. - * @param inst Router instance - * @return Number of servers - */ -static int router_get_servercount(ROUTER_INSTANCE* inst) -{ - int router_nservers = 0; - BACKEND** b = inst->servers; - /** count servers */ - while (*(b++) != NULL) - { - router_nservers++; - } - - return router_nservers; -} - /** * Finds out if there is a backend reference pointing at the DCB given as * parameter. @@ -4190,7 +3897,7 @@ static int router_handle_state_switch(DCB* dcb, bref = (backend_ref_t *) data; CHK_BACKEND_REF(bref); - srv = bref->bref_backend->backend_server; + srv = bref->bref_backend->server; if (SERVER_IS_RUNNING(srv)) { @@ -4200,7 +3907,7 @@ static int router_handle_state_switch(DCB* dcb, switch (reason) { case DCB_REASON_NOT_RESPONDING: - atomic_add(&bref->bref_backend->backend_conn_count, -1); + atomic_add(&bref->bref_backend->connections, -1); MXS_INFO("schemarouter: server %s not responding", srv->unique_name); dcb->func.hangup(dcb); break; @@ -4478,7 +4185,7 @@ int inspect_backend_mapping_states(ROUTER_CLIENT_SES *router_cli_ses, { router_cli_ses->rses_backend_ref[i].bref_mapped = true; MXS_DEBUG("schemarouter: Received SHOW DATABASES reply from %s for session %p", - router_cli_ses->rses_backend_ref[i].bref_backend->backend_server->unique_name, + router_cli_ses->rses_backend_ref[i].bref_backend->server->unique_name, router_cli_ses->rses_client_dcb->session); } else if (rc == SHOWDB_PARTIAL_RESPONSE) @@ -4486,7 +4193,7 @@ int inspect_backend_mapping_states(ROUTER_CLIENT_SES *router_cli_ses, bref->map_queue = writebuf; writebuf = NULL; MXS_DEBUG("schemarouter: Received partial SHOW DATABASES reply from %s for session %p", - router_cli_ses->rses_backend_ref[i].bref_backend->backend_server->unique_name, + router_cli_ses->rses_backend_ref[i].bref_backend->server->unique_name, router_cli_ses->rses_client_dcb->session); } else @@ -4539,7 +4246,7 @@ int inspect_backend_mapping_states(ROUTER_CLIENT_SES *router_cli_ses, { mapped = false; MXS_DEBUG("schemarouter: Still waiting for reply to SHOW DATABASES from %s for session %p", - bkrf[i].bref_backend->backend_server->unique_name, + bkrf[i].bref_backend->server->unique_name, router_cli_ses->rses_client_dcb->session); } } diff --git a/server/modules/routing/schemarouter/schemarouter.h b/server/modules/routing/schemarouter/schemarouter.h index 88dd01eda..c3cab3c28 100644 --- a/server/modules/routing/schemarouter/schemarouter.h +++ b/server/modules/routing/schemarouter/schemarouter.h @@ -261,7 +261,7 @@ typedef struct backend_ref_st #endif int n_mapping_eof; GWBUF* map_queue; - BACKEND* bref_backend; /*< Backend server */ + SERVER_REF* bref_backend; /*< Backend server */ DCB* bref_dcb; /*< Backend DCB */ bref_state_t bref_state; /*< State of the backend */ bool bref_mapped; /*< Whether the backend has been mapped */ @@ -357,8 +357,6 @@ typedef struct router_instance SERVICE* service; /*< Pointer to service */ ROUTER_CLIENT_SES* connections; /*< List of client connections */ SPINLOCK lock; /*< Lock for the instance data */ - BACKEND** servers; /*< Backend servers */ - BACKEND* master; /*< NULL or pointer */ schemarouter_config_t schemarouter_config; /*< expanded config info from SERVICE */ int schemarouter_version;/*< version number for router's config */ unsigned int bitmask; /*< Bitmask to apply to server->status */ diff --git a/server/modules/routing/testroute/testroute.c b/server/modules/routing/testroute/testroute.c index 502e7cfab..7449822ce 100644 --- a/server/modules/routing/testroute/testroute.c +++ b/server/modules/routing/testroute/testroute.c @@ -50,7 +50,8 @@ static ROUTER_OBJECT MyObject = diagnostic, clientReply, handleError, - getCapabilities + getCapabilities, + NULL }; typedef struct diff --git a/server/modules/routing/webserver/webserver.c b/server/modules/routing/webserver/webserver.c index c9889ba77..f6546abb1 100644 --- a/server/modules/routing/webserver/webserver.c +++ b/server/modules/routing/webserver/webserver.c @@ -64,7 +64,8 @@ static ROUTER_OBJECT MyObject = diagnostic, NULL, NULL, - getCapabilities + getCapabilities, + NULL };