Merge branch 'develop' into MXS-1209
This commit is contained in:
commit
2d7df3eb89
@ -47,7 +47,18 @@ transaction or change the autocommit mode using a prepared statement.
|
||||
|
||||
### Limitations with MySQL Protocol support (MySQLClient)
|
||||
|
||||
Compression is not included in the MySQL server handshake.
|
||||
* Compression is not included in the MySQL server handshake.
|
||||
|
||||
* MariaDB MaxScale will intercept `KILL <thread_id>` statements which are of the
|
||||
form `KILL 3`, `KILL CONNECTION 321` and `KILL QUERY 8`. These queries are not
|
||||
routed to backends because the `<thread_id>` sent by the client does not equal a
|
||||
backend id. MaxScale reacts to a thread kill command by killing the session with
|
||||
the given id if the user and host of the issuing session and the target session
|
||||
match. Query kill command is not supported and results in an error message. For
|
||||
MaxScale to recognize the *KILL* statement, the statement must start right after
|
||||
the command byte, have no comments and have minimal whitespace. These
|
||||
limitations are in place to limit the parsing MaxScale needs to do to every
|
||||
query.
|
||||
|
||||
## Authenticator limitations
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -542,7 +542,7 @@ The port where the HTTP admin interface listens on. The default value is port
|
||||
|
||||
Enable HTTP admin interface authentication using HTTP Basic Access
|
||||
authentication. This is not a secure method of authentication but it does add a
|
||||
small layer of security. This option id disabled by default.
|
||||
small layer of security. This option is disabled by default.
|
||||
|
||||
#### `admin_user`
|
||||
|
||||
@ -573,6 +573,11 @@ documentation for more details.
|
||||
The path to the TLS CA certificate in PEM format. See `admin_ssl_key`
|
||||
documentation for more details.
|
||||
|
||||
#### `admin_enabled`
|
||||
|
||||
Enable or disable the admin interface. This allows the admin interface to
|
||||
be completely disabled to prevent access to it.
|
||||
|
||||
### Service
|
||||
|
||||
A service represents the database service that MariaDB MaxScale offers to the
|
||||
@ -1042,6 +1047,18 @@ refuse these due to the lack of the header. To bypass this restriction, the
|
||||
server monitor needs to be disabled and the service listener needs to be
|
||||
configured to disregard authentication errors (`skip_authentication=true`).
|
||||
|
||||
#### `authenticator`
|
||||
|
||||
The authenticator module to use. Each protocol module defines a default
|
||||
authentication module which is used if no `authenticator` parameter is found
|
||||
from the configuration.
|
||||
|
||||
#### `authenticator_options`
|
||||
|
||||
Option string given to the authenticator module. The value of this parameter
|
||||
should be a comma-separated list of key-value pairs. See authenticator specific
|
||||
documentation for more details.
|
||||
|
||||
### Server and SSL
|
||||
|
||||
This section describes configuration parameters for servers that control the
|
||||
|
@ -4,6 +4,8 @@ This document describes the version 1 of the MaxScale REST API.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Resources](#resources)
|
||||
- [Common Request Parameter](#common-request-parameters)
|
||||
- [HTTP Headers](#http-headers)
|
||||
- [Request Headers](#request-headers)
|
||||
- [Response Headers](#response-headers)
|
||||
@ -12,8 +14,6 @@ This document describes the version 1 of the MaxScale REST API.
|
||||
- [3xx Redirection](#3xx-redirection)
|
||||
- [4xx Client Error](#4xx-client-error)
|
||||
- [5xx Server Error](#5xx-server-error)
|
||||
- [Resources](#resources)
|
||||
- [Common Request Parameter](#common-request-parameters)
|
||||
|
||||
## Note About Syntax
|
||||
|
||||
@ -21,6 +21,78 @@ Although JSON does not define a syntax for comments, some of the JSON examples
|
||||
have C-style inline comments in them. These comments use `//` to mark the start
|
||||
of the comment and extend to the end of the current line.
|
||||
|
||||
## Resources
|
||||
|
||||
The MaxScale REST API provides the following resources. All resources conform to
|
||||
the [JSON API](http://jsonapi.org/format/) specification.
|
||||
|
||||
- [/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)
|
||||
|
||||
### Resource Relationships
|
||||
|
||||
All resources return complete JSON objects. The returned objects can have a
|
||||
_relationships_ field that represents any relations the object has to other
|
||||
objects. This closely resembles the JSON API definition of links.
|
||||
|
||||
In the _relationships_ objects, all resources have a _self_ link that points to
|
||||
the resource itself. This allows for easier updating of resources as the reply
|
||||
URL is included in the response itself.
|
||||
|
||||
The following lists the resources and the types of links each resource can have
|
||||
in addition to the _self_ link.
|
||||
|
||||
- `services` - Service resource
|
||||
|
||||
- `servers`
|
||||
|
||||
List of servers used by the service
|
||||
|
||||
- `filters`
|
||||
|
||||
List of filters used by the service
|
||||
|
||||
- `monitors` - Monitor resource
|
||||
|
||||
- `servers`
|
||||
|
||||
List of servers used by the monitor
|
||||
|
||||
- `filters` - Filter resource
|
||||
|
||||
- `services`
|
||||
|
||||
List of services that use this filter
|
||||
|
||||
- `servers` - Server resource
|
||||
|
||||
- `services`
|
||||
|
||||
List of services that use this server
|
||||
|
||||
- `monitors`
|
||||
|
||||
List of monitors that use this server
|
||||
|
||||
## Common Request Parameters
|
||||
|
||||
Most of the resources that support GET also support the following
|
||||
parameters. See the resource documentation for a list of supported request
|
||||
parameters.
|
||||
|
||||
- `pretty`
|
||||
|
||||
- Pretty-print output.
|
||||
|
||||
If this parameter is set to `true` then the returned objects are
|
||||
formatted in a more human readable format. All resources support this
|
||||
parameter.
|
||||
|
||||
## HTTP Headers
|
||||
|
||||
### Request Headers
|
||||
@ -41,19 +113,9 @@ Credentials for authentication.
|
||||
|
||||
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.
|
||||
PATCH requests must use the `Content-Type: application/json` media type and the
|
||||
request body must be a JSON document containing a partial definition of the
|
||||
original resource.
|
||||
|
||||
#### Host
|
||||
|
||||
@ -101,8 +163,6 @@ 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
|
||||
@ -294,21 +354,6 @@ contains a more detailed version of the error message.
|
||||
|
||||
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
|
||||
@ -414,99 +459,3 @@ API could return them.
|
||||
|
||||
- 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)
|
||||
|
||||
### Resource Relationships
|
||||
|
||||
All resources return complete JSON objects. The returned objects can have a
|
||||
_relationships_ field that represents any relations the object has to other
|
||||
objects. This closely resembles the JSON API definition of links.
|
||||
|
||||
In the _relationships_ objects, all resources have a _self_ link that points to
|
||||
the resource itself. This allows for easier updating of resources as the reply
|
||||
URL is included in the response itself.
|
||||
|
||||
The following lists the resources and the types of links each resource can have
|
||||
in addition to the _self_ link.
|
||||
|
||||
- `services` - Service resource
|
||||
|
||||
- `servers`
|
||||
|
||||
List of servers used by the service
|
||||
|
||||
- `filters`
|
||||
|
||||
List of filters used by the service
|
||||
|
||||
- `monitors` - Monitor resource
|
||||
|
||||
- `servers`
|
||||
|
||||
List of servers used by the monitor
|
||||
|
||||
- `filters` - Filter resource
|
||||
|
||||
- `services`
|
||||
|
||||
List of services that use this filter
|
||||
|
||||
- `servers` - Server resource
|
||||
|
||||
- `services`
|
||||
|
||||
List of services that use this server
|
||||
|
||||
- `monitors`
|
||||
|
||||
List of monitors that use this server
|
||||
|
||||
## Common Request Parameters
|
||||
|
||||
Most of the resources that support GET also support the following
|
||||
parameters. See the resource documentation for a list of supported request
|
||||
parameters.
|
||||
|
||||
- `pretty`
|
||||
|
||||
- Pretty-print output.
|
||||
|
||||
If this parameter is set to `true` then the returned objects are
|
||||
formatted in a more human readable format. All resources support this
|
||||
parameter.
|
||||
|
||||
- `fields`
|
||||
|
||||
- A list of fields to return.
|
||||
|
||||
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.
|
||||
|
@ -7,7 +7,7 @@ A server resource represents a backend database server.
|
||||
### Get a server
|
||||
|
||||
```
|
||||
GET /servers/:name
|
||||
GET /v1/servers/:name
|
||||
```
|
||||
|
||||
Get a single server. The _:name_ in the URI must be a valid server name with all
|
||||
@ -20,38 +20,67 @@ whitespace replaced with hyphens. The server names are case-insensitive.
|
||||
|
||||
```
|
||||
Status: 200 OK
|
||||
```
|
||||
|
||||
```javascript
|
||||
{
|
||||
"name": "server1",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend",
|
||||
"monitoruser": "maxuser",
|
||||
"monitorpw": "maxpwd"
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/server1"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/server1",
|
||||
"services": [
|
||||
"http://localhost:8989/services/RW-Split-Router",
|
||||
"http://localhost:8989/services/Read-Connection-Router"
|
||||
],
|
||||
"monitors": [
|
||||
"http://localhost:8989/monitors/MySQL-Monitor"
|
||||
]
|
||||
"data": {
|
||||
"id": "server1", // Resource identifier
|
||||
"type": "servers", // Resource type
|
||||
"relationships": { // Resource relationships to other resources
|
||||
"services": { // Services that use this server
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/services/"
|
||||
},
|
||||
"data": [ // References to service resources
|
||||
{
|
||||
"id": "RW-Split-Router",
|
||||
"type": "services"
|
||||
},
|
||||
{
|
||||
"id": "Read-Connection-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": { // The monitor that is monitoring this server
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/monitors/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"attributes": { // Resource attributes
|
||||
"parameters": { // Server parameters
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend"
|
||||
},
|
||||
"status": "Master, Running", // Server status string
|
||||
"version_string": "10.1.22-MariaDB", // Server version
|
||||
"node_id": 3000, // Server node ID i.e. value of @@server_id
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [ // List of slave server IDs
|
||||
3001
|
||||
],
|
||||
"statictics": { // Server statistics
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
}
|
||||
},
|
||||
"links": { // Link to the server itself
|
||||
"self": "http://localhost:8989/v1/servers/server1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -69,80 +98,129 @@ Status: 404 Not Found
|
||||
### Get all servers
|
||||
|
||||
```
|
||||
GET /servers
|
||||
GET /v1/servers
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Response contains an array of all servers.
|
||||
Response contains a resource collection with all servers.
|
||||
|
||||
```
|
||||
Status: 200 OK
|
||||
```
|
||||
|
||||
[
|
||||
{
|
||||
"name": "server1",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend",
|
||||
"monitoruser": "maxuser",
|
||||
"monitorpw": "maxpwd"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/server1",
|
||||
"services": [
|
||||
"http://localhost:8989/services/RW-Split-Router",
|
||||
"http://localhost:8989/services/Read-Connection-Router"
|
||||
],
|
||||
"monitors": [
|
||||
"http://localhost:8989/monitors/MySQL-Monitor"
|
||||
]
|
||||
}
|
||||
```javascript
|
||||
{
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/"
|
||||
},
|
||||
{
|
||||
"name": "server2",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3001,
|
||||
"protocol": "MySQLBackend",
|
||||
"my-weighting-parameter": "3"
|
||||
"data": [ // List of server resouces
|
||||
{
|
||||
"id": "server1",
|
||||
"type": "servers",
|
||||
"relationships": {
|
||||
"services": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/services/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "RW-Split-Router",
|
||||
"type": "services"
|
||||
},
|
||||
{
|
||||
"id": "Read-Connection-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/monitors/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version_string": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/server1"
|
||||
}
|
||||
},
|
||||
"status": "Slave, Running",
|
||||
"version": "10.1.22-MariaDB",
|
||||
"node_id": 3001,
|
||||
"master_id": 3000,
|
||||
"replication_depth": 1,
|
||||
"slaves": [],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/server2",
|
||||
"services": [
|
||||
"http://localhost:8989/services/RW-Split-Router"
|
||||
],
|
||||
"monitors": [
|
||||
"http://localhost:8989/monitors/MySQL-Monitor"
|
||||
]
|
||||
{
|
||||
"id": "server2",
|
||||
"type": "servers",
|
||||
"relationships": {
|
||||
"services": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/services/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "RW-Split-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/monitors/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3001,
|
||||
"protocol": "MySQLBackend"
|
||||
},
|
||||
"status": "Slave, Running",
|
||||
"version_string": "10.1.22-MariaDB",
|
||||
"node_id": 3001,
|
||||
"master_id": 3000,
|
||||
"replication_depth": 1,
|
||||
"slaves": [],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/server2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported Request Parameter
|
||||
@ -152,7 +230,7 @@ Status: 200 OK
|
||||
### Create a server
|
||||
|
||||
```
|
||||
POST /servers
|
||||
POST /v1/servers
|
||||
```
|
||||
|
||||
Create a new server by defining the resource. The posted object must define the
|
||||
@ -160,60 +238,89 @@ _name_ field with the name of the server and the _parameters_ field with JSON
|
||||
object containing values for the _address_ and _port_ parameters. The following
|
||||
is the minimal required JSON object for defining a new server.
|
||||
|
||||
```
|
||||
```javascript
|
||||
{
|
||||
"name": "test-server",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3003
|
||||
"data": {
|
||||
"id": "server3",
|
||||
"type": "servers",
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3003,
|
||||
"protocol": "MySQLBackend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The relationships of a server can also be defined at creation time. This allows
|
||||
new servers to be created and immediately taken into use.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"data": {
|
||||
"id": "server4",
|
||||
"type": "servers",
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3002,
|
||||
"protocol": "MySQLBackend"
|
||||
}
|
||||
},
|
||||
"relationships": {
|
||||
"services": {
|
||||
"data": [
|
||||
{
|
||||
"id": "RW-Split-Router",
|
||||
"type": "services"
|
||||
},
|
||||
{
|
||||
"id": "Read-Connection-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": {
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following parameters can be defined when a server is being created.
|
||||
|
||||
- [address](../Getting-Started/Configuration-Guide.md#address)
|
||||
- [port](../Getting-Started/Configuration-Guide.md#port)
|
||||
- [protocol](../Getting-Started/Configuration-Guide.md#protocol)
|
||||
- [authenticator](../Getting-Started/Configuration-Guide.md#authenticator)
|
||||
- [authenticator_options](../Getting-Started/Configuration-Guide.md#authenticator-options)
|
||||
|
||||
#### Response
|
||||
|
||||
Response contains the created resource.
|
||||
Server created:
|
||||
|
||||
```
|
||||
Status: 200 OK
|
||||
|
||||
{
|
||||
"name": "test-server",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3003,
|
||||
"protocol": "MySQLBackend"
|
||||
},
|
||||
"status": "Running",
|
||||
"node_id": -1,
|
||||
"master_id": -1,
|
||||
"replication_depth": -1,
|
||||
"slaves": [],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/test-server"
|
||||
}
|
||||
}
|
||||
Status: 204 No Content
|
||||
```
|
||||
|
||||
Invalid JSON body:
|
||||
|
||||
```
|
||||
Status: 400 Bad Request
|
||||
Status: 403 Forbidden
|
||||
```
|
||||
|
||||
#### Supported Request Parameter
|
||||
|
||||
- `pretty`
|
||||
|
||||
### Update a server
|
||||
|
||||
```
|
||||
PUT /servers/:name
|
||||
PUT /v1/servers/:name
|
||||
```
|
||||
|
||||
The _:name_ in the URI must map to a server name with all whitespace replaced
|
||||
@ -249,81 +356,114 @@ _server1_ from the service _RW-Split-Router_.
|
||||
Removing a service from a server is analogous to removing the server from the
|
||||
service. Both unlink the two objects from each other.
|
||||
|
||||
```
|
||||
Response to `GET /v1/server/server1`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"name": "server1",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend",
|
||||
"monitoruser": "maxuser",
|
||||
"monitorpw": "maxpwd"
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/server1"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/server1",
|
||||
"services": [
|
||||
"http://localhost:8989/services/RW-Split-Router", // This value is removed
|
||||
"http://localhost:8989/services/Read-Connection-Router"
|
||||
],
|
||||
"monitors": [
|
||||
"http://localhost:8989/monitors/MySQL-Monitor"
|
||||
]
|
||||
"data": {
|
||||
"id": "server1",
|
||||
"type": "servers",
|
||||
"relationships": {
|
||||
"services": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/services/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "RW-Split-Router", // We'll remove this service
|
||||
"type": "services"
|
||||
},
|
||||
{
|
||||
"id": "Read-Connection-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/monitors/"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version_string": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001,
|
||||
3002
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/servers/server1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Request for `PUT /v1/server/server1`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"data": {
|
||||
"id": "server1",
|
||||
"type": "servers",
|
||||
"relationships": {
|
||||
"services": {
|
||||
"data": [
|
||||
{
|
||||
"id": "Read-Connection-Router",
|
||||
"type": "services"
|
||||
}
|
||||
]
|
||||
},
|
||||
"monitors": {
|
||||
"data": [
|
||||
{
|
||||
"id": "MySQL-Monitor",
|
||||
"type": "monitors"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The current implementation accepts both PUT and PATCH requests with partially
|
||||
defined resources as request body. If parts of the resource are not defined
|
||||
(e.g. the `attributes` field in the above example), those parts of the resource
|
||||
are not modified. All parts that are defined are interpreted as the new
|
||||
definition of those part of the resource. In the above example, the
|
||||
`relationships` of the resource are completely redefined.
|
||||
|
||||
#### Response
|
||||
|
||||
Response contains the modified resource.
|
||||
Server modified:
|
||||
|
||||
```
|
||||
Status: 200 OK
|
||||
|
||||
{
|
||||
"name": "server1",
|
||||
"parameters": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"protocol": "MySQLBackend",
|
||||
"monitoruser": "maxuser",
|
||||
"monitorpw": "maxpwd"
|
||||
},
|
||||
"status": "Master, Running",
|
||||
"version": "10.1.22-MariaDB",
|
||||
"node_id": 3000,
|
||||
"master_id": -1,
|
||||
"replication_depth": 0,
|
||||
"slaves": [
|
||||
3001
|
||||
],
|
||||
"statictics": {
|
||||
"connections": 0,
|
||||
"total_connections": 0,
|
||||
"active_operations": 0
|
||||
},
|
||||
"relationships": {
|
||||
"self": "http://localhost:8989/servers/server1",
|
||||
"services": [
|
||||
"http://localhost:8989/services/Read-Connection-Router"
|
||||
],
|
||||
"monitors": [
|
||||
"http://localhost:8989/monitors/MySQL-Monitor"
|
||||
]
|
||||
}
|
||||
}
|
||||
Status: 204 No Content
|
||||
```
|
||||
|
||||
Server not found:
|
||||
@ -335,7 +475,7 @@ Status: 404 Not Found
|
||||
Invalid JSON body:
|
||||
|
||||
```
|
||||
Status: 400 Bad Request
|
||||
Status: 403 Forbidden
|
||||
```
|
||||
|
||||
#### Supported Request Parameter
|
||||
@ -345,14 +485,13 @@ Status: 400 Bad Request
|
||||
### Destroy a server
|
||||
|
||||
```
|
||||
DELETE /servers/:name
|
||||
DELETE /v1/servers/:name
|
||||
```
|
||||
|
||||
The _:name_ in the URI must map to a server name with all whitespace replaced
|
||||
with hyphens.
|
||||
|
||||
A server can only be deleted if the only relations in the _relationships_ object
|
||||
is the _self_ link.
|
||||
A server can only be deleted if it is not used by any services or monitors.
|
||||
|
||||
#### Response
|
||||
|
||||
@ -371,7 +510,7 @@ Status: 404 Not Found
|
||||
Server is in use:
|
||||
|
||||
```
|
||||
Status: 400 Bad Request
|
||||
Status: 403 Forbidden
|
||||
```
|
||||
|
||||
# **TODO:** Implement the following features
|
||||
@ -381,56 +520,20 @@ Status: 400 Bad Request
|
||||
Get all connections that are connected to a server.
|
||||
|
||||
```
|
||||
GET /servers/:name/connections
|
||||
GET /v1/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
|
||||
DELETE /v1/servers/:name/connections
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
@ -68,6 +68,13 @@ The MySQL backend protocol module now supports sending a proxy protocol header
|
||||
to the server. For more information, see the server section in the
|
||||
[Configuration guide](../Getting-Started/Configuration-Guide.md).
|
||||
|
||||
### KILL command support
|
||||
|
||||
The MySQL client protocol now detects `KILL <thread_id>` statements (binary and
|
||||
query forms) and kills the MaxScale session with the given id. This feature has
|
||||
some limitations, see [Limitations](../About/Limitations.md) for more
|
||||
information.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
[Here is a list of bugs fixed since the release of MaxScale 2.1.X.]()
|
||||
|
@ -102,4 +102,19 @@ static inline void atomic_synchronize()
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Atomic compare-and-swap of pointers
|
||||
*
|
||||
* @param variable Pointer to the variable
|
||||
* @param old_value Pointer to the expected value of @variable
|
||||
* @param new_value Stored value if @c variable is equal to @c old_value
|
||||
*
|
||||
* @return True if @c variable and @c old_value were equal
|
||||
*
|
||||
* @note If GCC __atomic builtins are available, the contents of @c variable are
|
||||
* written to @c old_value if the two are not equal. Do not rely on this behavior
|
||||
* and always do a separate read before attempting a compare-and-swap.
|
||||
*/
|
||||
bool atomic_cas_ptr(void **variable, void** old_value, void *new_value);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -36,6 +36,30 @@ MXS_BEGIN_DECLS
|
||||
#define MAX_ADMIN_PW_LEN 1024
|
||||
#define MAX_ADMIN_HOST_LEN 1024
|
||||
|
||||
/** JSON Pointers to key parts of JSON objects */
|
||||
#define MXS_JSON_PTR_ID "/data/id"
|
||||
#define MXS_JSON_PTR_PARAMETERS "/data/attributes/parameters"
|
||||
|
||||
/** Pointers to relation lists */
|
||||
#define MXS_JSON_PTR_RELATIONSHIPS_SERVERS "/data/relationships/servers/data"
|
||||
#define MXS_JSON_PTR_RELATIONSHIPS_SERVICES "/data/relationships/services/data"
|
||||
#define MXS_JSON_PTR_RELATIONSHIPS_MONITORS "/data/relationships/monitors/data"
|
||||
#define MXS_JSON_PTR_RELATIONSHIPS_FILTERS "/data/relationships/filters/data"
|
||||
|
||||
/** Parameter value JSON Pointers */
|
||||
#define MXS_JSON_PTR_PARAM_PORT MXS_JSON_PTR_PARAMETERS "/port"
|
||||
#define MXS_JSON_PTR_PARAM_ADDRESS MXS_JSON_PTR_PARAMETERS "/address"
|
||||
#define MXS_JSON_PTR_PARAM_PROTOCOL MXS_JSON_PTR_PARAMETERS "/protocol"
|
||||
#define MXS_JSON_PTR_PARAM_AUTHENTICATOR MXS_JSON_PTR_PARAMETERS "/authenticator"
|
||||
#define MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS MXS_JSON_PTR_PARAMETERS "/authenticator_options"
|
||||
#define MXS_JSON_PTR_PARAM_SSL_KEY MXS_JSON_PTR_PARAMETERS "/ssl_key"
|
||||
#define MXS_JSON_PTR_PARAM_SSL_CERT MXS_JSON_PTR_PARAMETERS "/ssl_cert"
|
||||
#define MXS_JSON_PTR_PARAM_SSL_CA_CERT MXS_JSON_PTR_PARAMETERS "/ssl_ca_cert"
|
||||
#define MXS_JSON_PTR_PARAM_SSL_VERSION MXS_JSON_PTR_PARAMETERS "/ssl_version"
|
||||
#define MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH MXS_JSON_PTR_PARAMETERS "/ssl_cert_verify_depth"
|
||||
|
||||
#define MXS_JSON_PTR_MODULE "/data/attributes/module"
|
||||
|
||||
/**
|
||||
* Common configuration parameters names
|
||||
*
|
||||
@ -45,6 +69,7 @@ MXS_BEGIN_DECLS
|
||||
*/
|
||||
extern const char CN_ADDRESS[];
|
||||
extern const char CN_ADMIN_AUTH[];
|
||||
extern const char CN_ADMIN_ENABLED[];
|
||||
extern const char CN_ADMIN_HOST[];
|
||||
extern const char CN_ADMIN_PASSWORD[];
|
||||
extern const char CN_ADMIN_PORT[];
|
||||
@ -52,6 +77,7 @@ extern const char CN_ADMIN_USER[];
|
||||
extern const char CN_ADMIN_SSL_KEY[];
|
||||
extern const char CN_ADMIN_SSL_CERT[];
|
||||
extern const char CN_ADMIN_SSL_CA_CERT[];
|
||||
extern const char CN_ATTRIBUTES[];
|
||||
extern const char CN_AUTHENTICATOR[];
|
||||
extern const char CN_AUTHENTICATOR_OPTIONS[];
|
||||
extern const char CN_AUTH_ALL_SERVERS[];
|
||||
@ -60,12 +86,14 @@ extern const char CN_AUTH_READ_TIMEOUT[];
|
||||
extern const char CN_AUTH_WRITE_TIMEOUT[];
|
||||
extern const char CN_AUTO[];
|
||||
extern const char CN_CONNECTION_TIMEOUT[];
|
||||
extern const char CN_DATA[];
|
||||
extern const char CN_DEFAULT[];
|
||||
extern const char CN_ENABLE_ROOT_USER[];
|
||||
extern const char CN_FEEDBACK[];
|
||||
extern const char CN_FILTERS[];
|
||||
extern const char CN_FILTER[];
|
||||
extern const char CN_GATEWAY[];
|
||||
extern const char CN_ID[];
|
||||
extern const char CN_LISTENER[];
|
||||
extern const char CN_LISTENERS[];
|
||||
extern const char CN_LOCALHOST_MATCH_WILDCARD_HOST[];
|
||||
@ -75,6 +103,7 @@ extern const char CN_MAXSCALE[];
|
||||
extern const char CN_MAX_CONNECTIONS[];
|
||||
extern const char CN_MAX_RETRY_INTERVAL[];
|
||||
extern const char CN_MODULE[];
|
||||
extern const char CN_MODULES[];
|
||||
extern const char CN_MONITORS[];
|
||||
extern const char CN_MONITOR[];
|
||||
extern const char CN_MS_TIMESTAMP[];
|
||||
@ -89,6 +118,7 @@ extern const char CN_PROTOCOL[];
|
||||
extern const char CN_QUERY_CLASSIFIER[];
|
||||
extern const char CN_QUERY_CLASSIFIER_ARGS[];
|
||||
extern const char CN_RELATIONSHIPS[];
|
||||
extern const char CN_LINKS[];
|
||||
extern const char CN_REQUIRED[];
|
||||
extern const char CN_RETRY_ON_FAILURE[];
|
||||
extern const char CN_ROUTER[];
|
||||
@ -98,6 +128,7 @@ extern const char CN_SERVERS[];
|
||||
extern const char CN_SERVER[];
|
||||
extern const char CN_SERVICES[];
|
||||
extern const char CN_SERVICE[];
|
||||
extern const char CN_SESSIONS[];
|
||||
extern const char CN_SKIP_PERMISSION_CHECKS[];
|
||||
extern const char CN_SOCKET[];
|
||||
extern const char CN_STATE[];
|
||||
@ -166,6 +197,7 @@ typedef struct
|
||||
char admin_host[MAX_ADMIN_HOST_LEN]; /**< Admin interface host */
|
||||
uint16_t admin_port; /**< Admin interface port */
|
||||
bool admin_auth; /**< Admin interface authentication */
|
||||
bool admin_enabled; /**< Admin interface is enabled */
|
||||
char admin_ssl_key[PATH_MAX]; /**< Admin SSL key */
|
||||
char admin_ssl_cert[PATH_MAX]; /**< Admin SSL cert */
|
||||
char admin_ssl_ca_cert[PATH_MAX]; /**< Admin SSL CA cert */
|
||||
|
@ -313,7 +313,7 @@ int dcb_isvalid(DCB *); /* Check the DCB is in the linked li
|
||||
int dcb_count_by_usage(DCB_USAGE); /* Return counts of DCBs */
|
||||
int dcb_persistent_clean_count(DCB *, int, bool); /* Clean persistent and return count */
|
||||
void dcb_hangup_foreach (struct server* server);
|
||||
uint32_t dcb_get_session_id(DCB* dcb);
|
||||
uint64_t dcb_get_session_id(DCB* dcb);
|
||||
char *dcb_role_name(DCB *); /* Return the name of a role */
|
||||
int dcb_accept_SSL(DCB* dcb);
|
||||
int dcb_connect_SSL(DCB* dcb);
|
||||
|
@ -68,9 +68,61 @@ extern void hkshutdown();
|
||||
*/
|
||||
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 hkshow_tasks(DCB *pdcb);
|
||||
/**
|
||||
* @brief Add a new task
|
||||
*
|
||||
* The task will be first run @c frequency seconds after this call is
|
||||
* made and will the be executed repeatedly every frequency seconds
|
||||
* until the task is removed.
|
||||
*
|
||||
* Task names must be unique.
|
||||
*
|
||||
* @param name Task name
|
||||
* @param task Function to execute
|
||||
* @param data Data passed to function as the parameter
|
||||
* @param frequency Frequency of execution
|
||||
*
|
||||
* @return 1 if task was added
|
||||
*/
|
||||
int hktask_add(const char *name, void (*task)(void *) , void *data, int frequency);
|
||||
|
||||
/**
|
||||
* @brief Add oneshot task
|
||||
*
|
||||
* The task will only execute once.
|
||||
*
|
||||
* @param name Task name
|
||||
* @param task Function to execute
|
||||
* @param data Data passed to function as the parameter
|
||||
* @param when Number of seconds to wait until task is executed
|
||||
*
|
||||
* @return 1 if task was added
|
||||
*/
|
||||
int hktask_oneshot(const char *name, void (*task)(void *) , void *data, int when);
|
||||
|
||||
/**
|
||||
* @brief Remove a task
|
||||
*
|
||||
* @param name Task name
|
||||
*
|
||||
* @return 1 if the task was removed
|
||||
*/
|
||||
int hktask_remove(const char *name);
|
||||
|
||||
/**
|
||||
* @brief Show the tasks that are scheduled for the house keeper
|
||||
*
|
||||
* @param pdcb The DCB to send to output
|
||||
*/
|
||||
void hkshow_tasks(DCB *pdcb);
|
||||
|
||||
/**
|
||||
* @brief Show tasks as JSON resource
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
*
|
||||
* @return Collection of JSON formatted task resources
|
||||
*/
|
||||
json_t* hk_tasks_json(const char* host);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
90
include/maxscale/json_api.h
Normal file
90
include/maxscale/json_api.h
Normal file
@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2019-07-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file Helper functions for creating JSON API conforming objects
|
||||
*/
|
||||
|
||||
#include <maxscale/cppdefs.hh>
|
||||
#include <maxscale/jansson.hh>
|
||||
|
||||
MXS_BEGIN_DECLS
|
||||
|
||||
/** Resource endpoints */
|
||||
#define MXS_JSON_API_SERVERS "/servers/"
|
||||
#define MXS_JSON_API_SERVICES "/services/"
|
||||
#define MXS_JSON_API_FILTERS "/filters/"
|
||||
#define MXS_JSON_API_MONITORS "/monitors/"
|
||||
#define MXS_JSON_API_SESSIONS "/sessions/"
|
||||
#define MXS_JSON_API_MAXSCALE "/maxscale/"
|
||||
#define MXS_JSON_API_THREADS "/maxscale/threads/"
|
||||
#define MXS_JSON_API_LOGS "/maxscale/logs/"
|
||||
#define MXS_JSON_API_TASKS "/maxscale/tasks/"
|
||||
#define MXS_JSON_API_MODULES "/maxscale/modules/"
|
||||
|
||||
/**
|
||||
* @brief Create a JSON object
|
||||
*
|
||||
* The caller should add a `data` field to the returned object.
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
* @param self Endpoint of this resource
|
||||
* @param data The JSON data, either an array or an object
|
||||
*
|
||||
* @return A valid top-level JSON API object
|
||||
*/
|
||||
json_t* mxs_json_resource(const char* host, const char* self, json_t* data);
|
||||
|
||||
/**
|
||||
* @brief Create an empty relationship object
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
* @param endpoint The endpoint for the resource's collection
|
||||
*
|
||||
* @return New relationship object
|
||||
*/
|
||||
json_t* mxs_json_relationship(const char* host, const char* endpoint);
|
||||
|
||||
/**
|
||||
* @brief Add an item to a relationship object
|
||||
*
|
||||
* @param rel Relationship created with mxs_json_relationship
|
||||
* @param id The resource identifier
|
||||
* @param type The resource type
|
||||
*/
|
||||
void mxs_json_add_relation(json_t* rel, const char* id, const char* type);
|
||||
|
||||
/**
|
||||
* @brief Create self link object
|
||||
*
|
||||
* The self link points to the object itself.
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
* @param path Base path to the resource collection
|
||||
* @param id The identified of this resource
|
||||
*
|
||||
* @return New self link object
|
||||
*/
|
||||
json_t* mxs_json_self_link(const char* host, const char* path, const char* id);
|
||||
|
||||
/**
|
||||
* @brief Return value at provided JSON Pointer
|
||||
*
|
||||
* @param json JSON object
|
||||
* @param json_ptr JSON Pointer to object
|
||||
* @return Pointed value or NULL if no value is found
|
||||
*/
|
||||
json_t* mxs_json_pointer(json_t* json, const char* json_ptr);
|
||||
|
||||
MXS_END_DECLS
|
@ -47,8 +47,14 @@ typedef struct servlistener
|
||||
struct users *users; /**< The user data for this listener */
|
||||
struct service* service; /**< The service which used by this listener */
|
||||
SPINLOCK lock;
|
||||
int active; /**< True if the port has not been deleted */
|
||||
struct servlistener *next; /**< Next service protocol */
|
||||
} SERV_LISTENER;
|
||||
} SERV_LISTENER; // TODO: Rename to LISTENER
|
||||
|
||||
typedef struct listener_iterator
|
||||
{
|
||||
SERV_LISTENER* current;
|
||||
} LISTENER_ITERATOR;
|
||||
|
||||
/**
|
||||
* @brief Serialize a listener to a file
|
||||
@ -79,4 +85,40 @@ int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version);
|
||||
void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert);
|
||||
int listener_init_SSL(SSL_LISTENER *ssl_listener);
|
||||
|
||||
/**
|
||||
* @brief Check if listener is active
|
||||
*
|
||||
* @param listener Listener to check
|
||||
*
|
||||
* @return True if listener is active
|
||||
*/
|
||||
bool listener_is_active(SERV_LISTENER* listener);
|
||||
|
||||
/**
|
||||
* @brief Modify listener active state
|
||||
*
|
||||
* @param listener Listener to modify
|
||||
* @param active True to activate, false to disable
|
||||
*/
|
||||
void listener_set_active(SERV_LISTENER* listener, bool active);
|
||||
|
||||
/**
|
||||
* @brief Initialize a listener iterator for iterating service listeners
|
||||
*
|
||||
* @param service Service whose listeners are iterated
|
||||
* @param iter Pointer to iterator to initialize
|
||||
*
|
||||
* @return The first value pointed by the iterator
|
||||
*/
|
||||
SERV_LISTENER* listener_iterator_init(const struct service* service, LISTENER_ITERATOR* iter);
|
||||
|
||||
/**
|
||||
* @brief Get the next listener
|
||||
*
|
||||
* @param iter Listener iterator
|
||||
*
|
||||
* @return The next listener or NULL on end of list
|
||||
*/
|
||||
SERV_LISTENER* listener_iterator_next(LISTENER_ITERATOR* iter);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -13,11 +13,14 @@
|
||||
*/
|
||||
|
||||
#include <maxscale/cdefs.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <maxscale/jansson.h>
|
||||
|
||||
MXS_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
@ -105,6 +108,7 @@ void mxs_log_set_augmentation(int bits);
|
||||
void mxs_log_set_throttling(const MXS_LOG_THROTTLING* throttling);
|
||||
|
||||
void mxs_log_get_throttling(MXS_LOG_THROTTLING* throttling);
|
||||
json_t* mxs_logs_to_json(const char* host);
|
||||
|
||||
static inline bool mxs_log_priority_is_enabled(int priority)
|
||||
{
|
||||
|
@ -320,7 +320,7 @@ typedef struct
|
||||
uint32_t server_capabilities; /*< server capabilities, created or received */
|
||||
uint32_t client_capabilities; /*< client capabilities, created or received */
|
||||
uint32_t extra_capabilities; /*< MariaDB 10.2 capabilities */
|
||||
uint32_t tid; /*< MySQL Thread ID, in handshake */
|
||||
uint64_t thread_id; /*< MySQL Thread ID. Send only 32bits in handshake. */
|
||||
unsigned int charset; /*< MySQL character set at connect time */
|
||||
int ignore_replies; /*< How many replies should be discarded */
|
||||
GWBUF* stored_query; /*< Temporarily stored queries */
|
||||
|
@ -304,6 +304,16 @@ json_t* service_to_json(const SERVICE* service, const char* host);
|
||||
*/
|
||||
json_t* service_list_to_json(const char* host);
|
||||
|
||||
/**
|
||||
* @brief Convert service listeners to JSON
|
||||
*
|
||||
* @param service Service whose listeners are converted
|
||||
* @param host Hostname of this server
|
||||
*
|
||||
* @return Array of JSON format listeners
|
||||
*/
|
||||
json_t* service_listeners_to_json(const SERVICE* service, const char* host);
|
||||
|
||||
/**
|
||||
* @brief Get links to services that relate to a server
|
||||
*
|
||||
|
@ -134,7 +134,7 @@ typedef struct session
|
||||
{
|
||||
skygw_chk_t ses_chk_top;
|
||||
mxs_session_state_t state; /*< Current descriptor state */
|
||||
uint32_t ses_id; /*< Unique session identifier */
|
||||
uint64_t ses_id; /*< Unique session identifier */
|
||||
struct dcb *client_dcb; /*< The client connection */
|
||||
struct mxs_router_session *router_session; /*< The router instance data */
|
||||
MXS_SESSION_STATS stats; /*< Session statistics */
|
||||
@ -194,7 +194,7 @@ MXS_SESSION *session_alloc(struct service *, struct dcb *);
|
||||
* @param id Id for the new session.
|
||||
* @return The newly created session or NULL if an error occurred
|
||||
*/
|
||||
MXS_SESSION *session_alloc_with_id(struct service *, struct dcb *, uint32_t);
|
||||
MXS_SESSION *session_alloc_with_id(struct service *, struct dcb *, uint64_t);
|
||||
|
||||
MXS_SESSION *session_set_dummy(struct dcb *);
|
||||
|
||||
@ -352,14 +352,14 @@ static inline bool session_set_autocommit(MXS_SESSION* ses, bool autocommit)
|
||||
*
|
||||
* @note The caller must free the session reference by calling session_put_ref
|
||||
*/
|
||||
MXS_SESSION* session_get_by_id(uint32_t id);
|
||||
MXS_SESSION* session_get_by_id(uint64_t id);
|
||||
|
||||
/**
|
||||
* Get the next available unique (assuming no overflow) session id number.
|
||||
*
|
||||
* @return An unused session id.
|
||||
*/
|
||||
uint32_t session_get_next_id();
|
||||
uint64_t session_get_next_id();
|
||||
|
||||
/**
|
||||
* @brief Close a session
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <maxscale/cdefs.h>
|
||||
#include <maxscale/poll.h>
|
||||
#include <maxscale/thread.h>
|
||||
#include <maxscale/jansson.h>
|
||||
|
||||
MXS_BEGIN_DECLS
|
||||
|
||||
@ -142,4 +143,25 @@ MXS_SESSION* mxs_worker_deregister_session(uint64_t id);
|
||||
*/
|
||||
MXS_SESSION* mxs_worker_find_session(uint64_t id);
|
||||
|
||||
/**
|
||||
* @brief Convert a worker to JSON format
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
* @param id ID of the worker
|
||||
*
|
||||
* @return JSON resource representing the worker
|
||||
*/
|
||||
json_t* mxs_worker_to_json(const char* host, int id);
|
||||
|
||||
/**
|
||||
* Convert workers into JSON format
|
||||
*
|
||||
* @param host Hostname of this server
|
||||
*
|
||||
* @return A JSON resource collection of workers
|
||||
*
|
||||
* @see mxs_json_resource()
|
||||
*/
|
||||
json_t* mxs_worker_list_to_json(const char* host);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -15,6 +15,7 @@ add_library(maxscale-common SHARED
|
||||
housekeeper.cc
|
||||
httprequest.cc
|
||||
httpresponse.cc
|
||||
json_api.cc
|
||||
listener.cc
|
||||
load_utils.cc
|
||||
log_manager.cc
|
||||
|
@ -55,7 +55,7 @@ int kv_iter(void *cls,
|
||||
{
|
||||
size_t* rval = (size_t*)cls;
|
||||
|
||||
if (strcmp(key, "Content-Length") == 0)
|
||||
if (strcasecmp(key, "Content-Length") == 0)
|
||||
{
|
||||
*rval = atoi(value);
|
||||
return MHD_NO;
|
||||
@ -102,7 +102,17 @@ int Client::process(string url, string method, const char* upload_data, size_t *
|
||||
}
|
||||
|
||||
HttpRequest request(m_connection, url, method, json);
|
||||
HttpResponse reply = resource_handle_request(request);
|
||||
HttpResponse reply(MHD_HTTP_NOT_FOUND);
|
||||
|
||||
if (url == "/")
|
||||
{
|
||||
// Respond to pings with 200 OK
|
||||
reply = HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
else if (request.validate_api_version())
|
||||
{
|
||||
reply = resource_handle_request(request);
|
||||
}
|
||||
|
||||
string data;
|
||||
|
||||
@ -219,12 +229,12 @@ static bool host_to_sockaddr(const char* host, uint16_t port, struct sockaddr_st
|
||||
if (addr->ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in *ip = (struct sockaddr_in*)addr;
|
||||
ip->sin_port = htons(port);
|
||||
(*ip).sin_port = htons(port);
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr;
|
||||
ip->sin6_port = htons(port);
|
||||
(*ip).sin6_port = htons(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,3 +124,13 @@ void atomic_store_ptr(void **variable, void *value)
|
||||
(void)__sync_lock_test_and_set(variable, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool atomic_cas_ptr(void **variable, void** old_value, void *new_value)
|
||||
{
|
||||
#ifdef MXS_USE_ATOMIC_BUILTINS
|
||||
return __atomic_compare_exchange_n(variable, old_value, new_value,
|
||||
false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
#else
|
||||
return __sync_val_compare_and_swap(variable, *old_value, new_value);
|
||||
#endif
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/utils.h>
|
||||
#include <maxscale/paths.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/config.h"
|
||||
#include "maxscale/filter.h"
|
||||
@ -53,6 +54,7 @@ using std::string;
|
||||
|
||||
const char CN_ADDRESS[] = "address";
|
||||
const char CN_ADMIN_AUTH[] = "admin_auth";
|
||||
const char CN_ADMIN_ENABLED[] = "admin_enabled";
|
||||
const char CN_ADMIN_HOST[] = "admin_host";
|
||||
const char CN_ADMIN_PASSWORD[] = "admin_password";
|
||||
const char CN_ADMIN_PORT[] = "admin_port";
|
||||
@ -60,6 +62,7 @@ const char CN_ADMIN_USER[] = "admin_user";
|
||||
const char CN_ADMIN_SSL_KEY[] = "admin_ssl_key";
|
||||
const char CN_ADMIN_SSL_CERT[] = "admin_ssl_cert";
|
||||
const char CN_ADMIN_SSL_CA_CERT[] = "admin_ssl_ca_cert";
|
||||
const char CN_ATTRIBUTES[] = "attributes";
|
||||
const char CN_AUTHENTICATOR[] = "authenticator";
|
||||
const char CN_AUTHENTICATOR_OPTIONS[] = "authenticator_options";
|
||||
const char CN_AUTH_ALL_SERVERS[] = "auth_all_servers";
|
||||
@ -68,12 +71,14 @@ const char CN_AUTH_READ_TIMEOUT[] = "auth_read_timeout";
|
||||
const char CN_AUTH_WRITE_TIMEOUT[] = "auth_write_timeout";
|
||||
const char CN_AUTO[] = "auto";
|
||||
const char CN_CONNECTION_TIMEOUT[] = "connection_timeout";
|
||||
const char CN_DATA[] = "data";
|
||||
const char CN_DEFAULT[] = "default";
|
||||
const char CN_ENABLE_ROOT_USER[] = "enable_root_user";
|
||||
const char CN_FEEDBACK[] = "feedback";
|
||||
const char CN_FILTERS[] = "filters";
|
||||
const char CN_FILTER[] = "filter";
|
||||
const char CN_GATEWAY[] = "gateway";
|
||||
const char CN_ID[] = "id";
|
||||
const char CN_LISTENER[] = "listener";
|
||||
const char CN_LISTENERS[] = "listeners";
|
||||
const char CN_LOCALHOST_MATCH_WILDCARD_HOST[] = "localhost_match_wildcard_host";
|
||||
@ -83,6 +88,7 @@ const char CN_MAXSCALE[] = "maxscale";
|
||||
const char CN_MAX_CONNECTIONS[] = "max_connections";
|
||||
const char CN_MAX_RETRY_INTERVAL[] = "max_retry_interval";
|
||||
const char CN_MODULE[] = "module";
|
||||
const char CN_MODULES[] = "modules";
|
||||
const char CN_MONITORS[] = "monitors";
|
||||
const char CN_MONITOR[] = "monitor";
|
||||
const char CN_MS_TIMESTAMP[] = "ms_timestamp";
|
||||
@ -97,6 +103,7 @@ const char CN_PROTOCOL[] = "protocol";
|
||||
const char CN_QUERY_CLASSIFIER[] = "query_classifier";
|
||||
const char CN_QUERY_CLASSIFIER_ARGS[] = "query_classifier_args";
|
||||
const char CN_RELATIONSHIPS[] = "relationships";
|
||||
const char CN_LINKS[] = "links";
|
||||
const char CN_REQUIRED[] = "required";
|
||||
const char CN_RETRY_ON_FAILURE[] = "retry_on_failure";
|
||||
const char CN_ROUTER[] = "router";
|
||||
@ -106,6 +113,7 @@ const char CN_SERVERS[] = "servers";
|
||||
const char CN_SERVER[] = "server";
|
||||
const char CN_SERVICES[] = "services";
|
||||
const char CN_SERVICE[] = "service";
|
||||
const char CN_SESSIONS[] = "sessions";
|
||||
const char CN_SKIP_PERMISSION_CHECKS[] = "skip_permission_checks";
|
||||
const char CN_SOCKET[] = "socket";
|
||||
const char CN_STATE[] = "state";
|
||||
@ -1545,6 +1553,10 @@ handle_global_item(const char *name, const char *value)
|
||||
{
|
||||
gateway.admin_auth = config_truth_value(value);
|
||||
}
|
||||
else if (strcmp(name, CN_ADMIN_ENABLED) == 0)
|
||||
{
|
||||
gateway.admin_enabled = config_truth_value(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; lognames[i].name; i++)
|
||||
@ -1767,6 +1779,7 @@ global_defaults()
|
||||
gateway.skip_permission_checks = false;
|
||||
gateway.admin_port = DEFAULT_ADMIN_HTTP_PORT;
|
||||
gateway.admin_auth = false;
|
||||
gateway.admin_enabled = true;
|
||||
strcpy(gateway.admin_host, DEFAULT_ADMIN_HOST);
|
||||
strcpy(gateway.admin_user, INET_DEFAULT_USERNAME);
|
||||
strcpy(gateway.admin_password, INET_DEFAULT_PASSWORD);
|
||||
@ -3850,20 +3863,24 @@ int config_parse_server_list(const char *servers, char ***output_array)
|
||||
|
||||
json_t* config_paths_to_json(const char* host)
|
||||
{
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, "libdir", json_string(get_libdir()));
|
||||
json_object_set_new(attr, "datadir", json_string(get_datadir()));
|
||||
json_object_set_new(attr, "process_datadir", json_string(get_process_datadir()));
|
||||
json_object_set_new(attr, "cachedir", json_string(get_cachedir()));
|
||||
json_object_set_new(attr, "configdir", json_string(get_configdir()));
|
||||
json_object_set_new(attr, "config_persistdir", json_string(get_config_persistdir()));
|
||||
json_object_set_new(attr, "module_configdir", json_string(get_module_configdir()));
|
||||
json_object_set_new(attr, "piddir", json_string(get_piddir()));
|
||||
json_object_set_new(attr, "logdir", json_string(get_logdir()));
|
||||
json_object_set_new(attr, "langdir", json_string(get_langdir()));
|
||||
json_object_set_new(attr, "execdir", json_string(get_execdir()));
|
||||
json_object_set_new(attr, "connector_plugindir", json_string(get_connector_plugindir()));
|
||||
|
||||
json_t* obj = json_object();
|
||||
json_object_set_new(obj, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(obj, CN_ID, json_string(CN_MAXSCALE));
|
||||
json_object_set_new(obj, CN_TYPE, json_string(CN_MAXSCALE));
|
||||
|
||||
json_object_set_new(obj, "libdir", json_string(get_libdir()));
|
||||
json_object_set_new(obj, "datadir", json_string(get_datadir()));
|
||||
json_object_set_new(obj, "process_datadir", json_string(get_process_datadir()));
|
||||
json_object_set_new(obj, "cachedir", json_string(get_cachedir()));
|
||||
json_object_set_new(obj, "configdir", json_string(get_configdir()));
|
||||
json_object_set_new(obj, "config_persistdir", json_string(get_config_persistdir()));
|
||||
json_object_set_new(obj, "module_configdir", json_string(get_module_configdir()));
|
||||
json_object_set_new(obj, "piddir", json_string(get_piddir()));
|
||||
json_object_set_new(obj, "logdir", json_string(get_logdir()));
|
||||
json_object_set_new(obj, "langdir", json_string(get_langdir()));
|
||||
json_object_set_new(obj, "execdir", json_string(get_execdir()));
|
||||
json_object_set_new(obj, "connector_plugindir", json_string(get_connector_plugindir()));
|
||||
|
||||
return obj;
|
||||
return mxs_json_resource(host, MXS_JSON_API_MAXSCALE, obj);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <strings.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
@ -25,6 +26,7 @@
|
||||
#include <maxscale/paths.h>
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/jansson.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/config.h"
|
||||
#include "maxscale/monitor.h"
|
||||
@ -32,6 +34,7 @@
|
||||
#include "maxscale/service.h"
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::set;
|
||||
using mxs::Closer;
|
||||
|
||||
@ -316,12 +319,8 @@ bool runtime_alter_server(SERVER *server, const char *key, const char *value)
|
||||
}
|
||||
}
|
||||
|
||||
if (valid)
|
||||
if (valid && server_serialize(server))
|
||||
{
|
||||
if (server->created_online)
|
||||
{
|
||||
server_serialize(server);
|
||||
}
|
||||
MXS_NOTICE("Updated server '%s': %s=%s", server->unique_name, key, value);
|
||||
}
|
||||
|
||||
@ -702,6 +701,10 @@ bool runtime_create_monitor(const char *name, const char *module)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Can't create monitor, it already exists");
|
||||
}
|
||||
|
||||
spinlock_release(&crt_lock);
|
||||
return rval;
|
||||
@ -756,47 +759,40 @@ static bool extract_relations(json_t* json, set<string>& relations,
|
||||
bool (*relation_check)(const string&, const string&))
|
||||
{
|
||||
bool rval = true;
|
||||
json_t* rel;
|
||||
|
||||
if ((rel = json_object_get(json, CN_RELATIONSHIPS)))
|
||||
for (int i = 0; relation_types[i]; i++)
|
||||
{
|
||||
for (int i = 0; relation_types[i]; i++)
|
||||
json_t* arr = mxs_json_pointer(json, relation_types[i]);
|
||||
|
||||
if (arr && json_is_array(arr))
|
||||
{
|
||||
json_t* arr = json_object_get(rel, relation_types[i]);
|
||||
size_t size = json_array_size(arr);
|
||||
|
||||
if (arr)
|
||||
for (size_t j = 0; j < size; j++)
|
||||
{
|
||||
size_t size = json_array_size(arr);
|
||||
json_t* obj = json_array_get(arr, j);
|
||||
json_t* id = json_object_get(obj, CN_ID);
|
||||
json_t* type = mxs_json_pointer(obj, CN_TYPE);
|
||||
|
||||
for (size_t j = 0; j < size; j++)
|
||||
if (id && json_is_string(id) &&
|
||||
type && json_is_string(type))
|
||||
{
|
||||
json_t* t = json_array_get(arr, j);
|
||||
string id_value = json_string_value(id);
|
||||
string type_value = json_string_value(type);
|
||||
|
||||
if (json_is_string(t))
|
||||
if (relation_check(type_value, id_value))
|
||||
{
|
||||
string value = json_string_value(t);
|
||||
|
||||
// Remove the link part
|
||||
size_t pos = value.find_last_of("/");
|
||||
if (pos != string::npos)
|
||||
{
|
||||
value.erase(0, pos + 1);
|
||||
}
|
||||
|
||||
if (relation_check(relation_types[i], value))
|
||||
{
|
||||
relations.insert(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
relations.insert(id_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -804,10 +800,10 @@ static bool extract_relations(json_t* json, set<string>& relations,
|
||||
return rval;
|
||||
}
|
||||
|
||||
static inline const char* string_or_null(json_t* json, const char* name)
|
||||
static inline const char* string_or_null(json_t* json, const char* path)
|
||||
{
|
||||
const char* rval = NULL;
|
||||
json_t* value = json_object_get(json, name);
|
||||
json_t* value = mxs_json_pointer(json, path);
|
||||
|
||||
if (value && json_is_string(value))
|
||||
{
|
||||
@ -819,29 +815,19 @@ static inline const char* string_or_null(json_t* json, const char* name)
|
||||
|
||||
static bool server_contains_required_fields(json_t* json)
|
||||
{
|
||||
bool rval = false;
|
||||
json_t* value;
|
||||
json_t* id = mxs_json_pointer(json, MXS_JSON_PTR_ID);
|
||||
json_t* port = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT);
|
||||
json_t* address = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS);
|
||||
|
||||
if ((value = json_object_get(json, CN_NAME)) && json_is_string(value))
|
||||
{
|
||||
/** Object has a name field */
|
||||
json_t* param = json_object_get(json, CN_PARAMETERS);
|
||||
|
||||
if (param &&
|
||||
(value = json_object_get(param, CN_ADDRESS)) && json_is_string(value) &&
|
||||
(value = json_object_get(param, CN_PORT)) && json_is_integer(value))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
return (id && json_is_string(id) &&
|
||||
address && json_is_string(address) &&
|
||||
port && json_is_integer(port));
|
||||
}
|
||||
|
||||
const char* server_relation_types[] =
|
||||
{
|
||||
CN_SERVICES,
|
||||
CN_MONITORS,
|
||||
MXS_JSON_PTR_RELATIONSHIPS_SERVICES,
|
||||
MXS_JSON_PTR_RELATIONSHIPS_MONITORS,
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -883,31 +869,35 @@ static bool link_server_to_objects(SERVER* server, set<string>& relations)
|
||||
return rval;
|
||||
}
|
||||
|
||||
static string json_int_to_string(json_t* json)
|
||||
{
|
||||
char str[25]; // Enough to store any 64-bit integer value
|
||||
int64_t i = json_integer_value(json);
|
||||
snprintf(str, sizeof(str), "%ld", i);
|
||||
return string(str);
|
||||
}
|
||||
|
||||
SERVER* runtime_create_server_from_json(json_t* json)
|
||||
{
|
||||
SERVER* rval = NULL;
|
||||
|
||||
if (server_contains_required_fields(json))
|
||||
{
|
||||
const char* name = json_string_value(json_object_get(json, CN_NAME));
|
||||
json_t* params = json_object_get(json, CN_PARAMETERS);
|
||||
const char* address = json_string_value(json_object_get(params, CN_ADDRESS));
|
||||
const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
|
||||
const char* address = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS));
|
||||
|
||||
/** The port needs to be in string format */
|
||||
char port[200]; // Enough to store any port value
|
||||
int i = json_integer_value(json_object_get(params, CN_PORT));
|
||||
snprintf(port, sizeof(port), "%d", i);
|
||||
string port = json_int_to_string(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT));
|
||||
|
||||
/** Optional parameters */
|
||||
const char* protocol = string_or_null(params, CN_PROTOCOL);
|
||||
const char* authenticator = string_or_null(params, CN_AUTHENTICATOR);
|
||||
const char* authenticator_options = string_or_null(params, CN_AUTHENTICATOR_OPTIONS);
|
||||
|
||||
const char* protocol = string_or_null(json, MXS_JSON_PTR_PARAM_PROTOCOL);
|
||||
const char* authenticator = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR);
|
||||
const char* authenticator_options = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS);
|
||||
|
||||
set<string> relations;
|
||||
|
||||
if (extract_relations(json, relations, server_relation_types, server_relation_is_valid) &&
|
||||
runtime_create_server(name, address, port, protocol, authenticator, authenticator_options))
|
||||
runtime_create_server(name, address, port.c_str(), protocol, authenticator, authenticator_options))
|
||||
{
|
||||
rval = server_find_by_unique_name(name);
|
||||
ss_dassert(rval);
|
||||
@ -919,6 +909,10 @@ SERVER* runtime_create_server_from_json(json_t* json)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Invalid request JSON: %s", mxs::json_dump(json).c_str());
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
@ -961,14 +955,14 @@ bool runtime_alter_server_from_json(SERVER* server, json_t* new_json)
|
||||
|
||||
if (server_to_object_relations(server, old_json.get(), new_json))
|
||||
{
|
||||
json_t* parameters = json_object_get(new_json, CN_PARAMETERS);
|
||||
json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS);
|
||||
rval = true;
|
||||
json_t* parameters = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
|
||||
json_t* old_parameters = mxs_json_pointer(old_json.get(), MXS_JSON_PTR_PARAMETERS);
|
||||
|
||||
ss_dassert(old_parameters);
|
||||
|
||||
if (parameters)
|
||||
{
|
||||
rval = true;
|
||||
const char* key;
|
||||
json_t* value;
|
||||
|
||||
@ -994,7 +988,7 @@ bool runtime_alter_server_from_json(SERVER* server, json_t* new_json)
|
||||
|
||||
const char* object_relation_types[] =
|
||||
{
|
||||
CN_SERVERS,
|
||||
MXS_JSON_PTR_RELATIONSHIPS_SERVERS,
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -1015,8 +1009,8 @@ static bool validate_monitor_json(json_t* json)
|
||||
bool rval = false;
|
||||
json_t* value;
|
||||
|
||||
if ((value = json_object_get(json, CN_NAME)) && json_is_string(value) &&
|
||||
(value = json_object_get(json, CN_MODULE)) && json_is_string(value))
|
||||
if ((value = mxs_json_pointer(json, MXS_JSON_PTR_ID)) && json_is_string(value) &&
|
||||
(value = mxs_json_pointer(json, MXS_JSON_PTR_MODULE)) && json_is_string(value))
|
||||
{
|
||||
set<string> relations;
|
||||
if (extract_relations(json, relations, object_relation_types, object_relation_is_valid))
|
||||
@ -1071,8 +1065,8 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json)
|
||||
|
||||
if (validate_monitor_json(json))
|
||||
{
|
||||
const char* name = json_string_value(json_object_get(json, CN_NAME));
|
||||
const char* module = json_string_value(json_object_get(json, CN_MODULE));
|
||||
const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
|
||||
const char* module = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_MODULE));
|
||||
|
||||
if (runtime_create_monitor(name, module))
|
||||
{
|
||||
@ -1086,6 +1080,10 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Invalid request JSON: %s", mxs::json_dump(json).c_str());
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
@ -1128,15 +1126,15 @@ bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json)
|
||||
|
||||
if (object_to_server_relations(monitor->name, old_json.get(), new_json))
|
||||
{
|
||||
rval = true;
|
||||
bool changed = false;
|
||||
json_t* parameters = json_object_get(new_json, CN_PARAMETERS);
|
||||
json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS);
|
||||
json_t* parameters = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
|
||||
json_t* old_parameters = mxs_json_pointer(old_json.get(), MXS_JSON_PTR_PARAMETERS);
|
||||
|
||||
ss_dassert(old_parameters);
|
||||
|
||||
if (parameters)
|
||||
{
|
||||
rval = true;
|
||||
const char* key;
|
||||
json_t* value;
|
||||
|
||||
@ -1194,8 +1192,8 @@ bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json)
|
||||
if (object_to_server_relations(service->name, old_json.get(), new_json))
|
||||
{
|
||||
bool changed = false;
|
||||
json_t* parameters = json_object_get(new_json, CN_PARAMETERS);
|
||||
json_t* old_parameters = json_object_get(old_json.get(), CN_PARAMETERS);
|
||||
json_t* parameters = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
|
||||
json_t* old_parameters = mxs_json_pointer(old_json.get(), MXS_JSON_PTR_PARAMETERS);
|
||||
|
||||
ss_dassert(old_parameters);
|
||||
|
||||
@ -1226,6 +1224,7 @@ bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json)
|
||||
}
|
||||
else if (paramset.find(key) != paramset.end())
|
||||
{
|
||||
/** Parameter can be altered */
|
||||
if (!runtime_alter_service(service, key, mxs::json_to_string(value).c_str()))
|
||||
{
|
||||
rval = false;
|
||||
@ -1237,3 +1236,156 @@ bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json)
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool runtime_alter_logs_from_json(json_t* json)
|
||||
{
|
||||
bool rval = false;
|
||||
json_t* param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
|
||||
|
||||
if (param && json_is_object(param))
|
||||
{
|
||||
json_t* value;
|
||||
rval = true;
|
||||
|
||||
if ((value = mxs_json_pointer(param, "highprecision")) && json_is_boolean(value))
|
||||
{
|
||||
if (json_is_boolean(value))
|
||||
{
|
||||
mxs_log_set_highprecision_enabled(json_boolean_value(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((value = mxs_json_pointer(param, "maxlog")) && json_is_boolean(value))
|
||||
{
|
||||
if (json_is_boolean(value))
|
||||
{
|
||||
mxs_log_set_maxlog_enabled(json_boolean_value(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((value = mxs_json_pointer(param, "syslog")))
|
||||
{
|
||||
if (json_is_boolean(value))
|
||||
{
|
||||
mxs_log_set_syslog_enabled(json_boolean_value(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((param = mxs_json_pointer(param, "throttling")) && json_is_object(param))
|
||||
{
|
||||
int intval;
|
||||
MXS_LOG_THROTTLING throttle;
|
||||
mxs_log_get_throttling(&throttle);
|
||||
|
||||
if ((value = mxs_json_pointer(param, "count")))
|
||||
{
|
||||
if (json_is_integer(value) && (intval = json_integer_value(value)) > 0)
|
||||
{
|
||||
throttle.count = intval;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((value = mxs_json_pointer(param, "suppress_ms")))
|
||||
{
|
||||
if (json_is_integer(value) && (intval = json_integer_value(value)) > 0)
|
||||
{
|
||||
throttle.suppress_ms = intval;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((value = mxs_json_pointer(param, "window_ms")))
|
||||
{
|
||||
if (json_is_integer(value) && (intval = json_integer_value(value)) > 0)
|
||||
{
|
||||
throttle.window_ms = intval;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rval)
|
||||
{
|
||||
mxs_log_set_throttling(&throttle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static bool validate_listener_json(json_t* json)
|
||||
{
|
||||
bool rval = false;
|
||||
json_t* param;
|
||||
|
||||
if ((param = mxs_json_pointer(json, MXS_JSON_PTR_ID)) && json_is_string(param) &&
|
||||
(param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS)) && json_is_object(param))
|
||||
{
|
||||
json_t* value;
|
||||
|
||||
if ((value = mxs_json_pointer(param, CN_PORT)) && json_is_integer(value) &&
|
||||
(!(value = mxs_json_pointer(param, CN_ADDRESS)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_AUTHENTICATOR)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_AUTHENTICATOR_OPTIONS)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_SSL_KEY)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_SSL_CERT)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_SSL_CA_CERT)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_SSL_VERSION)) || json_is_string(value)) &&
|
||||
(!(value = mxs_json_pointer(param, CN_SSL_CERT_VERIFY_DEPTH)) || json_is_integer(value)))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool runtime_create_listener_from_json(SERVICE* service, json_t* json)
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
if (validate_listener_json(json))
|
||||
{
|
||||
string port = json_int_to_string(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT));
|
||||
|
||||
const char* id = string_or_null(json, MXS_JSON_PTR_ID);
|
||||
const char* address = string_or_null(json, MXS_JSON_PTR_PARAM_ADDRESS);
|
||||
const char* protocol = string_or_null(json, MXS_JSON_PTR_PARAM_PROTOCOL);
|
||||
const char* authenticator = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR);
|
||||
const char* authenticator_options = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS);
|
||||
const char* ssl_key = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_KEY);
|
||||
const char* ssl_cert = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT);
|
||||
const char* ssl_ca_cert = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CA_CERT);
|
||||
const char* ssl_version = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_VERSION);
|
||||
const char* ssl_cert_verify_depth = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH);
|
||||
|
||||
rval = runtime_create_listener(service, id, address, port.c_str(), protocol,
|
||||
authenticator, authenticator_options,
|
||||
ssl_key, ssl_cert, ssl_ca_cert, ssl_version,
|
||||
ssl_cert_verify_depth);
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ static uint32_t dcb_process_poll_events(DCB *dcb, int thread_id, uint32_t ev);
|
||||
static void dcb_process_fake_events(DCB *dcb, int thread_id);
|
||||
static bool dcb_session_check(DCB *dcb, const char *);
|
||||
|
||||
uint32_t dcb_get_session_id(DCB *dcb)
|
||||
uint64_t dcb_get_session_id(DCB *dcb)
|
||||
{
|
||||
return (dcb && dcb->session) ? dcb->session->ses_id : 0;
|
||||
}
|
||||
@ -1673,7 +1673,7 @@ dprintDCB(DCB *pdcb, DCB *dcb)
|
||||
|
||||
if (dcb->session && dcb->session->state != SESSION_STATE_DUMMY)
|
||||
{
|
||||
dcb_printf(pdcb, "\tOwning Session: %" PRIu32 "\n", dcb->session->ses_id);
|
||||
dcb_printf(pdcb, "\tOwning Session: %" PRIu64 "\n", dcb->session->ses_id);
|
||||
}
|
||||
|
||||
if (dcb->writeq)
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/service.h>
|
||||
#include <maxscale/filter.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/config.h"
|
||||
#include "maxscale/modules.h"
|
||||
@ -497,48 +498,46 @@ json_t* filter_parameters_to_json(const MXS_FILTER_DEF* filter)
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* filter_to_json(const MXS_FILTER_DEF* filter, const char* host)
|
||||
json_t* filter_json_data(const MXS_FILTER_DEF* filter, const char* host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
|
||||
json_object_set_new(rval, CN_NAME, json_string(filter->name));
|
||||
json_object_set_new(rval, CN_MODULE, json_string(filter->module));
|
||||
json_object_set_new(rval, CN_PARAMETERS, filter_parameters_to_json(filter));
|
||||
json_object_set_new(rval, CN_ID, json_string(filter->name));
|
||||
json_object_set_new(rval, CN_TYPE, json_string(CN_FILTERS));
|
||||
|
||||
if (filter->obj && filter->filter)
|
||||
json_t* attr = json_object();
|
||||
|
||||
json_object_set_new(attr, CN_MODULE, json_string(filter->module));
|
||||
json_object_set_new(attr, CN_PARAMETERS, filter_parameters_to_json(filter));
|
||||
|
||||
if (filter->obj && filter->filter && filter->obj->diagnostics_json)
|
||||
{
|
||||
json_t* diag = filter->obj->diagnostics_json(filter->filter, NULL);
|
||||
|
||||
if (diag)
|
||||
{
|
||||
json_object_set_new(rval, "filter_diagnostics", diag);
|
||||
json_object_set_new(attr, "filter_diagnostics", diag);
|
||||
}
|
||||
}
|
||||
|
||||
/** Store relationships to other objects */
|
||||
json_t* rel = json_object();
|
||||
json_object_set_new(rel, CN_SERVICES, service_relations_to_filter(filter, host));
|
||||
|
||||
string self = host;
|
||||
self += "/filters/";
|
||||
self += filter->name;
|
||||
json_object_set_new(rel, CN_SELF, json_string(self.c_str()));
|
||||
|
||||
json_t* arr = service_relations_to_filter(filter, host);
|
||||
|
||||
if (json_array_size(arr) > 0)
|
||||
{
|
||||
json_object_set_new(rel, "services", arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_decref(arr);
|
||||
}
|
||||
|
||||
json_object_set_new(rval, "relationships", rel);
|
||||
json_object_set_new(rval, CN_RELATIONSHIPS, rel);
|
||||
json_object_set_new(rval, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(rval, CN_LINKS, mxs_json_self_link(host, CN_FILTERS, filter->name));
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* filter_to_json(const MXS_FILTER_DEF* filter, const char* host)
|
||||
{
|
||||
string self = MXS_JSON_API_FILTERS;
|
||||
self += filter->name;
|
||||
return mxs_json_resource(host, self.c_str(), filter_json_data(filter, host));
|
||||
}
|
||||
|
||||
json_t* filter_list_to_json(const char* host)
|
||||
{
|
||||
json_t* rval = json_array();
|
||||
@ -547,7 +546,7 @@ json_t* filter_list_to_json(const char* host)
|
||||
|
||||
for (MXS_FILTER_DEF* f = allFilters; f; f = f->next)
|
||||
{
|
||||
json_t* json = filter_to_json(f, host);
|
||||
json_t* json = filter_json_data(f, host);
|
||||
|
||||
if (json)
|
||||
{
|
||||
@ -557,7 +556,7 @@ json_t* filter_list_to_json(const char* host)
|
||||
|
||||
spinlock_release(&filter_spin);
|
||||
|
||||
return rval;
|
||||
return mxs_json_resource(host, MXS_JSON_API_FILTERS, rval);
|
||||
}
|
||||
|
||||
namespace maxscale
|
||||
|
@ -1982,16 +1982,19 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (mxs_admin_init())
|
||||
if (cnf->admin_enabled)
|
||||
{
|
||||
MXS_NOTICE("Started REST API on [%s]:%u", cnf->admin_host, cnf->admin_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* logerr = "Failed to initialize admin interface";
|
||||
print_log_n_stderr(true, true, logerr, logerr, 0);
|
||||
rc = MAXSCALE_INTERNALERROR;
|
||||
goto return_main;
|
||||
if (mxs_admin_init())
|
||||
{
|
||||
MXS_NOTICE("Started REST API on [%s]:%u", cnf->admin_host, cnf->admin_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* logerr = "Failed to initialize admin interface";
|
||||
print_log_n_stderr(true, true, logerr, logerr, 0);
|
||||
rc = MAXSCALE_INTERNALERROR;
|
||||
goto return_main;
|
||||
}
|
||||
}
|
||||
|
||||
MXS_NOTICE("MaxScale started with %d server threads.", config_threadcount());
|
||||
|
@ -11,34 +11,29 @@
|
||||
* Public License.
|
||||
*/
|
||||
#include <maxscale/housekeeper.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/config.h>
|
||||
#include <maxscale/semaphore.h>
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/thread.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
/**
|
||||
* @file housekeeper.c Provide a mechanism to run periodic tasks
|
||||
*
|
||||
* The housekeeper provides a mechanism to allow for tasks, function
|
||||
* calls basically, to be run on a tiem basis. A task may be run
|
||||
* calls basically, to be run on a time basis. A task may be run
|
||||
* repeatedly, with a given frequency (in seconds), or may be a one
|
||||
* shot task that will only be run once after a specified number of
|
||||
* seconds.
|
||||
*
|
||||
* The housekeeper also maintains a global variable, hkheartbeat, that
|
||||
* is incremented every 100ms.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 29/08/14 Mark Riddoch Initial implementation
|
||||
* 22/10/14 Mark Riddoch Addition of one-shot tasks
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -57,8 +52,7 @@ static THREAD hk_thr_handle;
|
||||
|
||||
static void hkthread(void *);
|
||||
|
||||
bool
|
||||
hkinit()
|
||||
bool hkinit()
|
||||
{
|
||||
bool inited = false;
|
||||
|
||||
@ -74,25 +68,7 @@ hkinit()
|
||||
return inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new task to the housekeepers lists of tasks that should be
|
||||
* run periodically.
|
||||
*
|
||||
* The task will be first run frequency seconds after this call is
|
||||
* made and will the be executed repeatedly every frequency seconds
|
||||
* until the task is removed.
|
||||
*
|
||||
* Task names must be unique.
|
||||
*
|
||||
* @param name The unique name for this housekeeper task
|
||||
* @param taskfn The function to call for the task
|
||||
* @param data Data to pass to the task function
|
||||
* @param frequency How often to run the task, expressed in seconds
|
||||
* @return Return the time in seconds when the task will be first run
|
||||
* if the task was added, otherwise 0
|
||||
*/
|
||||
int
|
||||
hktask_add(const char *name, void (*taskfn)(void *), void *data, int frequency)
|
||||
int hktask_add(const char *name, void (*taskfn)(void *), void *data, int frequency)
|
||||
{
|
||||
HKTASK *task, *ptr;
|
||||
|
||||
@ -144,21 +120,7 @@ hktask_add(const char *name, void (*taskfn)(void *), void *data, int frequency)
|
||||
return task->nextdue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a one-shot task to the housekeeper task list
|
||||
*
|
||||
* Task names must be unique.
|
||||
*
|
||||
* @param name The unique name for this housekeeper task
|
||||
* @param taskfn The function to call for the task
|
||||
* @param data Data to pass to the task function
|
||||
* @param when How many second until the task is executed
|
||||
* @return Return the time in seconds when the task will be first run
|
||||
* if the task was added, otherwise 0
|
||||
*
|
||||
*/
|
||||
int
|
||||
hktask_oneshot(const char *name, void (*taskfn)(void *), void *data, int when)
|
||||
int hktask_oneshot(const char *name, void (*taskfn)(void *), void *data, int when)
|
||||
{
|
||||
HKTASK *task, *ptr;
|
||||
|
||||
@ -196,15 +158,7 @@ hktask_oneshot(const char *name, void (*taskfn)(void *), void *data, int when)
|
||||
return task->nextdue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a named task from the housekeepers task list
|
||||
*
|
||||
* @param name The task name to remove
|
||||
* @return Returns 0 if the task could not be removed
|
||||
*/
|
||||
int
|
||||
hktask_remove(const char *name)
|
||||
int hktask_remove(const char *name)
|
||||
{
|
||||
HKTASK *ptr, *lptr = NULL;
|
||||
|
||||
@ -237,7 +191,6 @@ hktask_remove(const char *name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The housekeeper thread implementation.
|
||||
*
|
||||
@ -253,8 +206,7 @@ hktask_remove(const char *name)
|
||||
*
|
||||
* @param data Unused, here to satisfy the thread system
|
||||
*/
|
||||
void
|
||||
hkthread(void *data)
|
||||
void hkthread(void *data)
|
||||
{
|
||||
HKTASK *ptr;
|
||||
time_t now;
|
||||
@ -304,8 +256,7 @@ hkthread(void *data)
|
||||
MXS_NOTICE("Housekeeper shutting down.");
|
||||
}
|
||||
|
||||
void
|
||||
hkshutdown()
|
||||
void hkshutdown()
|
||||
{
|
||||
do_shutdown = true;
|
||||
atomic_synchronize();
|
||||
@ -321,13 +272,7 @@ void hkfinish()
|
||||
MXS_NOTICE("Housekeeper has shut down.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the tasks that are scheduled for the house keeper
|
||||
*
|
||||
* @param pdcb The DCB to send to output
|
||||
*/
|
||||
void
|
||||
hkshow_tasks(DCB *pdcb)
|
||||
void hkshow_tasks(DCB *pdcb)
|
||||
{
|
||||
HKTASK *ptr;
|
||||
struct tm tm;
|
||||
@ -350,3 +295,40 @@ hkshow_tasks(DCB *pdcb)
|
||||
}
|
||||
spinlock_release(&tasklock);
|
||||
}
|
||||
|
||||
json_t* hk_tasks_json(const char* host)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
|
||||
spinlock_acquire(&tasklock);
|
||||
|
||||
for (HKTASK* ptr = tasks; ptr; ptr = ptr->next)
|
||||
{
|
||||
struct tm tm;
|
||||
char buf[40];
|
||||
localtime_r(&ptr->nextdue, &tm);
|
||||
asctime_r(&tm, buf);
|
||||
char* nl = strchr(buf, '\n');
|
||||
ss_dassert(nl);
|
||||
*nl = '\0';
|
||||
|
||||
const char* task_type = ptr->type == HK_REPEATED ? "Repeated" : "One-Shot";
|
||||
|
||||
json_t* obj = json_object();
|
||||
|
||||
json_object_set_new(obj, CN_ID, json_string(ptr->name));
|
||||
json_object_set_new(obj, CN_TYPE, json_string("tasks"));
|
||||
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, "task_type", json_string(task_type));
|
||||
json_object_set_new(attr, "frequency", json_integer(ptr->frequency));
|
||||
json_object_set_new(attr, "next_execution", json_string(buf));
|
||||
|
||||
json_object_set_new(obj, CN_ATTRIBUTES, attr);
|
||||
json_array_append_new(arr, obj);
|
||||
}
|
||||
|
||||
spinlock_release(&tasklock);
|
||||
|
||||
return mxs_json_resource(host, MXS_JSON_API_TASKS, arr);
|
||||
}
|
||||
|
@ -74,20 +74,12 @@ static void process_uri(string& uri, std::deque<string>& uri_parts)
|
||||
my_uri.erase(my_uri.begin());
|
||||
}
|
||||
|
||||
if (my_uri.length() == 0)
|
||||
while (my_uri.length() > 0)
|
||||
{
|
||||
/** Special handling for the / resource */
|
||||
uri_parts.push_back("");
|
||||
}
|
||||
else
|
||||
{
|
||||
while (my_uri.length() > 0)
|
||||
{
|
||||
size_t pos = my_uri.find("/");
|
||||
string part = pos == string::npos ? my_uri : my_uri.substr(0, pos);
|
||||
my_uri.erase(0, pos == string::npos ? pos : pos + 1);
|
||||
uri_parts.push_back(part);
|
||||
}
|
||||
size_t pos = my_uri.find("/");
|
||||
string part = pos == string::npos ? my_uri : my_uri.substr(0, pos);
|
||||
my_uri.erase(0, pos == string::npos ? pos : pos + 1);
|
||||
uri_parts.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,9 +94,29 @@ HttpRequest::HttpRequest(struct MHD_Connection *connection, string url, string m
|
||||
|
||||
m_hostname = mxs_admin_https_enabled() ? HttpRequest::HTTPS_PREFIX : HttpRequest::HTTP_PREFIX;
|
||||
m_hostname += get_header(HTTP_HOST_HEADER);
|
||||
|
||||
if (m_hostname[m_hostname.size() - 1] != '/')
|
||||
{
|
||||
m_hostname += "/";
|
||||
}
|
||||
|
||||
m_hostname += MXS_REST_API_VERSION;
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HttpRequest::validate_api_version()
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
if (m_resource_parts.size() > 0 &&
|
||||
m_resource_parts[0] == MXS_REST_API_VERSION)
|
||||
{
|
||||
m_resource_parts.pop_front();
|
||||
rval = true;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
@ -30,9 +30,11 @@ HttpResponse::HttpResponse(int code, json_t* response):
|
||||
{
|
||||
string http_date = http_get_date();
|
||||
add_header(HTTP_RESPONSE_HEADER_DATE, http_date);
|
||||
add_header(HTTP_RESPONSE_HEADER_LAST_MODIFIED, http_date);
|
||||
// This ETag is the base64 encoding of `not-yet-implemented`
|
||||
add_header(HTTP_RESPONSE_HEADER_ETAG, "bm90LXlldC1pbXBsZW1lbnRlZAo");
|
||||
|
||||
if (m_body)
|
||||
{
|
||||
add_header(HTTP_RESPONSE_HEADER_CONTENT_TYPE, "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(const HttpResponse& response):
|
||||
|
147
server/core/json_api.cc
Normal file
147
server/core/json_api.cc
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2019-07-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <maxscale/config.h>
|
||||
#include <jansson.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
static json_t* self_link(const char* host, const char* endpoint)
|
||||
{
|
||||
json_t* self_link = json_object();
|
||||
string links = host;
|
||||
links += endpoint;
|
||||
json_object_set_new(self_link, CN_SELF, json_string(links.c_str()));
|
||||
|
||||
return self_link;
|
||||
}
|
||||
|
||||
json_t* mxs_json_resource(const char* host, const char* self, json_t* data)
|
||||
{
|
||||
ss_dassert(data && (json_is_array(data) || json_is_object(data) || json_is_null(data)));
|
||||
json_t* rval = json_object();
|
||||
json_object_set_new(rval, CN_LINKS, self_link(host, self));
|
||||
json_object_set_new(rval, CN_DATA, data);
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* mxs_json_relationship(const char* host, const char* endpoint)
|
||||
{
|
||||
json_t* rel = json_object();
|
||||
|
||||
/** Add the relation self link */
|
||||
json_object_set_new(rel, CN_LINKS, self_link(host, endpoint));
|
||||
|
||||
/** Add empty array of relations */
|
||||
json_object_set_new(rel, CN_DATA, json_array());
|
||||
return rel;
|
||||
}
|
||||
|
||||
void mxs_json_add_relation(json_t* rel, const char* id, const char* type)
|
||||
{
|
||||
json_t* data = json_object_get(rel, CN_DATA);
|
||||
ss_dassert(data && json_is_array(data));
|
||||
|
||||
json_t* obj = json_object();
|
||||
json_object_set_new(obj, CN_ID, json_string(id));
|
||||
json_object_set_new(obj, CN_TYPE, json_string(type));
|
||||
json_array_append(data, obj);
|
||||
}
|
||||
|
||||
static string grab_next_component(string* s)
|
||||
{
|
||||
std::string& str = *s;
|
||||
|
||||
while (str.length() > 0 && str[0] == '/')
|
||||
{
|
||||
str.erase(str.begin());
|
||||
}
|
||||
|
||||
size_t pos = str.find("/");
|
||||
string rval;
|
||||
|
||||
if (pos != string::npos)
|
||||
{
|
||||
rval = str.substr(0, pos);
|
||||
str.erase(0, pos);
|
||||
return rval;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = str;
|
||||
str.erase(0);
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static bool is_integer(const string& str)
|
||||
{
|
||||
char* end;
|
||||
return strtol(str.c_str(), &end, 10) >= 0 && *end == '\0';
|
||||
}
|
||||
|
||||
static json_t* mxs_json_pointer_internal(json_t* json, string str)
|
||||
{
|
||||
json_t* rval = NULL;
|
||||
string comp = grab_next_component(&str);
|
||||
|
||||
if (comp.length() == 0)
|
||||
{
|
||||
return json;
|
||||
}
|
||||
|
||||
if (json_is_array(json) && is_integer(comp))
|
||||
{
|
||||
size_t idx = strtol(comp.c_str(), NULL, 10);
|
||||
|
||||
if (idx < json_array_size(json))
|
||||
{
|
||||
rval = mxs_json_pointer_internal(json_array_get(json, idx), str);
|
||||
}
|
||||
}
|
||||
else if (json_is_object(json))
|
||||
{
|
||||
json_t* obj = json_object_get(json, comp.c_str());
|
||||
|
||||
if (obj)
|
||||
{
|
||||
rval = mxs_json_pointer_internal(obj, str);
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* mxs_json_pointer(json_t* json, const char* json_ptr)
|
||||
{
|
||||
return mxs_json_pointer_internal(json, json_ptr);
|
||||
}
|
||||
|
||||
json_t* mxs_json_self_link(const char* host, const char* path, const char* id)
|
||||
{
|
||||
json_t* links = json_object();
|
||||
|
||||
string self = host;
|
||||
self += "/";
|
||||
self += path;
|
||||
self += "/";
|
||||
self += id;
|
||||
json_object_set_new(links, CN_SELF, json_string(self.c_str()));
|
||||
|
||||
return links;
|
||||
}
|
@ -119,6 +119,7 @@ listener_alloc(struct service* service, const char* name, const char *protocol,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
proto->active = 1;
|
||||
proto->name = my_name;
|
||||
proto->listener = NULL;
|
||||
proto->service = service;
|
||||
@ -514,13 +515,12 @@ bool listener_serialize(const SERV_LISTENER *listener)
|
||||
|
||||
json_t* listener_to_json(const SERV_LISTENER* listener)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
json_object_set_new(rval, "name", json_string(listener->name));
|
||||
json_object_set_new(rval, "address", json_string(listener->address));
|
||||
json_object_set_new(rval, "port", json_integer(listener->port));
|
||||
json_object_set_new(rval, "protocol", json_string(listener->protocol));
|
||||
json_object_set_new(rval, "authenticator", json_string(listener->authenticator));
|
||||
json_object_set_new(rval, "auth_options", json_string(listener->auth_options));
|
||||
json_t* param = json_object();
|
||||
json_object_set_new(param, "address", json_string(listener->address));
|
||||
json_object_set_new(param, "port", json_integer(listener->port));
|
||||
json_object_set_new(param, "protocol", json_string(listener->protocol));
|
||||
json_object_set_new(param, "authenticator", json_string(listener->authenticator));
|
||||
json_object_set_new(param, "auth_options", json_string(listener->auth_options));
|
||||
|
||||
if (listener->ssl)
|
||||
{
|
||||
@ -532,8 +532,50 @@ json_t* listener_to_json(const SERV_LISTENER* listener)
|
||||
json_object_set_new(ssl, "ssl_ca_cert", json_string(listener->ssl->ssl_ca_cert));
|
||||
json_object_set_new(ssl, "ssl_key", json_string(listener->ssl->ssl_key));
|
||||
|
||||
json_object_set_new(rval, "ssl", ssl);
|
||||
json_object_set_new(param, "ssl", ssl);
|
||||
}
|
||||
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, CN_PARAMETERS, param);
|
||||
|
||||
json_t* rval = json_object();
|
||||
json_object_set_new(rval, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(rval, CN_ID, json_string(listener->name));
|
||||
json_object_set_new(rval, CN_TYPE, json_string(CN_LISTENERS));
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
void listener_set_active(SERV_LISTENER* listener, bool active)
|
||||
{
|
||||
atomic_store_int32(&listener->active, active ? 1 : 0);
|
||||
}
|
||||
|
||||
bool listener_is_active(SERV_LISTENER* listener)
|
||||
{
|
||||
return atomic_load_int32(&listener->active);
|
||||
}
|
||||
|
||||
static inline SERV_LISTENER* load_port(SERV_LISTENER const *const *const port)
|
||||
{
|
||||
return (SERV_LISTENER*)atomic_load_ptr((void**)port);
|
||||
}
|
||||
|
||||
SERV_LISTENER* listener_iterator_init(const SERVICE* service, LISTENER_ITERATOR* iter)
|
||||
{
|
||||
ss_dassert(iter);
|
||||
iter->current = load_port(&service->ports);
|
||||
return iter->current;
|
||||
}
|
||||
|
||||
SERV_LISTENER* listener_iterator_next(LISTENER_ITERATOR* iter)
|
||||
{
|
||||
ss_dassert(iter);
|
||||
|
||||
if (iter->current)
|
||||
{
|
||||
iter->current = load_port(&iter->current->next);
|
||||
}
|
||||
|
||||
return iter->current;
|
||||
}
|
||||
|
@ -37,16 +37,17 @@
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include <curl/curl.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <maxscale/modinfo.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/version.h>
|
||||
#include <maxscale/notification.h>
|
||||
#include <curl/curl.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <maxscale/paths.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/modules.h"
|
||||
#include "maxscale/config.h"
|
||||
@ -406,16 +407,19 @@ void dprintAllModules(DCB *dcb)
|
||||
dcb_printf(dcb, "----------------+-----------------+---------+-------+-------------------------\n\n");
|
||||
}
|
||||
|
||||
static json_t* module_to_json(const LOADED_MODULE *mod, const char* host)
|
||||
static json_t* module_json_data(const LOADED_MODULE *mod, const char* host)
|
||||
{
|
||||
json_t* obj = json_object();
|
||||
|
||||
json_object_set_new(obj, "name", json_string(mod->module));
|
||||
json_object_set_new(obj, "type", json_string(mod->type));
|
||||
json_object_set_new(obj, "version", json_string(mod->info->version));
|
||||
json_object_set_new(obj, "description", json_string(mod->info->description));
|
||||
json_object_set_new(obj, "api", json_string(mxs_module_api_to_string(mod->info->modapi)));
|
||||
json_object_set_new(obj, "status", json_string(mxs_module_status_to_string(mod->info->status)));
|
||||
json_object_set_new(obj, CN_ID, json_string(mod->module));
|
||||
json_object_set_new(obj, CN_TYPE, json_string(CN_MODULE));
|
||||
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, "module_type", json_string(mod->type));
|
||||
json_object_set_new(attr, "version", json_string(mod->info->version));
|
||||
json_object_set_new(attr, "description", json_string(mod->info->description));
|
||||
json_object_set_new(attr, "api", json_string(mxs_module_api_to_string(mod->info->modapi)));
|
||||
json_object_set_new(attr, "status", json_string(mxs_module_status_to_string(mod->info->status)));
|
||||
|
||||
json_t* params = json_array();
|
||||
|
||||
@ -447,21 +451,42 @@ static json_t* module_to_json(const LOADED_MODULE *mod, const char* host)
|
||||
json_array_append_new(params, p);
|
||||
}
|
||||
|
||||
json_object_set_new(obj, CN_PARAMETERS, params);
|
||||
json_object_set_new(attr, CN_PARAMETERS, params);
|
||||
json_object_set_new(obj, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(obj, CN_LINKS, mxs_json_self_link(host, CN_MODULES, mod->module));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
json_t* module_to_json(const MXS_MODULE* module, const char* host)
|
||||
{
|
||||
json_t* data = NULL;
|
||||
|
||||
for (LOADED_MODULE *ptr = registered; ptr; ptr = ptr->next)
|
||||
{
|
||||
if (ptr->info == module)
|
||||
{
|
||||
data = module_json_data(ptr, host);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This should always be non-NULL
|
||||
ss_dassert(data);
|
||||
|
||||
return mxs_json_resource(host, MXS_JSON_API_MODULES, data);
|
||||
}
|
||||
|
||||
json_t* module_list_to_json(const char* host)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
|
||||
for (LOADED_MODULE *ptr = registered; ptr; ptr = ptr->next)
|
||||
{
|
||||
json_array_append_new(arr, module_to_json(ptr, host));
|
||||
json_array_append_new(arr, module_json_data(ptr, host));
|
||||
}
|
||||
|
||||
return arr;
|
||||
return mxs_json_resource(host, MXS_JSON_API_MODULES, arr);
|
||||
}
|
||||
|
||||
void moduleShowFeedbackReport(DCB *dcb)
|
||||
|
@ -11,10 +11,10 @@
|
||||
* Public License.
|
||||
*/
|
||||
#include <maxscale/log_manager.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
@ -23,14 +23,17 @@
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <syslog.h>
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/platform.h>
|
||||
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/config.h>
|
||||
#include <maxscale/platform.h>
|
||||
#include <maxscale/hashtable.h>
|
||||
#include <maxscale/json_api.h>
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/debug.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/utils.h>
|
||||
|
||||
#include "maxscale/mlist.h"
|
||||
|
||||
#define MAX_PREFIXLEN 250
|
||||
@ -3013,3 +3016,27 @@ const char* mxs_strerror(int error)
|
||||
|
||||
return strerror_r(error, errbuf, sizeof(errbuf));
|
||||
}
|
||||
|
||||
json_t* mxs_logs_to_json(const char* host)
|
||||
{
|
||||
json_t* param = json_object();
|
||||
json_object_set_new(param, "highprecision", json_boolean(log_config.do_highprecision));
|
||||
json_object_set_new(param, "maxlog", json_boolean(log_config.do_maxlog));
|
||||
json_object_set_new(param, "syslog", json_boolean(log_config.do_syslog));
|
||||
|
||||
json_t* throttling = json_object();
|
||||
json_object_set_new(throttling, "count", json_integer(log_config.throttling.count));
|
||||
json_object_set_new(throttling, "suppress_ms", json_integer(log_config.throttling.suppress_ms));
|
||||
json_object_set_new(throttling, "window_ms", json_integer(log_config.throttling.window_ms));
|
||||
json_object_set_new(param, "throttling", throttling);
|
||||
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, CN_PARAMETERS, param);
|
||||
|
||||
json_t* data = json_object();
|
||||
json_object_set_new(data, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(data, CN_ID, json_string("logs"));
|
||||
json_object_set_new(data, CN_TYPE, json_string("logs"));
|
||||
|
||||
return mxs_json_resource(host, MXS_JSON_API_LOGS, data);
|
||||
}
|
||||
|
@ -238,4 +238,23 @@ bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json);
|
||||
*/
|
||||
bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json);
|
||||
|
||||
/**
|
||||
* @brief Create a listener from JSON
|
||||
*
|
||||
* @param service Service where the listener is created
|
||||
* @param json JSON definition of the new listener
|
||||
*
|
||||
* @return True if the listener was successfully created and started
|
||||
*/
|
||||
bool runtime_create_listener_from_json(SERVICE* service, json_t* json);
|
||||
|
||||
/**
|
||||
* @brief Alter logging options using JSON
|
||||
*
|
||||
* @param json JSON definition of the updated logging options
|
||||
*
|
||||
* @return True if the modifications were successful
|
||||
*/
|
||||
bool runtime_alter_logs_from_json(json_t* json);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <maxscale/cppdefs.hh>
|
||||
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
|
||||
#include <maxscale/debug.h>
|
||||
|
||||
@ -34,3 +35,55 @@ static inline std::string http_get_date()
|
||||
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a time_t value into a HTTP-date string
|
||||
*
|
||||
* @param t Time to convert
|
||||
*
|
||||
* @return The time converted to a HTTP-date string
|
||||
*/
|
||||
static inline std::string http_to_date(time_t t)
|
||||
{
|
||||
struct tm tm;
|
||||
char buf[200]; // Enough to store all dates
|
||||
|
||||
gmtime_r(&t, &tm);
|
||||
strftime(buf, sizeof(buf), "%a, %d %b %Y %T GMT", &tm);
|
||||
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a HTTP-date string into time_t
|
||||
*
|
||||
* @param str HTTP-date formatted string to convert
|
||||
*
|
||||
* @return The time converted to time_t
|
||||
*/
|
||||
static inline time_t http_from_date(const std::string& str)
|
||||
{
|
||||
struct tm tm = {};
|
||||
|
||||
/** First get the GMT time in time_t format */
|
||||
strptime(str.c_str(), "%a, %d %b %Y %T GMT", &tm);
|
||||
time_t t = mktime(&tm);
|
||||
|
||||
/** Then convert it to local time by calculating the difference between
|
||||
* the local time and the GMT time */
|
||||
struct tm local_tm = {};
|
||||
struct tm gmt_tm = {};
|
||||
time_t epoch = 0;
|
||||
|
||||
/** Call tzset() for the sake of portability */
|
||||
tzset();
|
||||
gmtime_r(&epoch, &gmt_tm);
|
||||
localtime_r(&epoch, &local_tm);
|
||||
|
||||
time_t gmt_t = mktime(&gmt_tm);
|
||||
time_t local_t = mktime(&local_tm);
|
||||
|
||||
/** The value of `(gmt_t - local_t)` will be the number of seconds west
|
||||
* from GMT. For timezones east of GMT, it will be negative. */
|
||||
return t - (gmt_t - local_t);
|
||||
}
|
||||
|
@ -26,6 +26,9 @@
|
||||
|
||||
#include "http.hh"
|
||||
|
||||
// The API version part of the URL
|
||||
#define MXS_REST_API_VERSION "v1"
|
||||
|
||||
static int value_iterator(void *cls,
|
||||
enum MHD_ValueKind kind,
|
||||
const char *key,
|
||||
@ -33,7 +36,7 @@ static int value_iterator(void *cls,
|
||||
{
|
||||
std::pair<std::string, std::string>* cmp = (std::pair<std::string, std::string>*)cls;
|
||||
|
||||
if (cmp->first == key)
|
||||
if (strcasecmp(cmp->first.c_str(), key) == 0)
|
||||
{
|
||||
cmp->second = value;
|
||||
return MHD_NO;
|
||||
@ -156,10 +159,28 @@ public:
|
||||
return m_resource_parts.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the last part of the URI
|
||||
*
|
||||
* @return The last URI part
|
||||
*/
|
||||
const std::string last_uri_part() const
|
||||
{
|
||||
return m_resource_parts.size() > 0 ? m_resource_parts[m_resource_parts.size() - 1] : "";
|
||||
}
|
||||
|
||||
const char* host() const
|
||||
{
|
||||
return m_hostname.c_str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drop the API version prefix
|
||||
*
|
||||
* @return True if prefix is present and was successfully removed
|
||||
*/
|
||||
bool validate_api_version();
|
||||
|
||||
private:
|
||||
|
||||
/** Constants */
|
||||
@ -173,5 +194,5 @@ private:
|
||||
std::deque<std::string> m_resource_parts; /**< @c m_resource split into parts */
|
||||
std::string m_verb; /**< Request method */
|
||||
std::string m_hostname; /**< The value of the Host header */
|
||||
struct MHD_Connection* m_connection;
|
||||
struct MHD_Connection* m_connection;
|
||||
};
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define HTTP_RESPONSE_HEADER_LAST_MODIFIED "Last-Modified"
|
||||
#define HTTP_RESPONSE_HEADER_ETAG "ETag"
|
||||
#define HTTP_RESPONSE_HEADER_ACCEPT "Accept"
|
||||
#define HTTP_RESPONSE_HEADER_CONTENT_TYPE "Content-Type"
|
||||
|
||||
typedef std::map<std::string, std::string> Headers;
|
||||
|
||||
|
@ -161,10 +161,21 @@ bool mxs_module_iterator_has_next(const MXS_MODULE_ITERATOR* iterator);
|
||||
*/
|
||||
MXS_MODULE* mxs_module_iterator_get_next(MXS_MODULE_ITERATOR* iterator);
|
||||
|
||||
/**
|
||||
* @brief Convert module to JSON
|
||||
*
|
||||
* @param module Module to convert
|
||||
* @param host Hostname of this server
|
||||
*
|
||||
* @return The module in JSON format
|
||||
*/
|
||||
json_t* module_to_json(const MXS_MODULE* module, const char* host);
|
||||
|
||||
/**
|
||||
* @brief Convert all modules to JSON
|
||||
*
|
||||
* @param host The hostname of this server
|
||||
*
|
||||
* @return Array of modules in JSON format
|
||||
*/
|
||||
json_t* module_list_to_json(const char* host);
|
||||
|
@ -62,8 +62,8 @@ private:
|
||||
|
||||
bool matching_variable_path(const std::string& path, const std::string& target) const;
|
||||
|
||||
ResourceCallback m_cb; /**< Resource handler callback */
|
||||
std::deque<std::string> m_path; /**< Path components */
|
||||
ResourceCallback m_cb; /**< Resource handler callback */
|
||||
std::deque<std::string> m_path; /**< Path components */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -159,6 +159,16 @@ public:
|
||||
*/
|
||||
static int64_t get_one_statistic(POLL_STAT what);
|
||||
|
||||
/**
|
||||
* Return this worker's statistics.
|
||||
*
|
||||
* @return Local statistics for this worker.
|
||||
*/
|
||||
const STATISTICS& get_local_statistics() const
|
||||
{
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file descriptor to the epoll instance of the worker.
|
||||
*
|
||||
|
@ -31,11 +31,13 @@
|
||||
#include <maxscale/pcre2.h>
|
||||
#include <maxscale/secrets.h>
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/config.h"
|
||||
#include "maxscale/externcmd.h"
|
||||
#include "maxscale/monitor.h"
|
||||
#include "maxscale/modules.h"
|
||||
#include "maxscale/json_api.h"
|
||||
|
||||
using std::string;
|
||||
using std::set;
|
||||
@ -1496,16 +1498,22 @@ json_t* monitor_parameters_to_json(const MXS_MONITOR* monitor)
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* monitor_to_json(const MXS_MONITOR* monitor, const char* host)
|
||||
json_t* monitor_json_data(const MXS_MONITOR* monitor, const char* host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
|
||||
json_object_set_new(rval, CN_NAME, json_string(monitor->name));
|
||||
json_object_set_new(rval, CN_MODULE, json_string(monitor->module_name));
|
||||
json_object_set_new(rval, CN_STATE, json_string(monitor_state_to_string(monitor->state)));
|
||||
spinlock_acquire(&monitor->lock);
|
||||
|
||||
json_object_set_new(rval, CN_ID, json_string(monitor->name));
|
||||
json_object_set_new(rval, CN_TYPE, json_string(CN_MONITORS));
|
||||
|
||||
json_t* attr = json_object();
|
||||
|
||||
json_object_set_new(attr, CN_MODULE, json_string(monitor->module_name));
|
||||
json_object_set_new(attr, CN_STATE, json_string(monitor_state_to_string(monitor->state)));
|
||||
|
||||
/** Monitor parameters */
|
||||
json_object_set_new(rval, CN_PARAMETERS, monitor_parameters_to_json(monitor));
|
||||
json_object_set_new(attr, CN_PARAMETERS, monitor_parameters_to_json(monitor));
|
||||
|
||||
if (monitor->handle && monitor->module->diagnostics)
|
||||
{
|
||||
@ -1513,38 +1521,41 @@ json_t* monitor_to_json(const MXS_MONITOR* monitor, const char* host)
|
||||
|
||||
if (diag)
|
||||
{
|
||||
json_object_set_new(rval, "monitor_diagnostics", diag);
|
||||
json_object_set_new(attr, "monitor_diagnostics", diag);
|
||||
}
|
||||
}
|
||||
|
||||
json_t* rel = json_object();
|
||||
|
||||
/** Store relationships to other objects */
|
||||
string self = host;
|
||||
self += "/monitors/";
|
||||
self += monitor->name;
|
||||
json_object_set_new(rel, CN_SELF, json_string(self.c_str()));
|
||||
|
||||
if (monitor->databases)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* mon_rel = mxs_json_relationship(host, MXS_JSON_API_SERVERS);
|
||||
|
||||
for (MXS_MONITOR_SERVERS *db = monitor->databases; db; db = db->next)
|
||||
{
|
||||
string s = host;
|
||||
s += "/servers/";
|
||||
s += db->server->unique_name;
|
||||
json_array_append_new(arr, json_string(s.c_str()));
|
||||
mxs_json_add_relation(mon_rel, db->server->unique_name, CN_SERVERS);
|
||||
}
|
||||
|
||||
json_object_set_new(rel, "servers", arr);
|
||||
json_object_set_new(rel, CN_SERVERS, mon_rel);
|
||||
}
|
||||
|
||||
json_object_set_new(rval, "relationships", rel);
|
||||
spinlock_release(&monitor->lock);
|
||||
|
||||
json_object_set_new(rval, CN_RELATIONSHIPS, rel);
|
||||
json_object_set_new(rval, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(rval, CN_LINKS, mxs_json_self_link(host, CN_MONITORS, monitor->name));
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* monitor_to_json(const MXS_MONITOR* monitor, const char* host)
|
||||
{
|
||||
string self = MXS_JSON_API_MONITORS;
|
||||
self += monitor->name;
|
||||
return mxs_json_resource(host, self.c_str(), monitor_json_data(monitor, host));
|
||||
}
|
||||
|
||||
json_t* monitor_list_to_json(const char* host)
|
||||
{
|
||||
json_t* rval = json_array();
|
||||
@ -1553,7 +1564,7 @@ json_t* monitor_list_to_json(const char* host)
|
||||
|
||||
for (MXS_MONITOR* mon = allMonitors; mon; mon = mon->next)
|
||||
{
|
||||
json_t *json = monitor_to_json(mon, host);
|
||||
json_t *json = monitor_json_data(mon, host);
|
||||
|
||||
if (json)
|
||||
{
|
||||
@ -1563,12 +1574,12 @@ json_t* monitor_list_to_json(const char* host)
|
||||
|
||||
spinlock_release(&monLock);
|
||||
|
||||
return rval;
|
||||
return mxs_json_resource(host, MXS_JSON_API_MONITORS, rval);
|
||||
}
|
||||
|
||||
json_t* monitor_relations_to_server(const SERVER* server, const char* host)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* rel = mxs_json_relationship(host, MXS_JSON_API_MONITORS);
|
||||
|
||||
spinlock_acquire(&monLock);
|
||||
|
||||
@ -1580,10 +1591,8 @@ json_t* monitor_relations_to_server(const SERVER* server, const char* host)
|
||||
{
|
||||
if (db->server == server)
|
||||
{
|
||||
string m = host;
|
||||
m += "/monitors/";
|
||||
m += mon->name;
|
||||
json_array_append_new(arr, json_string(m.c_str()));
|
||||
mxs_json_add_relation(rel, mon->name, CN_MONITORS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1592,5 +1601,5 @@ json_t* monitor_relations_to_server(const SERVER* server, const char* host)
|
||||
|
||||
spinlock_release(&monLock);
|
||||
|
||||
return arr;
|
||||
return rel;
|
||||
}
|
||||
|
@ -14,10 +14,13 @@
|
||||
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/jansson.hh>
|
||||
#include <maxscale/spinlock.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
#include <maxscale/housekeeper.h>
|
||||
|
||||
#include "maxscale/httprequest.hh"
|
||||
#include "maxscale/httpresponse.hh"
|
||||
@ -27,13 +30,77 @@
|
||||
#include "maxscale/service.h"
|
||||
#include "maxscale/config_runtime.h"
|
||||
#include "maxscale/modules.h"
|
||||
#include "maxscale/worker.h"
|
||||
#include "maxscale/http.hh"
|
||||
|
||||
using std::list;
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using mxs::SpinLock;
|
||||
using mxs::SpinLockGuard;
|
||||
|
||||
/**
|
||||
* Class that keeps track of resource modification times
|
||||
*/
|
||||
class ResourceWatcher
|
||||
{
|
||||
public:
|
||||
|
||||
ResourceWatcher() :
|
||||
m_init(time(NULL))
|
||||
{
|
||||
}
|
||||
|
||||
void modify(const string& path)
|
||||
{
|
||||
map<string, uint64_t>::iterator it = m_etag.find(path);
|
||||
|
||||
if (it != m_etag.end())
|
||||
{
|
||||
it->second++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First modification
|
||||
m_etag[path] = 1;
|
||||
}
|
||||
|
||||
m_last_modified[path] = time(NULL);
|
||||
}
|
||||
|
||||
time_t last_modified(const string& path) const
|
||||
{
|
||||
map<string, time_t>::const_iterator it = m_last_modified.find(path);
|
||||
|
||||
if (it != m_last_modified.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Resource has not yet been updated
|
||||
return m_init;
|
||||
}
|
||||
|
||||
time_t etag(const string& path) const
|
||||
{
|
||||
map<string, uint64_t>::const_iterator it = m_etag.find(path);
|
||||
|
||||
if (it != m_etag.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Resource has not yet been updated
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
time_t m_init;
|
||||
map<string, time_t> m_last_modified;
|
||||
map<string, uint64_t> m_etag;
|
||||
};
|
||||
|
||||
Resource::Resource(ResourceCallback cb, int components, ...) :
|
||||
m_cb(cb)
|
||||
{
|
||||
@ -88,7 +155,8 @@ bool Resource::matching_variable_path(const string& path, const string& target)
|
||||
if ((path == ":service" && service_find(target.c_str())) ||
|
||||
(path == ":server" && server_find_by_unique_name(target.c_str())) ||
|
||||
(path == ":filter" && filter_def_find(target.c_str())) ||
|
||||
(path == ":monitor" && monitor_find(target.c_str())))
|
||||
(path == ":monitor" && monitor_find(target.c_str())) ||
|
||||
(path == ":module" && get_module(target.c_str(), NULL)))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
@ -103,6 +171,16 @@ bool Resource::matching_variable_path(const string& path, const string& target)
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
else if (path == ":thread")
|
||||
{
|
||||
char* end;
|
||||
int id = strtol(target.c_str(), &end, 10);
|
||||
|
||||
if (*end == '\0' && mxs_worker_get(id))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
@ -140,17 +218,12 @@ HttpResponse cb_create_server(const HttpRequest& request)
|
||||
{
|
||||
json_t* json = request.get_json();
|
||||
|
||||
if (json)
|
||||
if (json && runtime_create_server_from_json(json))
|
||||
{
|
||||
SERVER* server = runtime_create_server_from_json(json);
|
||||
|
||||
if (server)
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host()));
|
||||
}
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_alter_server(const HttpRequest& request)
|
||||
@ -163,28 +236,36 @@ HttpResponse cb_alter_server(const HttpRequest& request)
|
||||
|
||||
if (server && runtime_alter_server_from_json(server, json))
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host()));
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_create_monitor(const HttpRequest& request)
|
||||
{
|
||||
json_t* json = request.get_json();
|
||||
|
||||
if (json)
|
||||
if (json && runtime_create_monitor_from_json(json))
|
||||
{
|
||||
MXS_MONITOR* monitor = runtime_create_monitor_from_json(json);
|
||||
|
||||
if (monitor)
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host()));
|
||||
}
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_create_service_listener(const HttpRequest& request)
|
||||
{
|
||||
json_t* json = request.get_json();
|
||||
SERVICE* service = service_find(request.uri_part(1).c_str());
|
||||
|
||||
if (service && json && runtime_create_listener_from_json(service, json))
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_alter_monitor(const HttpRequest& request)
|
||||
@ -197,11 +278,11 @@ HttpResponse cb_alter_monitor(const HttpRequest& request)
|
||||
|
||||
if (monitor && runtime_alter_monitor_from_json(monitor, json))
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host()));
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_alter_service(const HttpRequest& request)
|
||||
@ -214,11 +295,23 @@ HttpResponse cb_alter_service(const HttpRequest& request)
|
||||
|
||||
if (service && runtime_alter_service_from_json(service, json))
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK, service_to_json(service, request.host()));
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_alter_logs(const HttpRequest& request)
|
||||
{
|
||||
json_t* json = request.get_json();
|
||||
|
||||
if (json && runtime_alter_logs_from_json(json))
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_delete_server(const HttpRequest& request)
|
||||
@ -230,7 +323,7 @@ HttpResponse cb_delete_server(const HttpRequest& request)
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_delete_monitor(const HttpRequest& request)
|
||||
@ -242,7 +335,7 @@ HttpResponse cb_delete_monitor(const HttpRequest& request)
|
||||
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return HttpResponse(MHD_HTTP_BAD_REQUEST);
|
||||
return HttpResponse(MHD_HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
HttpResponse cb_all_servers(const HttpRequest& request)
|
||||
@ -282,15 +375,7 @@ HttpResponse cb_get_service(const HttpRequest& request)
|
||||
HttpResponse cb_get_service_listeners(const HttpRequest& request)
|
||||
{
|
||||
SERVICE* service = service_find(request.uri_part(1).c_str());
|
||||
json_t* json = service_to_json(service, request.host());
|
||||
|
||||
// The 'listeners' key is always defined
|
||||
json_t* listeners = json_incref(json_object_get(json, CN_LISTENERS));
|
||||
ss_dassert(listeners);
|
||||
|
||||
json_decref(json);
|
||||
|
||||
return HttpResponse(MHD_HTTP_OK, listeners);
|
||||
return HttpResponse(MHD_HTTP_OK, service_listeners_to_json(service, request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_all_filters(const HttpRequest& request)
|
||||
@ -354,33 +439,36 @@ HttpResponse cb_maxscale(const HttpRequest& request)
|
||||
|
||||
HttpResponse cb_logs(const HttpRequest& request)
|
||||
{
|
||||
// TODO: Show logs
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
return HttpResponse(MHD_HTTP_OK, mxs_logs_to_json(request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_flush(const HttpRequest& request)
|
||||
{
|
||||
int code = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||
|
||||
// Flush logs
|
||||
if (mxs_log_rotate() == 0)
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
code = MHD_HTTP_NO_CONTENT;
|
||||
}
|
||||
|
||||
return HttpResponse(code);
|
||||
}
|
||||
|
||||
HttpResponse cb_threads(const HttpRequest& request)
|
||||
HttpResponse cb_all_threads(const HttpRequest& request)
|
||||
{
|
||||
// TODO: Show thread status
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
return HttpResponse(MHD_HTTP_OK, mxs_worker_list_to_json(request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_thread(const HttpRequest& request)
|
||||
{
|
||||
int id = atoi(request.last_uri_part().c_str());
|
||||
return HttpResponse(MHD_HTTP_OK, mxs_worker_to_json(request.host(), id));
|
||||
}
|
||||
|
||||
HttpResponse cb_tasks(const HttpRequest& request)
|
||||
{
|
||||
// TODO: Show housekeeper tasks
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
return HttpResponse(MHD_HTTP_OK, hk_tasks_json(request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_all_modules(const HttpRequest& request)
|
||||
@ -388,6 +476,12 @@ HttpResponse cb_all_modules(const HttpRequest& request)
|
||||
return HttpResponse(MHD_HTTP_OK, module_list_to_json(request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_module(const HttpRequest& request)
|
||||
{
|
||||
const MXS_MODULE* module = get_module(request.last_uri_part().c_str(), NULL);
|
||||
return HttpResponse(MHD_HTTP_OK, module_to_json(module, request.host()));
|
||||
}
|
||||
|
||||
HttpResponse cb_send_ok(const HttpRequest& request)
|
||||
{
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
@ -404,7 +498,7 @@ public:
|
||||
RootResource()
|
||||
{
|
||||
// Special resources required by OPTION etc.
|
||||
m_get.push_back(SResource(new Resource(cb_send_ok, 1, "/")));
|
||||
m_get.push_back(SResource(new Resource(cb_send_ok, 0)));
|
||||
m_get.push_back(SResource(new Resource(cb_send_ok, 1, "*")));
|
||||
|
||||
m_get.push_back(SResource(new Resource(cb_all_servers, 1, "servers")));
|
||||
@ -425,20 +519,25 @@ public:
|
||||
m_get.push_back(SResource(new Resource(cb_get_session, 2, "sessions", ":session")));
|
||||
|
||||
m_get.push_back(SResource(new Resource(cb_maxscale, 1, "maxscale")));
|
||||
m_get.push_back(SResource(new Resource(cb_threads, 2, "maxscale", "threads")));
|
||||
m_get.push_back(SResource(new Resource(cb_all_threads, 2, "maxscale", "threads")));
|
||||
m_get.push_back(SResource(new Resource(cb_thread, 3, "maxscale", "threads", ":thread")));
|
||||
m_get.push_back(SResource(new Resource(cb_logs, 2, "maxscale", "logs")));
|
||||
m_get.push_back(SResource(new Resource(cb_tasks, 2, "maxscale", "tasks")));
|
||||
m_get.push_back(SResource(new Resource(cb_all_modules, 2, "maxscale", "modules")));
|
||||
m_get.push_back(SResource(new Resource(cb_module, 3, "maxscale", "modules", ":module")));
|
||||
|
||||
/** Create new resources */
|
||||
m_post.push_back(SResource(new Resource(cb_flush, 3, "maxscale", "logs", "flush")));
|
||||
m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers")));
|
||||
m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors")));
|
||||
m_post.push_back(SResource(new Resource(cb_create_service_listener, 3,
|
||||
"services", ":service", "listeners")));
|
||||
|
||||
/** Update resources */
|
||||
m_put.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server")));
|
||||
m_put.push_back(SResource(new Resource(cb_alter_monitor, 2, "monitors", ":monitor")));
|
||||
m_put.push_back(SResource(new Resource(cb_alter_service, 2, "services", ":service")));
|
||||
m_put.push_back(SResource(new Resource(cb_alter_logs, 2, "maxscale", "logs")));
|
||||
|
||||
/** Change resource states */
|
||||
m_put.push_back(SResource(new Resource(cb_stop_monitor, 3, "monitors", ":monitor", "stop")));
|
||||
@ -568,10 +667,106 @@ private:
|
||||
};
|
||||
|
||||
static RootResource resources; /**< Core resource set */
|
||||
static ResourceWatcher watcher; /**< Modification watcher */
|
||||
static SpinLock resource_lock;
|
||||
|
||||
static bool request_modifies_data(const string& verb)
|
||||
{
|
||||
return verb == MHD_HTTP_METHOD_POST ||
|
||||
verb == MHD_HTTP_METHOD_PUT ||
|
||||
verb == MHD_HTTP_METHOD_DELETE;
|
||||
}
|
||||
|
||||
static bool request_reads_data(const string& verb)
|
||||
{
|
||||
return verb == MHD_HTTP_METHOD_GET ||
|
||||
verb == MHD_HTTP_METHOD_HEAD;
|
||||
}
|
||||
|
||||
bool request_precondition_met(const HttpRequest& request, HttpResponse& response)
|
||||
{
|
||||
bool rval = true;
|
||||
string str;
|
||||
const string& uri = request.get_uri();
|
||||
|
||||
if ((str = request.get_header(MHD_HTTP_HEADER_IF_MODIFIED_SINCE)).length())
|
||||
{
|
||||
if (watcher.last_modified(uri) <= http_from_date(str))
|
||||
{
|
||||
rval = false;
|
||||
response = HttpResponse(MHD_HTTP_NOT_MODIFIED);
|
||||
}
|
||||
}
|
||||
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE)).length())
|
||||
{
|
||||
if (watcher.last_modified(uri) > http_from_date(str))
|
||||
{
|
||||
rval = false;
|
||||
response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED);
|
||||
}
|
||||
}
|
||||
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_MATCH)).length())
|
||||
{
|
||||
str = str.substr(1, str.length() - 2);
|
||||
|
||||
if (watcher.etag(uri) != strtol(str.c_str(), NULL, 10))
|
||||
{
|
||||
rval = false;
|
||||
response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED);
|
||||
}
|
||||
}
|
||||
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_NONE_MATCH)).length())
|
||||
{
|
||||
str = str.substr(1, str.length() - 2);
|
||||
|
||||
if (watcher.etag(uri) == strtol(str.c_str(), NULL, 10))
|
||||
{
|
||||
rval = false;
|
||||
response = HttpResponse(MHD_HTTP_NOT_MODIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
HttpResponse resource_handle_request(const HttpRequest& request)
|
||||
{
|
||||
MXS_DEBUG("%s %s %s", request.get_verb().c_str(), request.get_uri().c_str(),
|
||||
request.get_json_str().c_str());
|
||||
|
||||
SpinLockGuard guard(resource_lock);
|
||||
return resources.process_request(request);
|
||||
HttpResponse rval;
|
||||
|
||||
if (request_precondition_met(request, rval))
|
||||
{
|
||||
rval = resources.process_request(request);
|
||||
|
||||
if (request_modifies_data(request.get_verb()))
|
||||
{
|
||||
switch (rval.get_code())
|
||||
{
|
||||
case MHD_HTTP_OK:
|
||||
case MHD_HTTP_NO_CONTENT:
|
||||
case MHD_HTTP_CREATED:
|
||||
watcher.modify(request.get_uri());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (request_reads_data(request.get_verb()))
|
||||
{
|
||||
const string& uri = request.get_uri();
|
||||
|
||||
rval.add_header(HTTP_RESPONSE_HEADER_LAST_MODIFIED,
|
||||
http_to_date(watcher.last_modified(uri)));
|
||||
|
||||
stringstream ss;
|
||||
ss << "\"" << watcher.etag(uri) << "\"";
|
||||
rval.add_header(HTTP_RESPONSE_HEADER_ETAG, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <maxscale/paths.h>
|
||||
#include <maxscale/utils.h>
|
||||
#include <maxscale/semaphore.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/monitor.h"
|
||||
#include "maxscale/poll.h"
|
||||
@ -1360,44 +1361,12 @@ bool server_is_mxs_service(const SERVER *server)
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* server_list_to_json(const char* host)
|
||||
static json_t* server_json_attributes(const SERVER* server)
|
||||
{
|
||||
json_t* rval = json_array();
|
||||
/** Resource attributes */
|
||||
json_t* attr = json_object();
|
||||
|
||||
if (rval)
|
||||
{
|
||||
spinlock_acquire(&server_spin);
|
||||
|
||||
for (SERVER* server = allServers; server; server = server->next)
|
||||
{
|
||||
if (SERVER_IS_ACTIVE(server))
|
||||
{
|
||||
json_t* srv_json = server_to_json(server, host);
|
||||
|
||||
if (srv_json == NULL)
|
||||
{
|
||||
json_decref(rval);
|
||||
rval = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
json_array_append_new(rval, srv_json);
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&server_spin);
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* server_to_json(const SERVER* server, const char* host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
|
||||
json_object_set_new(rval, CN_NAME, json_string(server->unique_name));
|
||||
|
||||
/** Store server parameters */
|
||||
/** Store server parameters in attributes */
|
||||
json_t* params = json_object();
|
||||
|
||||
json_object_set_new(params, CN_ADDRESS, json_string(server->name));
|
||||
@ -1419,21 +1388,22 @@ json_t* server_to_json(const SERVER* server, const char* host)
|
||||
json_object_set_new(params, p->name, json_string(p->value));
|
||||
}
|
||||
|
||||
json_object_set_new(rval, CN_PARAMETERS, params);
|
||||
json_object_set_new(attr, CN_PARAMETERS, params);
|
||||
|
||||
|
||||
/** Store general information about the server state */
|
||||
char* stat = server_status(server);
|
||||
json_object_set_new(rval, "status", json_string(stat));
|
||||
json_object_set_new(attr, CN_STATUS, json_string(stat));
|
||||
MXS_FREE(stat);
|
||||
|
||||
if (server->server_string)
|
||||
{
|
||||
json_object_set_new(rval, "version", json_string(server->server_string));
|
||||
json_object_set_new(attr, CN_VERSION_STRING, json_string(server->server_string));
|
||||
}
|
||||
|
||||
json_object_set_new(rval, "node_id", json_integer(server->node_id));
|
||||
json_object_set_new(rval, "master_id", json_integer(server->master_id));
|
||||
json_object_set_new(rval, "replication_depth", json_integer(server->depth));
|
||||
json_object_set_new(attr, "node_id", json_integer(server->node_id));
|
||||
json_object_set_new(attr, "master_id", json_integer(server->master_id));
|
||||
json_object_set_new(attr, "replication_depth", json_integer(server->depth));
|
||||
|
||||
if (server->slaves)
|
||||
{
|
||||
@ -1444,12 +1414,12 @@ json_t* server_to_json(const SERVER* server, const char* host)
|
||||
json_array_append_new(slaves, json_integer(server->slaves[i]));
|
||||
}
|
||||
|
||||
json_object_set_new(rval, "slaves", slaves);
|
||||
json_object_set_new(attr, "slaves", slaves);
|
||||
}
|
||||
|
||||
if (server->rlag >= 0)
|
||||
{
|
||||
json_object_set_new(rval, "replication_lag", json_integer(server->rlag));
|
||||
json_object_set_new(attr, "replication_lag", json_integer(server->rlag));
|
||||
}
|
||||
|
||||
if (server->node_ts > 0)
|
||||
@ -1460,7 +1430,7 @@ json_t* server_to_json(const SERVER* server, const char* host)
|
||||
asctime_r(localtime_r(&tim, &result), timebuf);
|
||||
trim(timebuf);
|
||||
|
||||
json_object_set_new(rval, "last_heartbeat", json_string(timebuf));
|
||||
json_object_set_new(attr, "last_heartbeat", json_string(timebuf));
|
||||
}
|
||||
|
||||
/** Store statistics */
|
||||
@ -1470,39 +1440,53 @@ json_t* server_to_json(const SERVER* server, const char* host)
|
||||
json_object_set_new(stats, "total_connections", json_integer(server->stats.n_connections));
|
||||
json_object_set_new(stats, "active_operations", json_integer(server->stats.n_current_ops));
|
||||
|
||||
json_object_set_new(rval, "statictics", stats);
|
||||
json_object_set_new(attr, "statictics", stats);
|
||||
|
||||
/** Store relationships to other objects */
|
||||
return attr;
|
||||
}
|
||||
|
||||
static json_t* server_to_json_data(const SERVER* server, const char* host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
|
||||
/** Add resource identifiers */
|
||||
json_object_set_new(rval, CN_ID, json_string(server->unique_name));
|
||||
json_object_set_new(rval, CN_TYPE, json_string(CN_SERVERS));
|
||||
|
||||
/** Relationships */
|
||||
json_t* rel = json_object();
|
||||
|
||||
string self = host;
|
||||
self += "/servers/";
|
||||
self += server->unique_name;
|
||||
json_object_set_new(rel, CN_SELF, json_string(self.c_str()));
|
||||
|
||||
json_t* arr = service_relations_to_server(server, host);
|
||||
|
||||
if (json_array_size(arr) > 0)
|
||||
{
|
||||
json_object_set_new(rel, CN_SERVICES, arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_decref(arr);
|
||||
}
|
||||
|
||||
arr = monitor_relations_to_server(server, host);
|
||||
|
||||
if (json_array_size(arr) > 0)
|
||||
{
|
||||
json_object_set_new(rel, CN_MONITORS, arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_decref(arr);
|
||||
}
|
||||
|
||||
json_object_set_new(rel, CN_SERVICES, service_relations_to_server(server, host));
|
||||
json_object_set_new(rel, CN_MONITORS, monitor_relations_to_server(server, host));
|
||||
json_object_set_new(rval, CN_RELATIONSHIPS, rel);
|
||||
/** Attributes */
|
||||
json_object_set_new(rval, CN_ATTRIBUTES, server_json_attributes(server));
|
||||
json_object_set_new(rval, CN_LINKS, mxs_json_self_link(host, CN_SERVERS, server->unique_name));
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* server_to_json(const SERVER* server, const char* host)
|
||||
{
|
||||
string self = MXS_JSON_API_SERVERS;
|
||||
self += server->unique_name;
|
||||
return mxs_json_resource(host, self.c_str(), server_to_json_data(server, host));
|
||||
}
|
||||
|
||||
json_t* server_list_to_json(const char* host)
|
||||
{
|
||||
json_t* data = json_array();
|
||||
|
||||
spinlock_acquire(&server_spin);
|
||||
|
||||
for (SERVER* server = allServers; server; server = server->next)
|
||||
{
|
||||
if (SERVER_IS_ACTIVE(server))
|
||||
{
|
||||
json_array_append_new(data, server_to_json_data(server, host));
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&server_spin);
|
||||
|
||||
return mxs_json_resource(host, MXS_JSON_API_SERVERS, data);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include <maxscale/utils.h>
|
||||
#include <maxscale/version.h>
|
||||
#include <maxscale/jansson.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/config.h"
|
||||
#include "maxscale/filter.h"
|
||||
@ -498,36 +499,24 @@ int serviceInitialize(SERVICE *service)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a failed listener
|
||||
* @brief Remove a listener from use
|
||||
*
|
||||
* This should only be called when a newly created listener fails to start.
|
||||
*
|
||||
* @note The service spinlock must be held when this function is called.
|
||||
* @note This does not free the memory
|
||||
*
|
||||
* @param service Service where @c port points to
|
||||
* @param port Port to remove
|
||||
*/
|
||||
void serviceRemoveListener(SERVICE *service, SERV_LISTENER *port)
|
||||
void serviceRemoveListener(SERVICE *service, SERV_LISTENER *target)
|
||||
{
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
if (service->ports == port)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
service->ports = service->ports->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
SERV_LISTENER *prev = service->ports;
|
||||
SERV_LISTENER *current = service->ports->next;
|
||||
|
||||
while (current)
|
||||
if (listener == target)
|
||||
{
|
||||
if (current == port)
|
||||
{
|
||||
prev->next = current->next;
|
||||
break;
|
||||
}
|
||||
prev = current;
|
||||
current = current->next;
|
||||
listener_set_active(listener, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -543,7 +532,6 @@ bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port)
|
||||
{
|
||||
/** Failed to start the listener */
|
||||
serviceRemoveListener(service, port);
|
||||
listener_free(port);
|
||||
rval = false;
|
||||
}
|
||||
|
||||
@ -555,49 +543,45 @@ bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port)
|
||||
bool serviceStopListener(SERVICE *service, const char *name)
|
||||
{
|
||||
bool rval = false;
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
spinlock_acquire(&service->spin);
|
||||
|
||||
for (SERV_LISTENER *port = service->ports; port; port = port->next)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (strcmp(port->name, name) == 0)
|
||||
if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
|
||||
{
|
||||
if (poll_remove_dcb(port->listener) == 0)
|
||||
if (poll_remove_dcb(listener->listener) == 0)
|
||||
{
|
||||
port->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
|
||||
listener->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
|
||||
rval = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service->spin);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool serviceStartListener(SERVICE *service, const char *name)
|
||||
{
|
||||
bool rval = false;
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
spinlock_acquire(&service->spin);
|
||||
|
||||
for (SERV_LISTENER *port = service->ports; port; port = port->next)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (strcmp(port->name, name) == 0)
|
||||
if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
|
||||
{
|
||||
if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER_STOPPED &&
|
||||
poll_add_dcb(port->listener) == 0)
|
||||
if (listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED &&
|
||||
poll_add_dcb(listener->listener) == 0)
|
||||
{
|
||||
port->listener->session->state = SESSION_STATE_LISTENER;
|
||||
listener->listener->session->state = SESSION_STATE_LISTENER;
|
||||
rval = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service->spin);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -631,13 +615,17 @@ bool serviceStop(SERVICE *service)
|
||||
|
||||
if (service)
|
||||
{
|
||||
for (SERV_LISTENER * port = service->ports; port; port = port->next)
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER)
|
||||
if (listener_is_active(listener) &&
|
||||
listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER)
|
||||
{
|
||||
if (poll_remove_dcb(port->listener) == 0)
|
||||
if (poll_remove_dcb(listener->listener) == 0)
|
||||
{
|
||||
port->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
|
||||
listener->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
|
||||
listeners++;
|
||||
}
|
||||
}
|
||||
@ -663,13 +651,17 @@ bool serviceStart(SERVICE *service)
|
||||
|
||||
if (service)
|
||||
{
|
||||
for (SERV_LISTENER* port = service->ports; port; port = port->next)
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (port->listener && port->listener->session->state == SESSION_STATE_LISTENER_STOPPED)
|
||||
if (listener_is_active(listener) &&
|
||||
listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED)
|
||||
{
|
||||
if (poll_add_dcb(port->listener) == 0)
|
||||
if (poll_add_dcb(listener->listener) == 0)
|
||||
{
|
||||
port->listener->session->state = SESSION_STATE_LISTENER;
|
||||
listener->listener->session->state = SESSION_STATE_LISTENER;
|
||||
listeners++;
|
||||
}
|
||||
}
|
||||
@ -726,6 +718,25 @@ void service_free(SERVICE *service)
|
||||
MXS_FREE(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to a service
|
||||
*
|
||||
* @param service Service where listener is added
|
||||
* @param proto Listener to add
|
||||
*/
|
||||
static void service_add_listener(SERVICE* service, SERV_LISTENER* proto)
|
||||
{
|
||||
do
|
||||
{
|
||||
/** Read the current value of the list's head. This will be our expected
|
||||
* value for the following compare-and-swap operation. */
|
||||
proto->next = (SERV_LISTENER*)atomic_load_ptr((void**)&service->ports);
|
||||
}
|
||||
/** Compare the current value to our expected value and if they match, replace
|
||||
* the current value with our new value. */
|
||||
while (!atomic_cas_ptr((void**)&service->ports, (void**)&proto->next, proto));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for the service
|
||||
*
|
||||
@ -747,10 +758,7 @@ SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const c
|
||||
|
||||
if (proto)
|
||||
{
|
||||
spinlock_acquire(&service->spin);
|
||||
proto->next = service->ports;
|
||||
service->ports = proto;
|
||||
spinlock_release(&service->spin);
|
||||
service_add_listener(service, proto);
|
||||
}
|
||||
|
||||
return proto;
|
||||
@ -768,23 +776,21 @@ SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const c
|
||||
bool serviceHasListener(SERVICE *service, const char *protocol,
|
||||
const char* address, unsigned short port)
|
||||
{
|
||||
SERV_LISTENER *proto;
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
spinlock_acquire(&service->spin);
|
||||
proto = service->ports;
|
||||
while (proto)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (strcmp(proto->protocol, protocol) == 0 && proto->port == port &&
|
||||
((address && proto->address && strcmp(proto->address, address) == 0) ||
|
||||
(address == NULL && proto->address == NULL)))
|
||||
if (listener_is_active(listener) &&
|
||||
strcmp(listener->protocol, protocol) == 0 && listener->port == port &&
|
||||
((address && listener->address && strcmp(listener->address, address) == 0) ||
|
||||
(address == NULL && listener->address == NULL)))
|
||||
{
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
proto = proto->next;
|
||||
}
|
||||
spinlock_release(&service->spin);
|
||||
|
||||
return proto != NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1522,7 +1528,7 @@ void
|
||||
dListListeners(DCB *dcb)
|
||||
{
|
||||
SERVICE *service;
|
||||
SERV_LISTENER *lptr;
|
||||
SERV_LISTENER *port;
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
service = allServices;
|
||||
@ -1538,19 +1544,22 @@ dListListeners(DCB *dcb)
|
||||
}
|
||||
while (service)
|
||||
{
|
||||
lptr = service->ports;
|
||||
while (lptr)
|
||||
{
|
||||
dcb_printf(dcb, "%-20s | %-19s | %-18s | %-15s | %5d | %s\n",
|
||||
lptr->name, service->name, lptr->protocol,
|
||||
(lptr && lptr->address) ? lptr->address : "*",
|
||||
lptr->port,
|
||||
(!lptr->listener ||
|
||||
!lptr->listener->session ||
|
||||
lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ?
|
||||
"Stopped" : "Running");
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
lptr = lptr->next;
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (listener_is_active(listener))
|
||||
{
|
||||
dcb_printf(dcb, "%-20s | %-19s | %-18s | %-15s | %5d | %s\n",
|
||||
listener->name, service->name, listener->protocol,
|
||||
(listener && listener->address) ? listener->address : "*",
|
||||
listener->port,
|
||||
(!listener->listener ||
|
||||
!listener->listener->session ||
|
||||
listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ?
|
||||
"Stopped" : "Running");
|
||||
}
|
||||
}
|
||||
service = service->next;
|
||||
}
|
||||
@ -1637,23 +1646,26 @@ int service_refresh_users(SERVICE *service)
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *port = service->ports; port; port = port->next)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
/** Load the authentication users before before starting the listener */
|
||||
if (port->listener && port->listener->authfunc.loadusers)
|
||||
if (listener_is_active(listener) && listener->listener &&
|
||||
listener->listener->authfunc.loadusers)
|
||||
{
|
||||
switch (port->listener->authfunc.loadusers(port))
|
||||
switch (listener->listener->authfunc.loadusers(listener))
|
||||
{
|
||||
case MXS_AUTH_LOADUSERS_FATAL:
|
||||
MXS_ERROR("[%s] Fatal error when loading users for listener '%s',"
|
||||
" authentication will not work.", service->name, port->name);
|
||||
" authentication will not work.", service->name, listener->name);
|
||||
ret = 1;
|
||||
break;
|
||||
|
||||
case MXS_AUTH_LOADUSERS_ERROR:
|
||||
MXS_WARNING("[%s] Failed to load users for listener '%s', authentication"
|
||||
" might not work.", service->name, port->name);
|
||||
" might not work.", service->name, listener->name);
|
||||
ret = 1;
|
||||
break;
|
||||
|
||||
@ -1902,6 +1914,8 @@ serviceSessionCountAll()
|
||||
* Provide a row to the result set that defines the set of service
|
||||
* listeners
|
||||
*
|
||||
* TODO: Replace these
|
||||
*
|
||||
* @param set The result set
|
||||
* @param data The index of the row to send
|
||||
* @return The next row or NULL
|
||||
@ -2085,11 +2099,15 @@ bool service_all_services_have_listeners()
|
||||
|
||||
while (service)
|
||||
{
|
||||
if (service->ports == NULL)
|
||||
LISTENER_ITERATOR iter;
|
||||
SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
|
||||
if (listener == NULL)
|
||||
{
|
||||
MXS_ERROR("Service '%s' has no listeners.", service->name);
|
||||
rval = false;
|
||||
}
|
||||
|
||||
service = service->next;
|
||||
}
|
||||
|
||||
@ -2240,7 +2258,8 @@ static bool create_service_config(const SERVICE *service, const char *filename)
|
||||
dprintf(file, "%s=%ld\n", CN_CONNECTION_TIMEOUT, service->conn_idle_timeout);
|
||||
dprintf(file, "%s=%s\n", CN_AUTH_ALL_SERVERS, service->users_from_all ? "true" : "false");
|
||||
dprintf(file, "%s=%s\n", CN_STRIP_DB_ESC, service->strip_db_esc ? "true" : "false");
|
||||
dprintf(file, "%s=%s\n", CN_LOCALHOST_MATCH_WILDCARD_HOST, service->localhost_match_wildcard_host ? "true" : "false");
|
||||
dprintf(file, "%s=%s\n", CN_LOCALHOST_MATCH_WILDCARD_HOST,
|
||||
service->localhost_match_wildcard_host ? "true" : "false");
|
||||
dprintf(file, "%s=%s\n", CN_VERSION_STRING, service->version_string);
|
||||
dprintf(file, "%s=%s\n", CN_WEIGHTBY, service->weightby);
|
||||
dprintf(file, "%s=%s\n", CN_LOG_AUTH_WARNINGS, service->log_auth_warnings ? "true" : "false");
|
||||
@ -2339,11 +2358,15 @@ bool service_serialize_servers(const SERVICE *service)
|
||||
|
||||
void service_print_users(DCB *dcb, const SERVICE *service)
|
||||
{
|
||||
for (SERV_LISTENER *port = service->ports; port; port = port->next)
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (port->listener && port->listener->authfunc.diagnostic)
|
||||
if (listener_is_active(listener) && listener->listener &&
|
||||
listener->listener->authfunc.diagnostic)
|
||||
{
|
||||
port->listener->authfunc.diagnostic(dcb, port);
|
||||
listener->listener->authfunc.diagnostic(dcb, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2355,18 +2378,17 @@ bool service_port_is_used(unsigned short port)
|
||||
|
||||
for (SERVICE *service = allServices; service && !rval; service = service->next)
|
||||
{
|
||||
spinlock_acquire(&service->spin);
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *proto = service->ports; proto; proto = proto->next)
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (proto->port == port)
|
||||
if (listener_is_active(listener) && listener->port == port)
|
||||
{
|
||||
rval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service->spin);
|
||||
}
|
||||
|
||||
spinlock_release(&service_spin);
|
||||
@ -2444,16 +2466,42 @@ json_t* service_parameters_to_json(const SERVICE* service)
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* service_to_json(const SERVICE* service, const char* host)
|
||||
static inline bool have_active_servers(const SERVICE* service)
|
||||
{
|
||||
spinlock_acquire(&service->spin);
|
||||
for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
|
||||
{
|
||||
if (SERVER_REF_IS_ACTIVE(ref))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
json_t* rval = json_object();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** General service information */
|
||||
json_object_set_new(rval, CN_NAME, json_string(service->name));
|
||||
json_object_set_new(rval, CN_ROUTER, json_string(service->routerModule));
|
||||
json_object_set_new(rval, CN_STATE, json_string(service_state_to_string(service->state)));
|
||||
json_t* service_listeners_json_data(const SERVICE* service)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
LISTENER_ITERATOR iter;
|
||||
|
||||
for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
|
||||
listener; listener = listener_iterator_next(&iter))
|
||||
{
|
||||
if (listener_is_active(listener))
|
||||
{
|
||||
json_array_append_new(arr, listener_to_json(listener));
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
json_t* service_attributes(const SERVICE* service)
|
||||
{
|
||||
json_t* attr = json_object();
|
||||
|
||||
json_object_set_new(attr, CN_ROUTER, json_string(service->routerModule));
|
||||
json_object_set_new(attr, CN_STATE, json_string(service_state_to_string(service->state)));
|
||||
|
||||
if (service->router && service->router_instance)
|
||||
{
|
||||
@ -2461,7 +2509,7 @@ json_t* service_to_json(const SERVICE* service, const char* host)
|
||||
|
||||
if (diag)
|
||||
{
|
||||
json_object_set_new(rval, "router_diagnostics", diag);
|
||||
json_object_set_new(attr, "router_diagnostics", diag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2471,117 +2519,112 @@ json_t* service_to_json(const SERVICE* service, const char* host)
|
||||
asctime_r(localtime_r(&service->stats.started, &result), timebuf);
|
||||
trim(timebuf);
|
||||
|
||||
json_object_set_new(rval, "started", json_string(timebuf));
|
||||
json_object_set_new(rval, "total_connections", json_integer(service->stats.n_sessions));
|
||||
json_object_set_new(rval, "connections", json_integer(service->stats.n_current));
|
||||
json_object_set_new(attr, "started", json_string(timebuf));
|
||||
json_object_set_new(attr, "total_connections", json_integer(service->stats.n_sessions));
|
||||
json_object_set_new(attr, "connections", json_integer(service->stats.n_current));
|
||||
|
||||
/** Add service parameters */
|
||||
json_object_set_new(rval, CN_PARAMETERS, service_parameters_to_json(service));
|
||||
/** Add service parameters and listeners */
|
||||
json_object_set_new(attr, CN_PARAMETERS, service_parameters_to_json(service));
|
||||
json_object_set_new(attr, CN_LISTENERS, service_listeners_json_data(service));
|
||||
|
||||
/** Add listeners */
|
||||
json_t* arr = json_array();
|
||||
|
||||
if (service->ports)
|
||||
{
|
||||
for (SERV_LISTENER* p = service->ports; p; p = p->next)
|
||||
{
|
||||
json_array_append_new(arr, listener_to_json(p));
|
||||
}
|
||||
}
|
||||
|
||||
json_object_set_new(rval, CN_LISTENERS, arr);
|
||||
return attr;
|
||||
}
|
||||
|
||||
json_t* service_relationships(const SERVICE* service, const char* host)
|
||||
{
|
||||
/** Store relationships to other objects */
|
||||
json_t* rel = json_object();
|
||||
|
||||
string self = host;
|
||||
self += "/services/";
|
||||
self += service->name;
|
||||
json_object_set_new(rel, CN_SELF, json_string(self.c_str()));
|
||||
|
||||
if (service->n_filters)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* filters = mxs_json_relationship(host, MXS_JSON_API_FILTERS);
|
||||
|
||||
for (int i = 0; i < service->n_filters; i++)
|
||||
{
|
||||
string filter = host;
|
||||
filter += "/filters/";
|
||||
filter += service->filters[i]->name;
|
||||
json_array_append_new(arr, json_string(filter.c_str()));
|
||||
mxs_json_add_relation(filters, service->filters[i]->name, CN_FILTERS);
|
||||
}
|
||||
|
||||
json_object_set_new(rel, "filters", arr);
|
||||
json_object_set_new(rel, CN_FILTERS, filters);
|
||||
}
|
||||
|
||||
bool active_servers = false;
|
||||
|
||||
for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
|
||||
if (have_active_servers(service))
|
||||
{
|
||||
if (SERVER_REF_IS_ACTIVE(ref))
|
||||
{
|
||||
active_servers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (active_servers)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* servers = mxs_json_relationship(host, MXS_JSON_API_SERVERS);
|
||||
|
||||
for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
|
||||
{
|
||||
if (SERVER_REF_IS_ACTIVE(ref))
|
||||
{
|
||||
string s = host;
|
||||
s += "/servers/";
|
||||
s += ref->server->unique_name;
|
||||
json_array_append_new(arr, json_string(s.c_str()));
|
||||
mxs_json_add_relation(servers, ref->server->unique_name, CN_SERVERS);
|
||||
}
|
||||
}
|
||||
|
||||
json_object_set_new(rel, CN_SERVERS, arr);
|
||||
json_object_set_new(rel, CN_SERVERS, servers);
|
||||
}
|
||||
|
||||
json_object_set_new(rval, CN_RELATIONSHIPS, rel);
|
||||
return rel;
|
||||
}
|
||||
|
||||
json_t* service_json_data(const SERVICE* service, const char* host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
|
||||
spinlock_acquire(&service->spin);
|
||||
|
||||
json_object_set_new(rval, CN_ID, json_string(service->name));
|
||||
json_object_set_new(rval, CN_TYPE, json_string(CN_SERVICES));
|
||||
json_object_set_new(rval, CN_ATTRIBUTES, service_attributes(service));
|
||||
json_object_set_new(rval, CN_RELATIONSHIPS, service_relationships(service, host));
|
||||
json_object_set_new(rval, CN_LINKS, mxs_json_self_link(host, CN_SERVICES, service->name));
|
||||
|
||||
spinlock_release(&service->spin);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
json_t* service_to_json(const SERVICE* service, const char* host)
|
||||
{
|
||||
string self = MXS_JSON_API_SERVICES;
|
||||
self += service->name;
|
||||
return mxs_json_resource(host, self.c_str(), service_json_data(service, host));
|
||||
}
|
||||
|
||||
json_t* service_listeners_to_json(const SERVICE* service, const char* host)
|
||||
{
|
||||
/** This needs to be done here as the listeners are sort of sub-resources
|
||||
* of the service. */
|
||||
string self = MXS_JSON_API_SERVICES;
|
||||
self += service->name;
|
||||
self += "/listeners";
|
||||
|
||||
return mxs_json_resource(host, self.c_str(), service_listeners_json_data(service));
|
||||
}
|
||||
|
||||
json_t* service_list_to_json(const char* host)
|
||||
{
|
||||
json_t* rval = json_array();
|
||||
json_t* arr = json_array();
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
|
||||
for (SERVICE *service = allServices; service; service = service->next)
|
||||
{
|
||||
json_t* svc = service_to_json(service, host);
|
||||
json_t* svc = service_json_data(service, host);
|
||||
|
||||
if (svc)
|
||||
{
|
||||
json_array_append_new(rval, svc);
|
||||
json_array_append_new(arr, svc);
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service_spin);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static void add_service_relation(json_t* arr, const char* host, const SERVICE* service)
|
||||
{
|
||||
string svc = host;
|
||||
svc += "/services/";
|
||||
svc += service->name;
|
||||
json_array_append_new(arr, json_string(svc.c_str()));
|
||||
return mxs_json_resource(host, MXS_JSON_API_SERVICES, arr);
|
||||
}
|
||||
|
||||
json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* host)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* rel = mxs_json_relationship(host, MXS_JSON_API_SERVICES);
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
|
||||
for (SERVICE *service = allServices; service; service = service->next)
|
||||
@ -2592,7 +2635,7 @@ json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* ho
|
||||
{
|
||||
if (service->filters[i] == filter)
|
||||
{
|
||||
add_service_relation(arr, host, service);
|
||||
mxs_json_add_relation(rel, service->name, CN_SERVICES);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2601,12 +2644,14 @@ json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* ho
|
||||
|
||||
spinlock_release(&service_spin);
|
||||
|
||||
return arr;
|
||||
return rel;
|
||||
}
|
||||
|
||||
|
||||
json_t* service_relations_to_server(const SERVER* server, const char* host)
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
json_t* rel = mxs_json_relationship(host, MXS_JSON_API_SERVICES);
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
|
||||
for (SERVICE *service = allServices; service; service = service->next)
|
||||
@ -2617,7 +2662,7 @@ json_t* service_relations_to_server(const SERVER* server, const char* host)
|
||||
{
|
||||
if (ref->server == server && SERVER_REF_IS_ACTIVE(ref))
|
||||
{
|
||||
add_service_relation(arr, host, service);
|
||||
mxs_json_add_relation(rel, service->name, CN_SERVICES);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2626,5 +2671,5 @@ json_t* service_relations_to_server(const SERVER* server, const char* host)
|
||||
|
||||
spinlock_release(&service_spin);
|
||||
|
||||
return arr;
|
||||
return rel;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/atomic.h>
|
||||
@ -47,6 +48,7 @@
|
||||
#include <maxscale/service.h>
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/utils.h>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
#include "maxscale/session.h"
|
||||
#include "maxscale/filter.h"
|
||||
@ -54,11 +56,12 @@
|
||||
#include "maxscale/workertask.hh"
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
|
||||
/** Global session id counter. Must be updated atomically. Value 0 is reserved for
|
||||
* dummy/unused sessions.
|
||||
*/
|
||||
static uint32_t next_session_id = 1;
|
||||
static uint64_t next_session_id = 1;
|
||||
|
||||
static struct session session_dummy_struct;
|
||||
|
||||
@ -159,7 +162,7 @@ MXS_SESSION* session_alloc(SERVICE *service, DCB *client_dcb)
|
||||
return session_alloc_body(service, client_dcb, session);
|
||||
}
|
||||
|
||||
MXS_SESSION* session_alloc_with_id(SERVICE *service, DCB *client_dcb, uint32_t id)
|
||||
MXS_SESSION* session_alloc_with_id(SERVICE *service, DCB *client_dcb, uint64_t id)
|
||||
{
|
||||
MXS_SESSION *session = (MXS_SESSION *)(MXS_MALLOC(sizeof(*session)));
|
||||
if (session == NULL)
|
||||
@ -260,13 +263,13 @@ static MXS_SESSION* session_alloc_body(SERVICE* service, DCB* client_dcb,
|
||||
|
||||
if (session->client_dcb->user == NULL)
|
||||
{
|
||||
MXS_INFO("Started session [%" PRIu32 "] for %s service ",
|
||||
MXS_INFO("Started session [%" PRIu64 "] for %s service ",
|
||||
session->ses_id,
|
||||
service->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_INFO("Started %s client session [%" PRIu32 "] for '%s' from %s",
|
||||
MXS_INFO("Started %s client session [%" PRIu64 "] for '%s' from %s",
|
||||
service->name,
|
||||
session->ses_id,
|
||||
session->client_dcb->user,
|
||||
@ -275,7 +278,7 @@ static MXS_SESSION* session_alloc_body(SERVICE* service, DCB* client_dcb,
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_INFO("Start %s client session [%" PRIu32 "] for '%s' from %s failed, will be "
|
||||
MXS_INFO("Start %s client session [%" PRIu64 "] for '%s' from %s failed, will be "
|
||||
"closed as soon as all related DCBs have been closed.",
|
||||
service->name,
|
||||
session->ses_id,
|
||||
@ -445,7 +448,7 @@ static void session_free(MXS_SESSION *session)
|
||||
MXS_FREE(session->filters);
|
||||
}
|
||||
|
||||
MXS_INFO("Stopped %s client session [%" PRIu32 "]", session->service->name, session->ses_id);
|
||||
MXS_INFO("Stopped %s client session [%" PRIu64 "]", session->service->name, session->ses_id);
|
||||
|
||||
/** If session doesn't have parent referencing to it, it can be freed */
|
||||
if (!session->ses_is_child)
|
||||
@ -557,7 +560,7 @@ dprintSession(DCB *dcb, MXS_SESSION *print_session)
|
||||
char buf[30];
|
||||
int i;
|
||||
|
||||
dcb_printf(dcb, "Session %" PRIu32 "\n", print_session->ses_id);
|
||||
dcb_printf(dcb, "Session %" PRIu64 "\n", print_session->ses_id);
|
||||
dcb_printf(dcb, "\tState: %s\n", session_state(print_session->state));
|
||||
dcb_printf(dcb, "\tService: %s\n", print_session->service->name);
|
||||
|
||||
@ -597,7 +600,7 @@ bool dListSessions_cb(DCB *dcb, void *data)
|
||||
{
|
||||
DCB *out_dcb = (DCB*)data;
|
||||
MXS_SESSION *session = dcb->session;
|
||||
dcb_printf(out_dcb, "%-16" PRIu32 " | %-15s | %-14s | %s\n", session->ses_id,
|
||||
dcb_printf(out_dcb, "%-16" PRIu64 " | %-15s | %-14s | %s\n", session->ses_id,
|
||||
session->client_dcb && session->client_dcb->remote ?
|
||||
session->client_dcb->remote : "",
|
||||
session->service && session->service->name ?
|
||||
@ -961,7 +964,7 @@ static bool ses_find_id(DCB *dcb, void *data)
|
||||
{
|
||||
void **params = (void**)data;
|
||||
MXS_SESSION **ses = (MXS_SESSION**)params[0];
|
||||
uint32_t *id = (uint32_t*)params[1];
|
||||
uint64_t *id = (uint64_t*)params[1];
|
||||
bool rval = true;
|
||||
|
||||
if (dcb->session->ses_id == *id)
|
||||
@ -973,7 +976,7 @@ static bool ses_find_id(DCB *dcb, void *data)
|
||||
return rval;
|
||||
}
|
||||
|
||||
MXS_SESSION* session_get_by_id(uint32_t id)
|
||||
MXS_SESSION* session_get_by_id(uint64_t id)
|
||||
{
|
||||
MXS_SESSION *session = NULL;
|
||||
void *params[] = {&session, &id};
|
||||
@ -1045,9 +1048,9 @@ void session_clear_stmt(MXS_SESSION *session)
|
||||
session->stmt.target = NULL;
|
||||
}
|
||||
|
||||
uint32_t session_get_next_id()
|
||||
uint64_t session_get_next_id()
|
||||
{
|
||||
return atomic_add_uint32(&next_session_id, 1);
|
||||
return atomic_add_uint64(&next_session_id, 1);
|
||||
}
|
||||
|
||||
void session_broadcast_kill_command(MXS_SESSION* issuer, uint64_t target_id)
|
||||
@ -1075,32 +1078,52 @@ void session_broadcast_kill_command(MXS_SESSION* issuer, uint64_t target_id)
|
||||
}
|
||||
}
|
||||
|
||||
json_t* session_to_json(const MXS_SESSION *session, const char *host)
|
||||
json_t* session_json_data(const MXS_SESSION *session, const char *host)
|
||||
{
|
||||
json_t* rval = json_object();
|
||||
json_t* data = json_object();
|
||||
|
||||
json_object_set_new(rval, "id", json_integer(session->ses_id));
|
||||
json_object_set_new(rval, "state", json_string(session_state(session->state)));
|
||||
/** ID must be a string */
|
||||
stringstream ss;
|
||||
ss << session->ses_id;
|
||||
|
||||
/** ID and type */
|
||||
json_object_set_new(data, CN_ID, json_string(ss.str().c_str()));
|
||||
json_object_set_new(data, CN_TYPE, json_string(CN_SESSIONS));
|
||||
|
||||
/** Relationships */
|
||||
json_t* rel = json_object();
|
||||
json_t* arr = json_array();
|
||||
|
||||
string svc = host;
|
||||
svc += "/services/";
|
||||
svc += session->service->name;
|
||||
/** Service relationship (one-to-one) */
|
||||
json_t* services = mxs_json_relationship(host, MXS_JSON_API_SERVICES);
|
||||
mxs_json_add_relation(services, session->service->name, CN_SERVICES);
|
||||
json_object_set_new(rel, CN_SERVICES, services);
|
||||
|
||||
json_array_append_new(arr, json_string(svc.c_str()));
|
||||
json_object_set_new(rel, "services", arr);
|
||||
json_object_set_new(rval, "relationships", rel);
|
||||
/** Filter relationships (one-to-many) */
|
||||
if (session->n_filters)
|
||||
{
|
||||
json_t* filters = mxs_json_relationship(host, MXS_JSON_API_FILTERS);
|
||||
|
||||
for (int i = 0; i < session->n_filters; i++)
|
||||
{
|
||||
mxs_json_add_relation(filters, session->filters[i].filter->name, CN_FILTERS);
|
||||
}
|
||||
json_object_set_new(rel, CN_FILTERS, filters);
|
||||
}
|
||||
|
||||
json_object_set_new(data, CN_RELATIONSHIPS, rel);
|
||||
|
||||
/** Session attributes */
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, "state", json_string(session_state(session->state)));
|
||||
|
||||
if (session->client_dcb->user)
|
||||
{
|
||||
json_object_set_new(rval, "user", json_string(session->client_dcb->user));
|
||||
json_object_set_new(attr, CN_USER, json_string(session->client_dcb->user));
|
||||
}
|
||||
|
||||
if (session->client_dcb->remote)
|
||||
{
|
||||
json_object_set_new(rval, "remote", json_string(session->client_dcb->remote));
|
||||
json_object_set_new(attr, "remote", json_string(session->client_dcb->remote));
|
||||
}
|
||||
|
||||
struct tm result;
|
||||
@ -1109,31 +1132,26 @@ json_t* session_to_json(const MXS_SESSION *session, const char *host)
|
||||
asctime_r(localtime_r(&session->stats.connect, &result), buf);
|
||||
trim(buf);
|
||||
|
||||
json_object_set_new(rval, "connected", json_string(buf));
|
||||
json_object_set_new(attr, "connected", json_string(buf));
|
||||
|
||||
if (session->client_dcb->state == DCB_STATE_POLLING)
|
||||
{
|
||||
double idle = (hkheartbeat - session->client_dcb->last_read);
|
||||
idle = idle > 0 ? idle / 10.f : 0;
|
||||
json_object_set_new(rval, "idle", json_real(idle));
|
||||
json_object_set_new(attr, "idle", json_real(idle));
|
||||
}
|
||||
|
||||
if (session->n_filters)
|
||||
{
|
||||
json_t* filters = json_array();
|
||||
json_object_set_new(data, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(data, CN_LINKS, mxs_json_self_link(host, CN_SESSIONS, ss.str().c_str()));
|
||||
|
||||
for (int i = 0; i < session->n_filters; i++)
|
||||
{
|
||||
string fil = host;
|
||||
fil += "/filters/";
|
||||
fil += session->filters[i].filter->name;
|
||||
json_array_append_new(filters, json_string(fil.c_str()));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
json_object_set_new(rval, "filters", filters);
|
||||
}
|
||||
|
||||
return rval;
|
||||
json_t* session_to_json(const MXS_SESSION *session, const char *host)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << MXS_JSON_API_SESSIONS << session->ses_id;
|
||||
return mxs_json_resource(host, ss.str().c_str(), session_json_data(session, host));
|
||||
}
|
||||
|
||||
struct SessionListData
|
||||
@ -1145,7 +1163,7 @@ struct SessionListData
|
||||
bool seslist_cb(DCB* dcb, void* data)
|
||||
{
|
||||
SessionListData* d = (SessionListData*)data;
|
||||
json_array_append_new(d->json, session_to_json(dcb->session, d->host));
|
||||
json_array_append_new(d->json, session_json_data(dcb->session, d->host));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1153,5 +1171,5 @@ json_t* session_list_to_json(const char* host)
|
||||
{
|
||||
SessionListData data = {json_array(), host};
|
||||
dcb_foreach(seslist_cb, &data);
|
||||
return data.json;
|
||||
return mxs_json_resource(host, MXS_JSON_API_SESSIONS, data.json);
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ add_executable(testmaxscalepcre2 testmaxscalepcre2.cc)
|
||||
add_executable(testmodulecmd testmodulecmd.cc)
|
||||
add_executable(testconfig testconfig.cc)
|
||||
add_executable(trxboundaryparser_profile trxboundaryparser_profile.cc)
|
||||
add_executable(testjson testjson.cc)
|
||||
add_executable(testhttp testhttp.cc)
|
||||
target_link_libraries(test_atomic maxscale-common)
|
||||
target_link_libraries(test_adminusers maxscale-common)
|
||||
target_link_libraries(test_buffer maxscale-common)
|
||||
@ -48,6 +50,8 @@ target_link_libraries(testmaxscalepcre2 maxscale-common)
|
||||
target_link_libraries(testmodulecmd maxscale-common)
|
||||
target_link_libraries(testconfig maxscale-common)
|
||||
target_link_libraries(trxboundaryparser_profile maxscale-common)
|
||||
target_link_libraries(testjson maxscale-common)
|
||||
target_link_libraries(testhttp maxscale-common)
|
||||
add_test(TestAtomic test_atomic)
|
||||
add_test(TestAdminUsers test_adminusers)
|
||||
add_test(TestBuffer test_buffer)
|
||||
@ -79,6 +83,8 @@ add_test(TestTrxCompare_Select test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../..
|
||||
add_test(TestTrxCompare_Set test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/set.test)
|
||||
add_test(TestTrxCompare_Update test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/update.test)
|
||||
add_test(TestTrxCompare_MaxScale test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/maxscale.test)
|
||||
add_test(TestJson testjson)
|
||||
add_test(TestHttp testhttp)
|
||||
|
||||
# This test requires external dependencies and thus cannot be run
|
||||
# as a part of the core test set
|
||||
@ -86,3 +92,5 @@ if(TEST_FEEDBACK)
|
||||
add_test(TestFeedback testfeedback)
|
||||
set_tests_properties(TestFeedback PROPERTIES TIMEOUT 30)
|
||||
endif()
|
||||
|
||||
add_subdirectory(rest-api)
|
||||
|
3
server/core/test/rest-api/CMakeLists.txt
Normal file
3
server/core/test/rest-api/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_custom_target(test_rest_api
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_rest_api.sh ${CMAKE_SOURCE_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
26
server/core/test/rest-api/after.sh
Executable file
26
server/core/test/rest-api/after.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# This script is run after each test block. It kills the MaxScale process
|
||||
# and cleans up the directories that contain generated files.
|
||||
#
|
||||
|
||||
test -z "$MAXSCALE_DIR" && exit 1
|
||||
|
||||
maxscaledir=$MAXSCALE_DIR
|
||||
|
||||
for ((i=0;i<10;i++))
|
||||
do
|
||||
pkill maxscale || break
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# If it wasn't dead before, now it is
|
||||
pgrep maxscale && pkill -9 maxscale
|
||||
|
||||
rm -r $maxscaledir/lib/maxscale
|
||||
rm -r $maxscaledir/cache/maxscale
|
||||
rm -r $maxscaledir/run/maxscale
|
||||
mkdir -m 0755 -p $maxscaledir/lib/maxscale
|
||||
mkdir -m 0755 -p $maxscaledir/cache/maxscale
|
||||
mkdir -m 0755 -p $maxscaledir/run/maxscale
|
21
server/core/test/rest-api/before.sh
Executable file
21
server/core/test/rest-api/before.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# This script is run before each test block. It starts MaxScale and waits for it
|
||||
# to become responsive.
|
||||
#
|
||||
|
||||
maxscaledir=$MAXSCALE_DIR
|
||||
|
||||
test -z "$MAXSCALE_DIR" && exit 1
|
||||
|
||||
# Start MaxScale
|
||||
$maxscaledir/bin/maxscale -df $maxscaledir/maxscale.cnf >& $maxscaledir/maxscale.output &
|
||||
pid=$!
|
||||
|
||||
# Wait for MaxScale to start
|
||||
for ((i=0;i<60;i++))
|
||||
do
|
||||
$maxscaledir/bin/maxadmin help >& /dev/null && break
|
||||
sleep 0.1
|
||||
done
|
19
server/core/test/rest-api/package.json
Normal file
19
server/core/test/rest-api/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "rest-api-tests",
|
||||
"version": "1.0.0",
|
||||
"repository": "https://github.com/mariadb-corporation/MaxScale",
|
||||
"description": "MaxScale REST API tests",
|
||||
"scripts": {
|
||||
"test": "mocha --timeout 30000 --slow 10000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "SEE LICENSE IN ../../../../LICENSE.txt",
|
||||
"dependencies": {
|
||||
"ajv": "^5.0.1",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"mocha": "^3.3.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.3"
|
||||
}
|
||||
}
|
126
server/core/test/rest-api/test/http.js
Normal file
126
server/core/test/rest-api/test/http.js
Normal file
@ -0,0 +1,126 @@
|
||||
require("../utils.js")()
|
||||
|
||||
|
||||
describe("HTTP Headers", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("ETag changes after modification", function() {
|
||||
return request.get(base_url + "/servers/server1", {resolveWithFullResponse: true})
|
||||
.then(function(resp) {
|
||||
resp.headers.etag.should.be.equal("\"0\"")
|
||||
var srv = JSON.parse(resp.body)
|
||||
delete srv.data.relationships
|
||||
return request.put(base_url + "/servers/server1", {json: srv})
|
||||
})
|
||||
.then(function() {
|
||||
return request.get(base_url + "/servers/server1", {resolveWithFullResponse: true})
|
||||
})
|
||||
.then(function(resp) {
|
||||
resp.headers.etag.should.be.equal("\"1\"")
|
||||
})
|
||||
});
|
||||
|
||||
it("Last-Modified changes after modification", function(done) {
|
||||
var date;
|
||||
|
||||
request.get(base_url + "/servers/server1", {resolveWithFullResponse: true})
|
||||
.then(function(resp) {
|
||||
|
||||
// Store the current modification time
|
||||
resp.headers["last-modified"].should.not.be.null
|
||||
date = resp.headers["last-modified"]
|
||||
|
||||
// Modify resource after three seconds
|
||||
setTimeout(function() {
|
||||
var srv = JSON.parse(resp.body)
|
||||
|
||||
srv.data.relationships = {
|
||||
services: {
|
||||
data: [
|
||||
{id: "RW-Split-Router", type: "services"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
request.put(base_url + "/servers/server1", {json: srv})
|
||||
.then(function() {
|
||||
return request.get(base_url + "/servers/server1", {resolveWithFullResponse: true})
|
||||
})
|
||||
.then(function(resp) {
|
||||
resp.headers["last-modified"].should.not.be.null
|
||||
resp.headers["last-modified"].should.not.be.equal(date)
|
||||
done()
|
||||
})
|
||||
.catch(function(e) {
|
||||
done(e)
|
||||
})
|
||||
|
||||
}, 2000)
|
||||
})
|
||||
.catch(function(e) {
|
||||
done(e)
|
||||
})
|
||||
});
|
||||
|
||||
var oldtime = new Date(new Date().getTime() - 1000000).toUTCString();
|
||||
var newtime = new Date(new Date().getTime() + 1000000).toUTCString();
|
||||
|
||||
it("request with older If-Modified-Since value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-Modified-Since": oldtime}
|
||||
}).should.be.fulfilled
|
||||
})
|
||||
|
||||
it("request with newer If-Modified-Since value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
resolveWithFullResponse: true,
|
||||
headers: { "If-Modified-Since": newtime }
|
||||
}).should.be.rejected
|
||||
})
|
||||
|
||||
it("request with older If-Unmodified-Since value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-Unmodified-Since": oldtime}
|
||||
}).should.be.rejected
|
||||
})
|
||||
|
||||
it("request with newer If-Unmodified-Since value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-Unmodified-Since": newtime}
|
||||
}).should.be.fulfilled
|
||||
})
|
||||
|
||||
it("request with mismatching If-Match value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-Match": "\"0\""}
|
||||
}).should.be.rejected
|
||||
})
|
||||
|
||||
it("request with matching If-Match value", function() {
|
||||
return request.get(base_url + "/servers/server1", { resolveWithFullResponse: true })
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-Match": resp.headers["etag"]}
|
||||
})
|
||||
})
|
||||
.should.be.fulfilled
|
||||
})
|
||||
|
||||
it("request with mismatching If-None-Match value", function() {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-None-Match": "\"0\""}
|
||||
}).should.be.fulfilled
|
||||
})
|
||||
|
||||
it("request with matching If-None-Match value", function() {
|
||||
return request.get(base_url + "/servers/server1", { resolveWithFullResponse: true })
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/servers/server1", {
|
||||
headers: { "If-None-Match": resp.headers["etag"]}
|
||||
})
|
||||
})
|
||||
.should.be.rejected
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
42
server/core/test/rest-api/test/logs.js
Normal file
42
server/core/test/rest-api/test/logs.js
Normal file
@ -0,0 +1,42 @@
|
||||
require("../utils.js")()
|
||||
|
||||
describe("Logs", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("change logging options", function() {
|
||||
return request.get(base_url + "/maxscale/logs")
|
||||
.then(function(resp) {
|
||||
var logs = JSON.parse(resp)
|
||||
logs.data.attributes.parameters.maxlog.should.be.true
|
||||
logs.data.attributes.parameters.syslog.should.be.true
|
||||
logs.data.attributes.parameters.highprecision.should.be.false
|
||||
logs.data.attributes.parameters.maxlog = false
|
||||
logs.data.attributes.parameters.syslog = false
|
||||
logs.data.attributes.parameters.highprecision = true
|
||||
logs.data.attributes.parameters.throttling.count = 1
|
||||
logs.data.attributes.parameters.throttling.suppress_ms = 1
|
||||
logs.data.attributes.parameters.throttling.window_ms = 1
|
||||
|
||||
return request.put(base_url + "/maxscale/logs", {json: logs})
|
||||
})
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/maxscale/logs")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var logs = JSON.parse(resp)
|
||||
logs.data.attributes.parameters.maxlog.should.be.false
|
||||
logs.data.attributes.parameters.syslog.should.be.false
|
||||
logs.data.attributes.parameters.highprecision.should.be.true
|
||||
logs.data.attributes.parameters.throttling.count.should.be.equal(1)
|
||||
logs.data.attributes.parameters.throttling.suppress_ms.should.be.equal(1)
|
||||
logs.data.attributes.parameters.throttling.window_ms.should.be.equal(1)
|
||||
})
|
||||
});
|
||||
|
||||
it("flush logs", function() {
|
||||
return request.post(base_url + "/maxscale/logs/flush")
|
||||
.should.be.fulfilled
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
135
server/core/test/rest-api/test/monitor.js
Normal file
135
server/core/test/rest-api/test/monitor.js
Normal file
@ -0,0 +1,135 @@
|
||||
require("../utils.js")()
|
||||
|
||||
var monitor = {
|
||||
data: {
|
||||
id: "test-monitor",
|
||||
type: "monitors",
|
||||
attributes: {
|
||||
module: "mysqlmon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Monitor", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("create new monitor", function() {
|
||||
return request.post(base_url + "/monitors/", {json: monitor})
|
||||
.should.be.fulfilled
|
||||
})
|
||||
|
||||
it("request monitor", function() {
|
||||
return request.get(base_url + "/monitors/" + monitor.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("alter monitor", function() {
|
||||
monitor.data.attributes.parameters = {
|
||||
monitor_interval: 1000
|
||||
}
|
||||
return request.put(base_url + "/monitors/" + monitor.data.id, {json:monitor})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("destroy created monitor", function() {
|
||||
return request.delete(base_url + "/monitors/" + monitor.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
})
|
||||
|
||||
describe("Monitor Relationships", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("create new monitor", function() {
|
||||
return request.post(base_url + "/monitors/", {json: monitor})
|
||||
.should.be.fulfilled
|
||||
})
|
||||
|
||||
it("remove relationships from old monitor", function() {
|
||||
|
||||
return request.get(base_url + "/monitors/MySQL-Monitor")
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
delete mon.data.relationships.servers
|
||||
return request.put(base_url + "/monitors/MySQL-Monitor", {json: mon})
|
||||
})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("add relationships to new monitor", function() {
|
||||
|
||||
return request.get(base_url + "/monitors/" + monitor.data.id)
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
mon.data.relationships.servers = [
|
||||
{id: "server1", type: "servers"},
|
||||
{id: "server2", type: "servers"},
|
||||
{id: "server3", type: "servers"},
|
||||
{id: "server4", type: "servers"},
|
||||
]
|
||||
return request.put(base_url + "/monitors/" + monitor.data.id, {json: mon})
|
||||
})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("move relationships back to old monitor", function() {
|
||||
|
||||
return request.get(base_url + "/monitors/" + monitor.data.id)
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
delete mon.data.relationships.servers
|
||||
return request.put(base_url + "/monitors/" + monitor.data.id, {json: mon})
|
||||
})
|
||||
.then(function() {
|
||||
return request.get(base_url + "/monitors/MySQL-Monitor")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
mon.data.relationships.servers = [
|
||||
{id: "server1", type: "servers"},
|
||||
{id: "server2", type: "servers"},
|
||||
{id: "server3", type: "servers"},
|
||||
{id: "server4", type: "servers"},
|
||||
]
|
||||
return request.put(base_url + "/monitors/MySQL-Monitor", {json: mon})
|
||||
})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("destroy created monitor", function() {
|
||||
return request.delete(base_url + "/monitors/" + monitor.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
})
|
||||
|
||||
describe("Monitor Actions", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("stop monitor", function() {
|
||||
return request.put(base_url + "/monitors/MySQL-Monitor/stop")
|
||||
.then(function() {
|
||||
return request.get(base_url + "/monitors/MySQL-Monitor")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
mon.data.attributes.state.should.be.equal("Stopped")
|
||||
})
|
||||
});
|
||||
|
||||
it("start monitor", function() {
|
||||
return request.put(base_url + "/monitors/MySQL-Monitor/start")
|
||||
.then(function() {
|
||||
return request.get(base_url + "/monitors/MySQL-Monitor")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var mon = JSON.parse(resp)
|
||||
mon.data.attributes.state.should.be.equal("Running")
|
||||
})
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
})
|
12
server/core/test/rest-api/test/options.js
Normal file
12
server/core/test/rest-api/test/options.js
Normal file
@ -0,0 +1,12 @@
|
||||
require("../utils.js")()
|
||||
|
||||
describe("Request Options", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("pretty=true", function() {
|
||||
return request.get(base_url + "/services/?pretty=true")
|
||||
.should.eventually.satisfy(validate)
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
108
server/core/test/rest-api/test/schema_validation.js
Normal file
108
server/core/test/rest-api/test/schema_validation.js
Normal file
@ -0,0 +1,108 @@
|
||||
// These tests use the server/test/maxscale_test.cnf configuration
|
||||
|
||||
require("../utils.js")()
|
||||
|
||||
describe("Resource Collections", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
var tests = [
|
||||
"/servers",
|
||||
"/sessions",
|
||||
"/services",
|
||||
"/monitors",
|
||||
"/filters",
|
||||
"/maxscale/threads",
|
||||
"/maxscale/modules",
|
||||
"/maxscale/tasks",
|
||||
]
|
||||
|
||||
tests.forEach(function(endpoint) {
|
||||
it(endpoint + ': resource found', function() {
|
||||
return request(base_url + endpoint)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it(endpoint + ': resource schema is valid', function() {
|
||||
return request(base_url + endpoint)
|
||||
.should.eventually.satisfy(validate)
|
||||
});
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
||||
|
||||
describe("Individual Resources", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
var tests = [
|
||||
"/servers/server1",
|
||||
"/servers/server2",
|
||||
"/services/RW-Split-Router",
|
||||
"/services/RW-Split-Router/listeners",
|
||||
"/monitors/MySQL-Monitor",
|
||||
"/filters/Hint",
|
||||
"/sessions/1",
|
||||
"/maxscale/",
|
||||
"/maxscale/threads/0",
|
||||
"/maxscale/logs",
|
||||
"/maxscale/modules/readwritesplit",
|
||||
]
|
||||
|
||||
tests.forEach(function(endpoint) {
|
||||
it(endpoint + ': resource found', function() {
|
||||
return request(base_url + endpoint)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it(endpoint + ': resource schema is valid', function() {
|
||||
return request(base_url + endpoint)
|
||||
.should.eventually.satisfy(validate)
|
||||
});
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
||||
|
||||
describe("Resource Self Links", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
var tests = [
|
||||
"/servers",
|
||||
"/sessions",
|
||||
"/services",
|
||||
"/monitors",
|
||||
"/filters",
|
||||
"/maxscale/threads",
|
||||
"/maxscale/modules",
|
||||
"/maxscale/tasks",
|
||||
"/servers/server1",
|
||||
"/servers/server2",
|
||||
"/services/RW-Split-Router",
|
||||
"/services/RW-Split-Router/listeners",
|
||||
"/monitors/MySQL-Monitor",
|
||||
"/filters/Hint",
|
||||
"/sessions/1",
|
||||
"/maxscale/",
|
||||
"/maxscale/threads/0",
|
||||
"/maxscale/logs",
|
||||
"/maxscale/modules/readwritesplit",
|
||||
]
|
||||
|
||||
tests.forEach(function(endpoint) {
|
||||
it(endpoint + ': correct self link', function() {
|
||||
var obj = null;
|
||||
return request.get(base_url + endpoint)
|
||||
.then(function(resp) {
|
||||
obj = JSON.parse(resp)
|
||||
return request.get(obj.links.self)
|
||||
})
|
||||
.then(function(resp) {
|
||||
var obj_self = JSON.parse(resp)
|
||||
obj_self.links.self.should.be.equal(obj.links.self)
|
||||
})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
82
server/core/test/rest-api/test/server.js
Normal file
82
server/core/test/rest-api/test/server.js
Normal file
@ -0,0 +1,82 @@
|
||||
require("../utils.js")()
|
||||
|
||||
var server = {
|
||||
data: {
|
||||
id: "test-server",
|
||||
type: "servers",
|
||||
attributes: {
|
||||
parameters: {
|
||||
port: 3003,
|
||||
address: "127.0.0.1",
|
||||
protocol: "MySQLBackend"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var rel = {
|
||||
services: {
|
||||
data: [
|
||||
{ id: "RW-Split-Router", type: "services" },
|
||||
{ id: "Read-Connection-Router", type: "services" },
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe("Server", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("create new server", function() {
|
||||
return request.post(base_url + "/servers/", {json: server })
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("request server", function() {
|
||||
return request.get(base_url + "/servers/" + server.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("update server", function() {
|
||||
server.data.attributes.parameters.weight = 10
|
||||
return request.put(base_url + "/servers/" + server.data.id, { json: server})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("destroy server", function() {
|
||||
return request.delete(base_url + "/servers/" + server.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
||||
|
||||
describe("Server Relationships", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
// We need a deep copy of the original server
|
||||
var rel_server = JSON.parse(JSON.stringify(server))
|
||||
rel_server.data.relationships = rel
|
||||
|
||||
it("create new server", function() {
|
||||
return request.post(base_url + "/servers/", {json: rel_server})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("request server", function() {
|
||||
return request.get(base_url + "/servers/" + rel_server.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("remove relationships", function() {
|
||||
delete rel_server.data["relationships"]
|
||||
return request.put(base_url + "/servers/" + rel_server.data.id, {json: rel_server})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
it("destroy server", function() {
|
||||
return request.delete(base_url + "/servers/" + rel_server.data.id)
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
89
server/core/test/rest-api/test/service.js
Normal file
89
server/core/test/rest-api/test/service.js
Normal file
@ -0,0 +1,89 @@
|
||||
require("../utils.js")()
|
||||
|
||||
describe("Service", function() {
|
||||
before(startMaxScale)
|
||||
|
||||
it("change service parameter", function() {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
svc.data.attributes.parameters.enable_root_user = true
|
||||
return request.put(base_url + "/services/RW-Split-Router", {json: svc})
|
||||
})
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
svc.data.attributes.parameters.enable_root_user.should.be.true
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it("remove service relationship", function() {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
delete svc.data.relationships
|
||||
return request.put(base_url + "/services/RW-Split-Router", {json: svc})
|
||||
})
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
svc.data.relationships.should.be.empty
|
||||
})
|
||||
});
|
||||
|
||||
it("add service relationship", function() {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
svc.data.relationships = {
|
||||
servers: {
|
||||
data: [
|
||||
{id: "server1", type: "servers"},
|
||||
{id: "server2", type: "servers"},
|
||||
{id: "server3", type: "servers"},
|
||||
{id: "server4", type: "servers"},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return request.put(base_url + "/services/RW-Split-Router", {json: svc})
|
||||
})
|
||||
.then(function(resp) {
|
||||
return request.get(base_url + "/services/RW-Split-Router")
|
||||
})
|
||||
.then(function(resp) {
|
||||
var svc = JSON.parse(resp)
|
||||
svc.data.relationships.servers.data[0].id.should.be.equal("server1")
|
||||
})
|
||||
});
|
||||
|
||||
it("create a listener", function() {
|
||||
var listener = {
|
||||
"links": {
|
||||
"self": "http://localhost:8989/v1/services/RW-Split-Router/listeners"
|
||||
},
|
||||
"data": {
|
||||
"attributes": {
|
||||
"parameters": {
|
||||
"port": 4012,
|
||||
"protocol": "MySQLClient",
|
||||
"authenticator": "MySQLAuth",
|
||||
"address": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"id": "RW-Split-Listener-2",
|
||||
"type": "listeners"
|
||||
}
|
||||
}
|
||||
|
||||
return request.post(base_url + "/services/RW-Split-Router/listeners", {json: listener})
|
||||
.should.be.fulfilled
|
||||
});
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
61
server/core/test/rest-api/test_rest_api.sh
Executable file
61
server/core/test/rest-api/test_rest_api.sh
Executable file
@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script builds and installs MaxScale, starts a MaxScale instance, runs the
|
||||
# tests use npm and stops MaxScale.
|
||||
#
|
||||
# This is definitely not the most efficient way to test the binaries but it's a
|
||||
# guaranteed method of creating a consistent and "safe" testing environment.
|
||||
#
|
||||
# TODO: Install and start a local MariaDB server for testing purposes
|
||||
|
||||
|
||||
srcdir=$1
|
||||
maxscaledir=$PWD/maxscale_test/
|
||||
testdir=$PWD/local_test/
|
||||
|
||||
mkdir -p $testdir && cd $testdir
|
||||
|
||||
# Currently all tests that use npm are for the REST API
|
||||
cp -t $testdir -r $srcdir/server/core/test/rest-api/*
|
||||
npm install
|
||||
|
||||
mkdir -p $maxscaledir && cd $maxscaledir
|
||||
|
||||
# Configure and install MaxScale
|
||||
cmake $srcdir -DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_INSTALL_PREFIX=$maxscaledir \
|
||||
-DBUILD_TESTS=Y \
|
||||
-DMAXSCALE_VARDIR=$maxscaledir \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DWITH_SCRIPTS=N \
|
||||
-DWITH_MAXSCALE_CNF=N \
|
||||
-DBUILD_CDC=Y \
|
||||
-DTARGET_COMPONENT=all \
|
||||
-DDEFAULT_MODULE_CONFIGDIR=$maxscaledir \
|
||||
-DDEFAULT_ADMIN_USER=`whoami`
|
||||
|
||||
make install
|
||||
|
||||
# Create required directories (we could run the postinst script but it's a bit too invasive)
|
||||
mkdir -p $maxscaledir/lib64/maxscale
|
||||
mkdir -p $maxscaledir/bin
|
||||
mkdir -p $maxscaledir/share/maxscale
|
||||
mkdir -p $maxscaledir/share/doc/MaxScale/maxscale
|
||||
mkdir -p $maxscaledir/log/maxscale
|
||||
mkdir -p $maxscaledir/lib/maxscale
|
||||
mkdir -p $maxscaledir/cache/maxscale
|
||||
mkdir -p $maxscaledir/run/maxscale
|
||||
chmod 0755 $maxscaledir/log/maxscale
|
||||
chmod 0755 $maxscaledir/lib/maxscale
|
||||
chmod 0755 $maxscaledir/cache/maxscale
|
||||
chmod 0755 $maxscaledir/run/maxscale
|
||||
|
||||
# This variable is used to start and stop MaxScale before each test
|
||||
export MAXSCALE_DIR=$maxscaledir
|
||||
|
||||
# Run tests
|
||||
cd $testdir
|
||||
npm test
|
||||
rval=$?
|
||||
|
||||
exit $rval
|
438
server/core/test/rest-api/utils.js
Normal file
438
server/core/test/rest-api/utils.js
Normal file
@ -0,0 +1,438 @@
|
||||
var json_api_schema = {
|
||||
"title": "JSON API Schema",
|
||||
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/success"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/failure"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/info"
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"success": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/data"
|
||||
},
|
||||
"included": {
|
||||
"description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/resource"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
},
|
||||
"links": {
|
||||
"description": "Link members related to the primary data.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/links"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/pagination"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jsonapi": {
|
||||
"$ref": "#/definitions/jsonapi"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"failure": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"errors"
|
||||
],
|
||||
"properties": {
|
||||
"errors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/error"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
},
|
||||
"jsonapi": {
|
||||
"$ref": "#/definitions/jsonapi"
|
||||
},
|
||||
"links": {
|
||||
"$ref": "#/definitions/links"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"info": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
},
|
||||
"links": {
|
||||
"$ref": "#/definitions/links"
|
||||
},
|
||||
"jsonapi": {
|
||||
"$ref": "#/definitions/jsonapi"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"meta": {
|
||||
"description": "Non-standard meta-information that can not be represented as an attribute or relationship.",
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"data": {
|
||||
"description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/resource"
|
||||
},
|
||||
{
|
||||
"description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/resource"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
{
|
||||
"description": "null if the request is one that might correspond to a single resource, but doesn't currently.",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resource": {
|
||||
"description": "\"Resource objects\" appear in a JSON API document to represent resources.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"attributes": {
|
||||
"$ref": "#/definitions/attributes"
|
||||
},
|
||||
"relationships": {
|
||||
"$ref": "#/definitions/relationships"
|
||||
},
|
||||
"links": {
|
||||
"$ref": "#/definitions/links"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"links": {
|
||||
"description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"self": {
|
||||
"description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"related": {
|
||||
"$ref": "#/definitions/link"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"link": {
|
||||
"description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A string containing the link's URL.",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"href"
|
||||
],
|
||||
"properties": {
|
||||
"href": {
|
||||
"description": "A string containing the link's URL.",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^(?!relationships$|links$)\\w[-\\w_]*$": {
|
||||
"description": "Attributes may contain any valid JSON value."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"relationships": {
|
||||
"description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^\\w[-\\w_]*$": {
|
||||
"properties": {
|
||||
"links": {
|
||||
"$ref": "#/definitions/links"
|
||||
},
|
||||
"data": {
|
||||
"description": "Member, whose value represents \"resource linkage\".",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/relationshipToOne"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/relationshipToMany"
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"links"
|
||||
]
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"relationshipToOne": {
|
||||
"description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/empty"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/linkage"
|
||||
}
|
||||
]
|
||||
},
|
||||
"relationshipToMany": {
|
||||
"description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/linkage"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"empty": {
|
||||
"description": "Describes an empty to-one relationship.",
|
||||
"type": "null"
|
||||
},
|
||||
"linkage": {
|
||||
"description": "The \"type\" and \"id\" to non-empty members.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first": {
|
||||
"description": "The first page of data",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"last": {
|
||||
"description": "The last page of data",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"prev": {
|
||||
"description": "The previous page of data",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"next": {
|
||||
"description": "The next page of data",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonapi": {
|
||||
"description": "An object describing the server's implementation",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "A unique identifier for this particular occurrence of the problem.",
|
||||
"type": "string"
|
||||
},
|
||||
"links": {
|
||||
"$ref": "#/definitions/links"
|
||||
},
|
||||
"status": {
|
||||
"description": "The HTTP status code applicable to this problem, expressed as a string value.",
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"description": "An application-specific error code, expressed as a string value.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.",
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"description": "A human-readable explanation specific to this occurrence of the problem.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pointer": {
|
||||
"description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].",
|
||||
"type": "string"
|
||||
},
|
||||
"parameter": {
|
||||
"description": "A string indicating which query parameter caused the error.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validate_json(data) {
|
||||
return validate_func(JSON.parse(data))
|
||||
}
|
||||
|
||||
var child_process = require("child_process")
|
||||
|
||||
module.exports = function() {
|
||||
this.fs = require("fs")
|
||||
this.request = require("request-promise-native")
|
||||
this.chai = require("chai")
|
||||
this.assert = require("assert")
|
||||
this.chaiAsPromised = require("chai-as-promised")
|
||||
chai.use(chaiAsPromised)
|
||||
this.should = chai.should()
|
||||
this.expect = chai.expect
|
||||
this.Ajv = require("ajv")
|
||||
this.ajv = new Ajv({$data: true, allErrors: true, extendRefs: true, verbose: true})
|
||||
this.validate_func = ajv.compile(json_api_schema)
|
||||
this.validate = validate_json
|
||||
this.base_url = "http://localhost:8989/v1"
|
||||
this.startMaxScale = function(done) {
|
||||
child_process.execFile("./before.sh", function(err, stdout, stderr) {
|
||||
if (process.env.MAXSCALE_DIR == null) {
|
||||
throw new Error("MAXSCALE_DIR is not set");
|
||||
}
|
||||
|
||||
done()
|
||||
})
|
||||
};
|
||||
this.stopMaxScale = function(done) {
|
||||
child_process.execFile("./after.sh", function(err, stdout, stderr) {
|
||||
done()
|
||||
})
|
||||
};
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include "../maxscale/poll.h"
|
||||
#include "../maxscale/statistics.h"
|
||||
#include "../maxscale/worker.hh"
|
||||
|
||||
|
||||
void init_test_env(char *path)
|
||||
@ -37,6 +38,8 @@ void init_test_env(char *path)
|
||||
mxs_log_init(NULL, logdir, MXS_LOG_TARGET_DEFAULT);
|
||||
dcb_global_init();
|
||||
poll_init();
|
||||
maxscale::MessageQueue::init();
|
||||
maxscale::Worker::init();
|
||||
hkinit();
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ void test_add(void* data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_load_store(void* data)
|
||||
{
|
||||
int id = (size_t)data;
|
||||
@ -51,6 +50,29 @@ void test_load_store(void* data)
|
||||
}
|
||||
}
|
||||
|
||||
static void* cas_dest = (void*)1;
|
||||
|
||||
void test_cas(void* data)
|
||||
{
|
||||
int id = (size_t)data;
|
||||
static int loops = 0;
|
||||
|
||||
while (atomic_load_int32(&running))
|
||||
{
|
||||
intptr_t my_value = (id + 1) % NTHR;
|
||||
intptr_t my_expected = id;
|
||||
|
||||
while (!atomic_cas_ptr(&cas_dest, (void**)&my_expected, (void*)&my_value))
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
loops++;
|
||||
}
|
||||
|
||||
ss_dassert(loops > 0);
|
||||
}
|
||||
|
||||
int run_test(void(*func)(void*))
|
||||
{
|
||||
THREAD threads[NTHR];
|
||||
@ -58,9 +80,9 @@ int run_test(void(*func)(void*))
|
||||
atomic_store_int32(&expected, 0);
|
||||
atomic_store_int32(&running, 1);
|
||||
|
||||
for (int i = 0; i < NTHR; i++)
|
||||
for (size_t i = 0; i < NTHR; i++)
|
||||
{
|
||||
if (thread_start(&threads[i], func, NULL) == NULL)
|
||||
if (thread_start(&threads[i], func, (void*)(i + 1)) == NULL)
|
||||
{
|
||||
ss_dassert(false);
|
||||
}
|
||||
@ -83,6 +105,7 @@ int main(int argc, char** argv)
|
||||
|
||||
run_test(test_load_store);
|
||||
run_test(test_add);
|
||||
run_test(test_cas);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ int test_add_parameter()
|
||||
};
|
||||
|
||||
|
||||
CONFIG_CONTEXT svc1, svc2, ctx;
|
||||
CONFIG_CONTEXT svc1 = {}, svc2 = {}, ctx = {};
|
||||
svc1.object = (char*)"my-service";
|
||||
svc2.object = (char*)"some-service";
|
||||
svc2.next = &svc1;
|
||||
|
@ -45,12 +45,7 @@
|
||||
static int
|
||||
test1()
|
||||
{
|
||||
DCB *dcb, *extra, *clone;
|
||||
int size = 100;
|
||||
int bite1 = 35;
|
||||
int bite2 = 60;
|
||||
int bite3 = 10;
|
||||
int buflen;
|
||||
DCB *dcb;
|
||||
SERV_LISTENER dummy;
|
||||
/* Single buffer tests */
|
||||
ss_dfprintf(stderr,
|
||||
@ -59,8 +54,6 @@ test1()
|
||||
printDCB(dcb);
|
||||
ss_info_dassert(dcb_isvalid(dcb), "New DCB must be valid");
|
||||
ss_dfprintf(stderr, "\t..done\nAllocated dcb.");
|
||||
clone = dcb_clone(dcb);
|
||||
ss_dfprintf(stderr, "\t..done\nCloned dcb");
|
||||
printAllDCBs();
|
||||
ss_info_dassert(true, "Something is true");
|
||||
ss_dfprintf(stderr, "\t..done\n");
|
||||
@ -68,12 +61,6 @@ test1()
|
||||
dcb_close(dcb);
|
||||
ss_dfprintf(stderr, "Freed original dcb");
|
||||
ss_info_dassert(!dcb_isvalid(dcb), "Closed DCB must not be valid");
|
||||
ss_dfprintf(stderr, "\t..done\nMake clone DCB a zombie");
|
||||
clone->state = DCB_STATE_NOPOLLING;
|
||||
dcb_add_to_list(clone);
|
||||
dcb_close(clone);
|
||||
ss_dfprintf(stderr, "\t..done\nCheck clone no longer valid");
|
||||
ss_info_dassert(!dcb_isvalid(clone), "After closing, clone DCB must not be valid");
|
||||
ss_dfprintf(stderr, "\t..done\nProcess the zombies list");
|
||||
dcb_process_zombies(0);
|
||||
ss_dfprintf(stderr, "\t..done\n");
|
||||
|
39
server/core/test/testhttp.cc
Normal file
39
server/core/test/testhttp.cc
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2019-07-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <maxscale/debug.h>
|
||||
#include "../maxscale/http.hh"
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
string date = http_to_date(now);
|
||||
time_t converted_now = http_from_date(date);
|
||||
string converted_date = http_to_date(converted_now);
|
||||
|
||||
cout << "Current linux time: " << now << endl;
|
||||
cout << "HTTP-date from current time: " << date << endl;
|
||||
cout << "Converted Linux time: " << converted_now << endl;
|
||||
cout << "Converted HTTP-date: " << converted_date << endl;
|
||||
|
||||
ss_dassert(now == converted_now);
|
||||
ss_dassert(date == converted_date);
|
||||
|
||||
return 0;
|
||||
}
|
297
server/core/test/testjson.cc
Normal file
297
server/core/test/testjson.cc
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2019-07-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxscale/cppdefs.hh>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <maxscale/debug.h>
|
||||
#include <maxscale/jansson.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
const char* test1_json =
|
||||
"{"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/servers/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"server1\","
|
||||
" \"type\": \"servers\","
|
||||
" \"relationships\": {"
|
||||
" \"services\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/services/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"SchemaRouter-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Hint-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"Read-Connection-Router\","
|
||||
" \"type\": \"services\""
|
||||
" }"
|
||||
" ]"
|
||||
" },"
|
||||
" \"monitors\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/monitors/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"MySQL-Monitor\","
|
||||
" \"type\": \"monitors\""
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
" },"
|
||||
" \"attributes\": {"
|
||||
" \"parameters\": {"
|
||||
" \"address\": \"127.0.0.1\","
|
||||
" \"port\": 3000,"
|
||||
" \"protocol\": \"MySQLBackend\""
|
||||
" },"
|
||||
" \"status\": \"Master, Running\","
|
||||
" \"version_string\": \"10.1.19-MariaDB-1~jessie\","
|
||||
" \"node_id\": 3000,"
|
||||
" \"master_id\": -1,"
|
||||
" \"replication_depth\": 0,"
|
||||
" \"slaves\": ["
|
||||
" 3001,"
|
||||
" 3002,"
|
||||
" 3003"
|
||||
" ],"
|
||||
" \"statictics\": {"
|
||||
" \"connections\": 0,"
|
||||
" \"total_connections\": 0,"
|
||||
" \"active_operations\": 0"
|
||||
" }"
|
||||
" }"
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"server2\","
|
||||
" \"type\": \"servers\","
|
||||
" \"relationships\": {"
|
||||
" \"services\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/services/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"SchemaRouter-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Hint-Router\","
|
||||
" \"type\": \"services\""
|
||||
" }"
|
||||
" ]"
|
||||
" },"
|
||||
" \"monitors\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/monitors/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"MySQL-Monitor\","
|
||||
" \"type\": \"monitors\""
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
" },"
|
||||
" \"attributes\": {"
|
||||
" \"parameters\": {"
|
||||
" \"address\": \"127.0.0.1\","
|
||||
" \"port\": 3001,"
|
||||
" \"protocol\": \"MySQLBackend\""
|
||||
" },"
|
||||
" \"status\": \"Slave, Running\","
|
||||
" \"version_string\": \"10.1.19-MariaDB-1~jessie\","
|
||||
" \"node_id\": 3001,"
|
||||
" \"master_id\": 3000,"
|
||||
" \"replication_depth\": 1,"
|
||||
" \"slaves\": [],"
|
||||
" \"statictics\": {"
|
||||
" \"connections\": 0,"
|
||||
" \"total_connections\": 0,"
|
||||
" \"active_operations\": 0"
|
||||
" }"
|
||||
" }"
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"server3\","
|
||||
" \"type\": \"servers\","
|
||||
" \"relationships\": {"
|
||||
" \"services\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/services/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"SchemaRouter-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Hint-Router\","
|
||||
" \"type\": \"services\""
|
||||
" }"
|
||||
" ]"
|
||||
" },"
|
||||
" \"monitors\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/monitors/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"MySQL-Monitor\","
|
||||
" \"type\": \"monitors\""
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
" },"
|
||||
" \"attributes\": {"
|
||||
" \"parameters\": {"
|
||||
" \"address\": \"127.0.0.1\","
|
||||
" \"port\": 3002,"
|
||||
" \"protocol\": \"MySQLBackend\""
|
||||
" },"
|
||||
" \"status\": \"Slave, Running\","
|
||||
" \"version_string\": \"10.1.19-MariaDB-1~jessie\","
|
||||
" \"node_id\": 3002,"
|
||||
" \"master_id\": 3000,"
|
||||
" \"replication_depth\": 1,"
|
||||
" \"slaves\": [],"
|
||||
" \"statictics\": {"
|
||||
" \"connections\": 0,"
|
||||
" \"total_connections\": 0,"
|
||||
" \"active_operations\": 0"
|
||||
" }"
|
||||
" }"
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"server4\","
|
||||
" \"type\": \"servers\","
|
||||
" \"relationships\": {"
|
||||
" \"services\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/services/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"SchemaRouter-Router\","
|
||||
" \"type\": \"services\""
|
||||
" },"
|
||||
" {"
|
||||
" \"id\": \"RW-Split-Hint-Router\","
|
||||
" \"type\": \"services\""
|
||||
" }"
|
||||
" ]"
|
||||
" },"
|
||||
" \"monitors\": {"
|
||||
" \"links\": {"
|
||||
" \"self\": \"http://localhost:8989/v1/monitors/\""
|
||||
" },"
|
||||
" \"data\": ["
|
||||
" {"
|
||||
" \"id\": \"MySQL-Monitor\","
|
||||
" \"type\": \"monitors\""
|
||||
" }"
|
||||
" ]"
|
||||
" }"
|
||||
" },"
|
||||
" \"attributes\": {"
|
||||
" \"parameters\": {"
|
||||
" \"address\": \"127.0.0.1\","
|
||||
" \"port\": 3003,"
|
||||
" \"protocol\": \"MySQLBackend\""
|
||||
" },"
|
||||
" \"status\": \"Slave, Running\","
|
||||
" \"version_string\": \"10.1.19-MariaDB-1~jessie\","
|
||||
" \"node_id\": 3003,"
|
||||
" \"master_id\": 3000,"
|
||||
" \"replication_depth\": 1,"
|
||||
" \"slaves\": [],"
|
||||
" \"statictics\": {"
|
||||
" \"connections\": 0,"
|
||||
" \"total_connections\": 0,"
|
||||
" \"active_operations\": 0"
|
||||
" }"
|
||||
" }"
|
||||
" }"
|
||||
" ]"
|
||||
"}";
|
||||
|
||||
int test1()
|
||||
{
|
||||
json_error_t err = {};
|
||||
json_t* json = json_loads(test1_json, 0, &err);
|
||||
|
||||
ss_dassert(json);
|
||||
|
||||
ss_dassert(mxs_json_pointer(json, "") == json);
|
||||
ss_dassert(mxs_json_pointer(json, "links") == json_object_get(json, "links"));
|
||||
ss_dassert(json_is_string(mxs_json_pointer(json, "links/self")));
|
||||
|
||||
ss_dassert(mxs_json_pointer(json, "data") == json_object_get(json, "data"));
|
||||
ss_dassert(json_is_array(mxs_json_pointer(json, "data")));
|
||||
|
||||
ss_dassert(json_is_object(mxs_json_pointer(json, "data/0")));
|
||||
ss_dassert(json_is_string(mxs_json_pointer(json, "data/0/id")));
|
||||
string s = json_string_value(mxs_json_pointer(json, "data/0/id"));
|
||||
ss_dassert(s == "server1");
|
||||
|
||||
ss_dassert(json_is_object(mxs_json_pointer(json, "data/1")));
|
||||
ss_dassert(json_is_string(mxs_json_pointer(json, "data/1/id")));
|
||||
s = json_string_value(mxs_json_pointer(json, "data/1/id"));
|
||||
ss_dassert(s == "server2");
|
||||
|
||||
ss_dassert(json_is_object(mxs_json_pointer(json, "data/0/attributes")));
|
||||
ss_dassert(json_is_object(mxs_json_pointer(json, "data/0/attributes/parameters")));
|
||||
ss_dassert(json_is_integer(mxs_json_pointer(json, "data/0/attributes/parameters/port")));
|
||||
int i = json_integer_value(mxs_json_pointer(json, "data/0/attributes/parameters/port"));
|
||||
ss_dassert(i == 3000);
|
||||
|
||||
ss_dassert(json_is_array(mxs_json_pointer(json, "data/0/attributes/slaves")));
|
||||
ss_dassert(json_array_size(mxs_json_pointer(json, "data/0/attributes/slaves")) == 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
test1();
|
||||
return 0;
|
||||
}
|
@ -12,11 +12,15 @@
|
||||
*/
|
||||
|
||||
#include "maxscale/worker.hh"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/config.h>
|
||||
@ -24,13 +28,21 @@
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/platform.h>
|
||||
#include <maxscale/semaphore.hh>
|
||||
#include <maxscale/json_api.h>
|
||||
#include <maxscale/utils.hh>
|
||||
|
||||
#include "maxscale/modules.h"
|
||||
#include "maxscale/poll.h"
|
||||
#include "maxscale/statistics.h"
|
||||
#include "maxscale/workertask.hh"
|
||||
|
||||
#define WORKER_ABSENT_ID -1
|
||||
|
||||
using maxscale::Worker;
|
||||
using maxscale::Closer;
|
||||
using maxscale::Semaphore;
|
||||
using std::vector;
|
||||
using std::stringstream;
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -767,6 +779,90 @@ MXS_SESSION* Worker::find_session(uint64_t id)
|
||||
return rval;
|
||||
}
|
||||
|
||||
class WorkerInfoTask: public maxscale::WorkerTask
|
||||
{
|
||||
public:
|
||||
WorkerInfoTask(const char* host, uint32_t nthreads):
|
||||
m_host(host)
|
||||
{
|
||||
m_data.resize(nthreads);
|
||||
}
|
||||
|
||||
void execute(Worker& worker)
|
||||
{
|
||||
json_t* stats = json_object();
|
||||
const Worker::STATISTICS& s = worker.get_local_statistics();
|
||||
json_object_set_new(stats, "reads", json_integer(s.n_read));
|
||||
json_object_set_new(stats, "writes", json_integer(s.n_write));
|
||||
json_object_set_new(stats, "errors", json_integer(s.n_error));
|
||||
json_object_set_new(stats, "hangups", json_integer(s.n_hup));
|
||||
json_object_set_new(stats, "accepts", json_integer(s.n_accept));
|
||||
json_object_set_new(stats, "blocking_polls", json_integer(s.blockingpolls));
|
||||
json_object_set_new(stats, "event_queue_length", json_integer(s.evq_length));
|
||||
json_object_set_new(stats, "max_event_queue_length", json_integer(s.evq_max));
|
||||
json_object_set_new(stats, "max_exec_time", json_integer(s.maxexectime));
|
||||
json_object_set_new(stats, "max_queue_time", json_integer(s.maxqtime));
|
||||
|
||||
json_t* attr = json_object();
|
||||
json_object_set_new(attr, "stats", stats);
|
||||
|
||||
int idx = worker.get_current_id();
|
||||
stringstream ss;
|
||||
ss << idx;
|
||||
|
||||
json_t* json = json_object();
|
||||
json_object_set_new(json, CN_ID, json_string(ss.str().c_str()));
|
||||
json_object_set_new(json, CN_TYPE, json_string(CN_THREADS));
|
||||
json_object_set_new(json, CN_ATTRIBUTES, attr);
|
||||
json_object_set_new(json, CN_LINKS, mxs_json_self_link(m_host, CN_THREADS, ss.str().c_str()));
|
||||
|
||||
ss_dassert((size_t)idx < m_data.size());
|
||||
m_data[idx] = json;
|
||||
}
|
||||
|
||||
json_t* resource()
|
||||
{
|
||||
json_t* arr = json_array();
|
||||
|
||||
for (vector<json_t*>::iterator it = m_data.begin(); it != m_data.end(); it++)
|
||||
{
|
||||
json_array_append_new(arr, *it);
|
||||
}
|
||||
|
||||
return mxs_json_resource(m_host, MXS_JSON_API_THREADS, arr);
|
||||
}
|
||||
|
||||
json_t* resource(int id)
|
||||
{
|
||||
stringstream self;
|
||||
self << MXS_JSON_API_THREADS << id;
|
||||
return mxs_json_resource(m_host, self.str().c_str(), m_data[id]);
|
||||
}
|
||||
|
||||
private:
|
||||
vector<json_t*> m_data;
|
||||
const char* m_host;
|
||||
};
|
||||
|
||||
json_t* mxs_worker_to_json(const char* host, int id)
|
||||
{
|
||||
Worker* target = Worker::get(id);
|
||||
WorkerInfoTask task(host, id + 1);
|
||||
Semaphore sem;
|
||||
|
||||
target->post(&task, &sem);
|
||||
sem.wait();
|
||||
|
||||
return task.resource(id);
|
||||
}
|
||||
|
||||
json_t* mxs_worker_list_to_json(const char* host)
|
||||
{
|
||||
WorkerInfoTask task(host, config_threadcount());
|
||||
Worker::execute_concurrently(task);
|
||||
return task.resource();
|
||||
}
|
||||
|
||||
void Worker::run()
|
||||
{
|
||||
this_thread.current_worker_id = m_id;
|
||||
|
@ -66,7 +66,8 @@
|
||||
|
||||
/* The maximum size for query statements in a transaction (64MB) */
|
||||
static size_t sql_size_limit = 64 * 1024 * 1024;
|
||||
|
||||
/* The size of the buffer for recording latency of individual statements */
|
||||
static size_t latency_buf_size = 64 * 1024;
|
||||
static const int default_sql_size = 4 * 1024;
|
||||
|
||||
#define DEFAULT_QUERY_DELIMITER "@@@"
|
||||
@ -125,14 +126,17 @@ typedef struct
|
||||
char *clientHost;
|
||||
char *userName;
|
||||
char* sql;
|
||||
char* latency;
|
||||
struct timeval start;
|
||||
char *current;
|
||||
int n_statements;
|
||||
struct timeval total;
|
||||
struct timeval current_start;
|
||||
struct timeval last_statement_start;
|
||||
bool query_end;
|
||||
char *buf;
|
||||
int sql_index;
|
||||
int latency_index;
|
||||
size_t max_sql_size;
|
||||
} TPM_SESSION;
|
||||
|
||||
@ -310,10 +314,12 @@ newSession(MXS_FILTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
atomic_add(&my_instance->sessions, 1);
|
||||
|
||||
my_session->latency = (char*)MXS_CALLOC(latency_buf_size, sizeof(char));
|
||||
my_session->max_sql_size = default_sql_size; // default max query size of 4k.
|
||||
my_session->sql = (char*)MXS_CALLOC(my_session->max_sql_size, sizeof(char));
|
||||
memset(my_session->sql, 0x00, my_session->max_sql_size);
|
||||
my_session->sql_index = 0;
|
||||
my_session->latency_index = 0;
|
||||
my_session->n_statements = 0;
|
||||
my_session->total.tv_sec = 0;
|
||||
my_session->total.tv_usec = 0;
|
||||
@ -384,6 +390,7 @@ freeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session)
|
||||
MXS_FREE(my_session->clientHost);
|
||||
MXS_FREE(my_session->userName);
|
||||
MXS_FREE(my_session->sql);
|
||||
MXS_FREE(my_session->latency);
|
||||
MXS_FREE(session);
|
||||
return;
|
||||
}
|
||||
@ -508,6 +515,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||
/* set new pointer for the buffer */
|
||||
my_session->sql_index += (my_instance->query_delimiter_size + strlen(ptr));
|
||||
}
|
||||
gettimeofday(&my_session->last_statement_start, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -528,6 +536,28 @@ clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *reply)
|
||||
struct timeval tv, diff;
|
||||
int i, inserted;
|
||||
|
||||
/* records latency of the SQL statement. */
|
||||
if (my_session->sql_index > 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
timersub(&tv, &(my_session->last_statement_start), &diff);
|
||||
|
||||
/* get latency */
|
||||
double millis = (diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
|
||||
|
||||
int written = sprintf(my_session->latency + my_session->latency_index, "%.3f", millis);
|
||||
my_session->latency_index += written;
|
||||
if (!my_session->query_end)
|
||||
{
|
||||
written = sprintf(my_session->latency + my_session->latency_index, "%s", my_instance->query_delimiter);
|
||||
my_session->latency_index += written;
|
||||
}
|
||||
if (my_session->latency_index > latency_buf_size)
|
||||
{
|
||||
MXS_ERROR("Latency buffer overflow.");
|
||||
}
|
||||
}
|
||||
|
||||
/* found 'commit' and sql statements exist. */
|
||||
if (my_session->query_end && my_session->sql_index > 0)
|
||||
{
|
||||
@ -544,8 +574,8 @@ clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *reply)
|
||||
/* print to log. */
|
||||
if (my_instance->log_enabled)
|
||||
{
|
||||
/* this prints "timestamp | server_name | user_name | latency | sql_statements" */
|
||||
fprintf(my_instance->fp, "%ld%s%s%s%s%s%ld%s%s\n",
|
||||
/* this prints "timestamp | server_name | user_name | latency of entire transaction | latencies of individual statements | sql_statements" */
|
||||
fprintf(my_instance->fp, "%ld%s%s%s%s%s%ld%s%s%s%s\n",
|
||||
timestamp,
|
||||
my_instance->delimiter,
|
||||
reply->server->unique_name,
|
||||
@ -554,10 +584,13 @@ clientReply(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *reply)
|
||||
my_instance->delimiter,
|
||||
millis,
|
||||
my_instance->delimiter,
|
||||
my_session->latency,
|
||||
my_instance->delimiter,
|
||||
my_session->sql);
|
||||
}
|
||||
|
||||
my_session->sql_index = 0;
|
||||
my_session->latency_index = 0;
|
||||
}
|
||||
|
||||
/* Pass the result upstream */
|
||||
|
@ -5,3 +5,4 @@ install_module(MySQLCommon core)
|
||||
|
||||
add_subdirectory(MySQLBackend)
|
||||
add_subdirectory(MySQLClient)
|
||||
add_subdirectory(test)
|
||||
|
@ -14,6 +14,11 @@
|
||||
|
||||
#define MXS_MODULE_NAME "MySQLClient"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <maxscale/protocol.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
@ -21,14 +26,30 @@
|
||||
#include <maxscale/ssl.h>
|
||||
#include <maxscale/poll.h>
|
||||
#include <maxscale/modinfo.h>
|
||||
#include <sys/stat.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include <maxscale/authenticator.h>
|
||||
#include <maxscale/session.h>
|
||||
#include <maxscale/worker.h>
|
||||
|
||||
/** Return type of process_special_commands() */
|
||||
typedef enum spec_com_res_t
|
||||
{
|
||||
RES_CONTINUE, // No special command detected, proceed as normal.
|
||||
RES_END, // Query handling completed, do not send to filters/router.
|
||||
RES_MORE_DATA // Possible special command, but not enough data to be sure. Must
|
||||
// wait for more data.
|
||||
} spec_com_res_t;
|
||||
|
||||
/* Type of the kill-command sent by client. */
|
||||
typedef enum kill_type
|
||||
{
|
||||
KT_CONNECTION,
|
||||
KT_QUERY
|
||||
} kill_type_t;
|
||||
|
||||
const char WORD_KILL[] = "KILL";
|
||||
|
||||
static int process_init(void);
|
||||
static void process_finish(void);
|
||||
static int thread_init(void);
|
||||
@ -52,11 +73,10 @@ static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities);
|
||||
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read);
|
||||
static void gw_process_one_new_client(DCB *client_dcb);
|
||||
static bool process_special_commands(DCB* client_dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
|
||||
/*
|
||||
* The "module object" for the mysqld client protocol module.
|
||||
*/
|
||||
static spec_com_res_t process_special_commands(DCB *client_dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static spec_com_res_t handle_query_kill(DCB* dcb, GWBUF* read_buffer, spec_com_res_t current,
|
||||
bool is_complete, unsigned int packet_len);
|
||||
static bool parse_kill_query(char *query, uint64_t *thread_id_out, kill_type_t *kt_out);
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
@ -241,9 +261,10 @@ int MySQLSendHandshake(DCB* dcb)
|
||||
memcpy(mysql_filler_ten + 6, &new_flags, sizeof(new_flags));
|
||||
}
|
||||
|
||||
// Get the equivalent of the server process id.
|
||||
protocol->tid = session_get_next_id();
|
||||
gw_mysql_set_byte4(mysql_thread_id_num, protocol->tid);
|
||||
// Get the equivalent of the server thread id.
|
||||
protocol->thread_id = session_get_next_id();
|
||||
// Send only the low 32bits in the handshake.
|
||||
gw_mysql_set_byte4(mysql_thread_id_num, (uint32_t)(protocol->thread_id));
|
||||
memcpy(mysql_scramble_buf, server_scramble, 8);
|
||||
|
||||
memcpy(mysql_plugin_data, server_scramble + 8, 12);
|
||||
@ -668,7 +689,7 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
* normal data handling function instead of this one.
|
||||
*/
|
||||
MXS_SESSION *session =
|
||||
session_alloc_with_id(dcb->service, dcb, protocol->tid);
|
||||
session_alloc_with_id(dcb->service, dcb, protocol->thread_id);
|
||||
|
||||
if (session != NULL)
|
||||
{
|
||||
@ -907,12 +928,29 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
}
|
||||
}
|
||||
|
||||
if (!process_special_commands(dcb, read_buffer, nbytes_read))
|
||||
spec_com_res_t res = process_special_commands(dcb, read_buffer, nbytes_read);
|
||||
int rval = 1;
|
||||
switch (res)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
case RES_MORE_DATA:
|
||||
dcb->dcb_readqueue = read_buffer;
|
||||
rval = 0;
|
||||
break;
|
||||
|
||||
return gw_read_finish_processing(dcb, read_buffer, capabilities);
|
||||
case RES_END:
|
||||
// Do not send this packet for routing
|
||||
gwbuf_free(read_buffer);
|
||||
rval = 0;
|
||||
break;
|
||||
|
||||
case RES_CONTINUE:
|
||||
rval = gw_read_finish_processing(dcb, read_buffer, capabilities);
|
||||
break;
|
||||
|
||||
default:
|
||||
ss_dassert(!true);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1483,14 +1521,23 @@ static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_rea
|
||||
/**
|
||||
* Some SQL commands/queries need to be detected and handled by the protocol
|
||||
* and MaxScale instead of being routed forward as is.
|
||||
*
|
||||
* @param dcb Client dcb
|
||||
* @param read_buffer the current read buffer
|
||||
* @param nbytes_read How many bytes were read
|
||||
* @return true if read buffer should be sent forward to routing, false if more
|
||||
* data is required or processing is complete
|
||||
* @return see @c spec_com_res_t
|
||||
*/
|
||||
static bool process_special_commands(DCB* dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
static spec_com_res_t process_special_commands(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
{
|
||||
spec_com_res_t rval = RES_CONTINUE;
|
||||
bool is_complete = false;
|
||||
unsigned int packet_len =
|
||||
MYSQL_GET_PAYLOAD_LEN((uint8_t *)GWBUF_DATA(read_buffer)) + MYSQL_HEADER_LEN;
|
||||
if (gwbuf_length(read_buffer) == packet_len)
|
||||
{
|
||||
is_complete = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle COM_SET_OPTION. This seems to be only used by some versions of PHP.
|
||||
*
|
||||
@ -1515,32 +1562,252 @@ static bool process_special_commands(DCB* dcb, GWBUF *read_buffer, int nbytes_re
|
||||
/**
|
||||
* Handle COM_PROCESS_KILL
|
||||
*/
|
||||
else if ((proto->current_command == MYSQL_COM_PROCESS_KILL))
|
||||
else if (proto->current_command == MYSQL_COM_PROCESS_KILL)
|
||||
{
|
||||
/* Make sure we have a complete SQL packet before trying to read the
|
||||
* process id. If not, try again next time. */
|
||||
unsigned int expected_len =
|
||||
MYSQL_GET_PAYLOAD_LEN((uint8_t *)GWBUF_DATA(read_buffer)) + MYSQL_HEADER_LEN;
|
||||
if (gwbuf_length(read_buffer) < expected_len)
|
||||
if (!is_complete)
|
||||
{
|
||||
dcb->dcb_readqueue = read_buffer;
|
||||
return false;
|
||||
rval = RES_MORE_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t bytes[4];
|
||||
if (gwbuf_copy_data(read_buffer, MYSQL_HEADER_LEN + 1, sizeof(bytes), (uint8_t*)bytes)
|
||||
if (gwbuf_copy_data(read_buffer, MYSQL_HEADER_LEN + 1, sizeof(bytes), bytes)
|
||||
== sizeof(bytes))
|
||||
{
|
||||
uint64_t process_id = gw_mysql_get_byte4(bytes);
|
||||
// Do not send this packet for routing
|
||||
gwbuf_free(read_buffer);
|
||||
session_broadcast_kill_command(dcb->session, process_id);
|
||||
// Even if id not found, send ok. TODO: send a correct response to client
|
||||
mxs_mysql_send_ok(dcb, 1, 0, NULL);
|
||||
return false;
|
||||
rval = RES_END;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (proto->current_command == MYSQL_COM_QUERY)
|
||||
{
|
||||
/* Limits on the length of the queries in which "KILL" is searched for. Reducing
|
||||
* LONGEST_KILL will reduce overhead but also limit the range of accepted queries. */
|
||||
const int SHORTEST_KILL = sizeof("KILL 1") - 1;
|
||||
const int LONGEST_KILL = sizeof("KILL CONNECTION 12345678901234567890 ;");
|
||||
/* Is length within limits for a kill-type query? */
|
||||
if (packet_len >= (MYSQL_HEADER_LEN + 1 + SHORTEST_KILL) &&
|
||||
packet_len <= (MYSQL_HEADER_LEN + 1 + LONGEST_KILL))
|
||||
{
|
||||
rval = handle_query_kill(dcb, read_buffer, rval, is_complete, packet_len);
|
||||
}
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle text version of KILL [CONNECTION | QUERY] <process_id>. Only detects
|
||||
* commands in the beginning of the packet and with no comments.
|
||||
* Increased parsing would slow down the handling of every single query.
|
||||
*
|
||||
* @param dcb Client dcb
|
||||
* @param read_buffer Input buffer
|
||||
* @param current Latest value of rval in calling function
|
||||
* @param is_complete Is read_buffer a complete sql packet
|
||||
* @param packet_len Read from sql header
|
||||
* @return Updated (or old) value of rval
|
||||
*/
|
||||
spec_com_res_t handle_query_kill(DCB* dcb, GWBUF* read_buffer, spec_com_res_t current,
|
||||
bool is_complete, unsigned int packet_len)
|
||||
{
|
||||
spec_com_res_t rval = current;
|
||||
/* First, we need to detect the text "KILL" (ignorecase) in the start
|
||||
* of the packet. Copy just enough characters. */
|
||||
const size_t KILL_BEGIN_LEN = sizeof(WORD_KILL) - 1;
|
||||
char startbuf[KILL_BEGIN_LEN]; // Not 0-terminated, careful...
|
||||
size_t copied_len = gwbuf_copy_data(read_buffer, MYSQL_HEADER_LEN + 1,
|
||||
KILL_BEGIN_LEN, (uint8_t*)startbuf);
|
||||
if (is_complete)
|
||||
{
|
||||
if (strncasecmp(WORD_KILL, startbuf, KILL_BEGIN_LEN) == 0)
|
||||
{
|
||||
/* Good chance that the query is a KILL-query. Copy the entire
|
||||
* buffer and process. */
|
||||
size_t buffer_len = packet_len - (MYSQL_HEADER_LEN + 1);
|
||||
char querybuf[buffer_len + 1]; // 0-terminated
|
||||
copied_len = gwbuf_copy_data(read_buffer,
|
||||
MYSQL_HEADER_LEN + 1,
|
||||
buffer_len,
|
||||
(uint8_t*)querybuf);
|
||||
querybuf[copied_len] = '\0';
|
||||
kill_type_t kt = KT_CONNECTION;
|
||||
uint64_t thread_id = 0;
|
||||
bool parsed = parse_kill_query(querybuf, &thread_id, &kt);
|
||||
|
||||
if (parsed && (thread_id > 0)) // MaxScale session counter starts at 1
|
||||
{
|
||||
switch (kt)
|
||||
{
|
||||
case KT_CONNECTION:
|
||||
session_broadcast_kill_command(dcb->session, thread_id);
|
||||
// Even if id not found, send ok. TODO: send a correct response to client
|
||||
mxs_mysql_send_ok(dcb, 1, 0, NULL);
|
||||
rval = RES_END;
|
||||
break;
|
||||
|
||||
case KT_QUERY:
|
||||
// TODO: Implement this
|
||||
MXS_WARNING("Received 'KILL QUERY %" PRIu64 "' from "
|
||||
"the client. This feature is not supported.", thread_id);
|
||||
mysql_send_custom_error(dcb, 1, 0, "'KILL QUERY <thread_id>' "
|
||||
"is not supported.");
|
||||
rval = RES_END;
|
||||
break;
|
||||
|
||||
default:
|
||||
ss_dassert(!true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Look at the start of the query and see if it might contain "KILL" */
|
||||
if (strncasecmp(WORD_KILL, startbuf, copied_len) == 0)
|
||||
{
|
||||
rval = RES_MORE_DATA;
|
||||
}
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a "KILL [CONNECTION | QUERY] <process_id>" query. Will modify
|
||||
* the argument string even if unsuccessful.
|
||||
*
|
||||
* @param query Query string to parse
|
||||
* @paran thread_id_out Thread id output
|
||||
* @param kt_out Kill command type output
|
||||
* @return true on success, false on error
|
||||
*/
|
||||
static bool parse_kill_query(char *query, uint64_t *thread_id_out, kill_type_t *kt_out)
|
||||
{
|
||||
const char WORD_CONNECTION[] = "CONNECTION";
|
||||
const char WORD_QUERY[] = "QUERY";
|
||||
const char DELIM[] = " \n\t";
|
||||
|
||||
kill_type_t kill_type = KT_CONNECTION;
|
||||
unsigned long long int thread_id = 0;
|
||||
|
||||
enum kill_parse_state_t
|
||||
{
|
||||
KILL,
|
||||
CONN_QUERY,
|
||||
ID,
|
||||
SEMICOLON,
|
||||
DONE
|
||||
} state = KILL;
|
||||
char *saveptr = NULL;
|
||||
bool error = false;
|
||||
|
||||
char *token = strtok_r(query, DELIM, &saveptr);
|
||||
|
||||
while (token && !error)
|
||||
{
|
||||
bool get_next = false;
|
||||
switch (state)
|
||||
{
|
||||
case KILL:
|
||||
if (strncasecmp(token, WORD_KILL, sizeof(WORD_KILL) - 1) == 0)
|
||||
{
|
||||
state = CONN_QUERY;
|
||||
get_next = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONN_QUERY:
|
||||
if (strncasecmp(token, WORD_QUERY, sizeof(WORD_QUERY) - 1) == 0)
|
||||
{
|
||||
kill_type = KT_QUERY;
|
||||
get_next = true;
|
||||
}
|
||||
else if (strncasecmp(token, WORD_CONNECTION, sizeof(WORD_CONNECTION) - 1) == 0)
|
||||
{
|
||||
get_next = true;
|
||||
}
|
||||
/* Move to next state regardless of comparison result. The current
|
||||
* part is optional and the process id may already be in the token. */
|
||||
state = ID;
|
||||
break;
|
||||
|
||||
case ID:
|
||||
{
|
||||
/* strtoull() accepts negative numbers, so check for '-' here */
|
||||
if (*token == '-')
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
char *endptr_id = NULL;
|
||||
thread_id = strtoull(token, &endptr_id, 0);
|
||||
if ((thread_id == ULLONG_MAX) && (errno == ERANGE))
|
||||
{
|
||||
error = true;
|
||||
errno = 0;
|
||||
}
|
||||
else if (endptr_id == token)
|
||||
{
|
||||
error = true; // No digits were read
|
||||
}
|
||||
else if (*endptr_id == '\0') // Can be real end or written by strtok
|
||||
{
|
||||
state = SEMICOLON; // In case we have space before ;
|
||||
get_next = true;
|
||||
}
|
||||
else if (*endptr_id == ';')
|
||||
{
|
||||
token = endptr_id;
|
||||
state = SEMICOLON;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SEMICOLON:
|
||||
{
|
||||
if (strncmp(token, ";", 1) == 0)
|
||||
{
|
||||
state = DONE;
|
||||
get_next = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (get_next)
|
||||
{
|
||||
token = strtok_r(NULL, DELIM, &saveptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (error || (state != DONE && state != SEMICOLON))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
*thread_id_out = thread_id;
|
||||
*kt_out = kill_type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1410,7 +1410,6 @@ gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
|
||||
uint8_t *server_version_end = NULL;
|
||||
uint16_t mysql_server_capabilities_one = 0;
|
||||
uint16_t mysql_server_capabilities_two = 0;
|
||||
unsigned long tid = 0;
|
||||
uint8_t scramble_data_1[GW_SCRAMBLE_LENGTH_323] = "";
|
||||
uint8_t scramble_data_2[GW_MYSQL_SCRAMBLE_SIZE - GW_SCRAMBLE_LENGTH_323] = "";
|
||||
uint8_t capab_ptr[4] = "";
|
||||
@ -1433,8 +1432,10 @@ gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
|
||||
payload = server_version_end + 1;
|
||||
|
||||
// get ThreadID: 4 bytes
|
||||
tid = gw_mysql_get_byte4(payload);
|
||||
memcpy(&conn->tid, &tid, 4);
|
||||
uint32_t tid = gw_mysql_get_byte4(payload);
|
||||
/* TODO: Correct value of thread id could be queried later from backend if
|
||||
* there is any worry it might be larger than 32bit allows. */
|
||||
conn->thread_id = tid;
|
||||
|
||||
payload += 4;
|
||||
|
||||
|
4
server/modules/protocol/MySQL/test/CMakeLists.txt
Normal file
4
server/modules/protocol/MySQL/test/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_executable(test_parse_kill test_parse_kill.c)
|
||||
target_link_libraries(test_parse_kill maxscale-common MySQLCommon)
|
||||
add_test(test_parse_kill test_parse_kill)
|
||||
|
89
server/modules/protocol/MySQL/test/test_parse_kill.c
Normal file
89
server/modules/protocol/MySQL/test/test_parse_kill.c
Normal file
@ -0,0 +1,89 @@
|
||||
#include <maxscale/cdefs.h>
|
||||
|
||||
#include "../MySQLClient/mysql_client.c"
|
||||
|
||||
int test_one_query(char *query, bool should_succeed, uint64_t expected_tid,
|
||||
kill_type_t expected_kt)
|
||||
{
|
||||
char *query_copy = MXS_STRDUP_A(query);
|
||||
uint64_t result_tid = 1111111;
|
||||
kill_type_t result_kt = KT_QUERY;
|
||||
|
||||
/* If the parse fails, these should remain unchanged */
|
||||
if (!should_succeed)
|
||||
{
|
||||
result_tid = expected_tid;
|
||||
result_kt = expected_kt;
|
||||
}
|
||||
bool success = parse_kill_query(query_copy, &result_tid, &result_kt);
|
||||
MXS_FREE(query_copy);
|
||||
|
||||
if ((success == should_succeed) && (result_tid == expected_tid) &&
|
||||
(result_kt == expected_kt))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Result wrong on query: '%s'.\n", query);
|
||||
if (success != should_succeed)
|
||||
{
|
||||
printf("Expected success '%d', got '%d'.\n", should_succeed, success);
|
||||
}
|
||||
if (result_tid != expected_tid)
|
||||
{
|
||||
printf("Expected thread id '%" PRIu64 "', got '%" PRIu64 "'.\n",
|
||||
expected_tid, result_tid);
|
||||
}
|
||||
if (result_kt != expected_kt)
|
||||
{
|
||||
printf("Expected kill type '%u', got '%u'.\n",
|
||||
expected_kt, result_kt);
|
||||
}
|
||||
printf("\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
typedef struct test_t
|
||||
{
|
||||
char *query;
|
||||
bool should_succeed;
|
||||
uint64_t correct_id;
|
||||
kill_type_t correct_kt;
|
||||
} test_t;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
test_t tests[] =
|
||||
{
|
||||
{" kill ConNectioN 123 ", true, 123, KT_CONNECTION},
|
||||
{"kIlL coNNectioN 987654321 ;", true, 987654321, KT_CONNECTION},
|
||||
{" Ki5L CoNNectioN 987654321 ", false, 0, KT_CONNECTION},
|
||||
{"1", false, 0, KT_CONNECTION},
|
||||
{"kILL 1", true, 1, KT_CONNECTION},
|
||||
{"\n\t kill \nQueRy 456", true, 456, KT_QUERY},
|
||||
{" A kill 1; ", false, 0, KT_CONNECTION},
|
||||
{" kill connection 1A", false, 0, KT_CONNECTION},
|
||||
{" kill connection 1 A ", false, 0, KT_CONNECTION},
|
||||
{"kill query 7 ; select * ", false, 0, KT_CONNECTION},
|
||||
{
|
||||
"KIll query \t \n \t 12345678901234567890 \n \t ",
|
||||
true, 12345678901234567890ULL, KT_QUERY
|
||||
},
|
||||
{"KIll query \t \n \t 21 \n \t ", true, 21, KT_QUERY},
|
||||
{"KIll \t \n \t -6 \n \t ", false, 0, KT_CONNECTION},
|
||||
{"KIll 12345678901234567890123456 \n \t ", false, 0, KT_CONNECTION},
|
||||
{"kill ;", false, 0, KT_QUERY}
|
||||
};
|
||||
int result = 0;
|
||||
int arr_size = sizeof(tests) / sizeof(test_t);
|
||||
for (int i = 0; i < arr_size; i++)
|
||||
{
|
||||
char *query = tests[i].query;
|
||||
bool should_succeed = tests[i].should_succeed;
|
||||
uint64_t expected_tid = tests[i].correct_id;
|
||||
kill_type_t expected_kt = tests[i].correct_kt;
|
||||
result += test_one_query(query, should_succeed, expected_tid, expected_kt);
|
||||
}
|
||||
return result;
|
||||
}
|
@ -129,8 +129,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
inst->service = service;
|
||||
inst->user = service->credentials.name;
|
||||
inst->password = service->credentials.authdata;
|
||||
inst->user = MXS_STRDUP_A(service->credentials.name);
|
||||
inst->password = MXS_STRDUP_A(service->credentials.authdata);
|
||||
|
||||
MXS_NOTICE("testbinlog v1.0");
|
||||
|
||||
|
@ -363,7 +363,7 @@ int32_t SchemaRouterSession::routeQuery(GWBUF* pPacket)
|
||||
if (m_config->debug)
|
||||
{
|
||||
sprintf(errbuf + strlen(errbuf),
|
||||
" ([%" PRIu32 "]: DB change failed)",
|
||||
" ([%" PRIu64 "]: DB change failed)",
|
||||
m_client->session->ses_id);
|
||||
}
|
||||
|
||||
@ -991,7 +991,7 @@ bool SchemaRouterSession::handle_default_db()
|
||||
sprintf(errmsg, "Unknown database '%s'", m_connect_db.c_str());
|
||||
if (m_config->debug)
|
||||
{
|
||||
sprintf(errmsg + strlen(errmsg), " ([%" PRIu32 "]: DB not found on connect)",
|
||||
sprintf(errmsg + strlen(errmsg), " ([%" PRIu64 "]: DB not found on connect)",
|
||||
m_client->session->ses_id);
|
||||
}
|
||||
write_error_to_client(m_client,
|
||||
|
Loading…
x
Reference in New Issue
Block a user