Merge branch 'binlog_server_wait_data' into binlog_server_waitdata_encryption
This commit is contained in:
@ -218,13 +218,12 @@ if(WITH_MAXSCALE_CNF AND (NOT TARGET_COMPONENT OR "core" STREQUAL "${TARGET_COMP
|
||||
install_custom_file(server/maxscale.cnf.template ${MAXSCALE_CONFDIR} core)
|
||||
endif()
|
||||
|
||||
# This should be moved to the qc_mysqlembedded directory
|
||||
# install(PROGRAMS ${ERRMSG} DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale)
|
||||
|
||||
install_file(${CMAKE_SOURCE_DIR}/COPYRIGHT core)
|
||||
install_file(${CMAKE_SOURCE_DIR}/README core)
|
||||
install_file(${CMAKE_SOURCE_DIR}/LICENSE.TXT core)
|
||||
install_file(etc/lsyncd_example.conf core)
|
||||
install_manual(Documentation/maxscale.1 1 core)
|
||||
install_file(${CMAKE_SOURCE_DIR}/server/maxscale_binlogserver_template.cnf core)
|
||||
|
||||
|
||||
# Install startup scripts and ldconfig files
|
||||
|
@ -5,14 +5,13 @@
|
||||
* SSL can be used in the communication between MariaDB MaxScale and the backend servers.
|
||||
* The number of allowed connections can explicitly be throttled.
|
||||
* MariaDB MaxScale can continue serving read request even if the master has gone down.
|
||||
* The security of MaxAdmin has been improved; you can only connect from the
|
||||
same host MariaDB MaxScale is running on, and the Linux identity is used for
|
||||
authorization.
|
||||
* The security of MaxAdmin has been improved; Unix domain sockets can be used in the
|
||||
communication with MariaDB MaxScale and the Linux identity can be used for authorization.
|
||||
* MariaDB MaxScale can in real time make binlog events available as raw AVRO or
|
||||
as JSON objects (beta level functionality).
|
||||
|
||||
For more details, please refer to:
|
||||
* [MariaDB MaxScale 2.0.0 Release Notes](Release-Notes/MaxScale-2.0.0-Release-Notes.md)
|
||||
* [MariaDB MaxScale 2.0.1 Release Notes](Release-Notes/MaxScale-2.0.1-Release-Notes.md)
|
||||
|
||||
## MariaDB MaxScale 1.4
|
||||
* Authentication now allows table level resolution of grants. MaxScale service
|
||||
|
@ -0,0 +1,107 @@
|
||||
# Transaction Performance Monitoring Filter
|
||||
|
||||
## Overview
|
||||
|
||||
The Transaction Performance Monitoring (TPM) filter is a filter module for MaxScale that monitors every SQL statement that passes through the filter. The filter groups a series of SQL statements into a transaction by detecting 'commit' or 'rollback' statements. It logs all committed transactions with necessary information, such as timestamp, client, SQL statements, latency, etc., which can be used later for transaction performance analysis.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration block for the TPM filter requires the minimal filter options in it's section within the maxscale.cnf file, stored in /etc/maxscale.cnf.
|
||||
|
||||
```
|
||||
[MyLogFilter]
|
||||
type=filter
|
||||
module=tpmfilter
|
||||
|
||||
[MyService]
|
||||
type=service
|
||||
router=readconnrouter
|
||||
servers=server1
|
||||
user=myuser
|
||||
passwd=mypasswd
|
||||
filters=MyLogFilter
|
||||
```
|
||||
|
||||
## Filter Options
|
||||
|
||||
The TPM filter does not support any filter options currently.
|
||||
|
||||
## Filter Parameters
|
||||
|
||||
The TPM filter accepts a number of optional parameters.
|
||||
|
||||
### Filename
|
||||
|
||||
The name of the output file created for performance logging. The default filename is **tpm.log**.
|
||||
|
||||
```
|
||||
filebase=/tmp/SqlQueryLog
|
||||
```
|
||||
|
||||
### Source
|
||||
|
||||
The optional source parameter defines an address that is used to match against the address from which the client connection to MaxScale originates. Only sessions that originate from this address will be logged.
|
||||
|
||||
```
|
||||
source=127.0.0.1
|
||||
```
|
||||
|
||||
### User
|
||||
|
||||
The optional user parameter defines a user name that is used to match against the user from which the client connection to MaxScale originates. Only sessions that are connected using this username are logged.
|
||||
|
||||
```
|
||||
user=john
|
||||
```
|
||||
|
||||
### Delimiter
|
||||
|
||||
The optional delimiter parameter defines a delimiter that is used to distinguish columns in the log. The default delimiter is **|**.
|
||||
|
||||
```
|
||||
delimiter=:
|
||||
```
|
||||
|
||||
### Query_delimiter
|
||||
|
||||
The optional query_delimiter defines a delimiter that is used to distinguish different SQL statements in a transaction. The default query delimiter is **;**.
|
||||
|
||||
```
|
||||
query_delimiter=@@@
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1 - Log Transactions for Performance Analysis
|
||||
|
||||
You want to log every transaction with its SQL statements and latency for future transaction performance analysis.
|
||||
|
||||
Add a filter with the following definition:
|
||||
|
||||
```
|
||||
[PerformanceLogger]
|
||||
type=filter
|
||||
module=tpmfilter
|
||||
delimiter=::
|
||||
query_delimiter=@@
|
||||
filebase=/var/logs/tpm/perf.log
|
||||
|
||||
[Product Service]
|
||||
type=service
|
||||
router=readconnrouter
|
||||
servers=server1
|
||||
user=myuser
|
||||
passwd=mypasswd
|
||||
filters=PerformanceLogger
|
||||
```
|
||||
|
||||
The following is an example log that is generated from the above TPM filter:
|
||||
|
||||
```
|
||||
1450469909::127.0.0.1::root::5::UPDATE WAREHOUSE SET W_YTD = W_YTD + 1954.67 WHERE W_ID = 1 @@SELECT W_STREET_1, W_STREET_2, W_CITY, W_STATE, W_ZIP, W_NAME FROM WAREHOUSE WHERE W_ID = 1@@UPDATE DISTRICT SET D_YTD = D_YTD + 1954.67 WHERE D_W_ID = 1 AND D_ID = 4@@SELECT D_STREET_1, D_STREET_2, D_CITY, D_STATE, D_ZIP, D_NAME FROM DISTRICT WHERE D_W_ID = 1 AND D_ID = 4@@SELECT C_FIRST, C_MIDDLE, C_LAST, C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, C_PHONE, C_CREDIT, C_CREDIT_LIM, C_DISCOUNT, C_BALANCE, C_YTD_PAYMENT, C_PAYMENT_CNT, C_SINCE FROM CUSTOMER WHERE C_W_ID = 1 AND C_D_ID = 4 AND C_ID = 766@@UPDATE CUSTOMER SET C_BALANCE = 145950.77, C_YTD_PAYMENT = 173436.67, C_PAYMENT_CNT = 67 WHERE C_W_ID = 1 AND C_D_ID = 4 AND C_ID = 766@@INSERT INTO HISTORY (H_C_D_ID, H_C_W_ID, H_C_ID, H_D_ID, H_W_ID, H_DATE, H_AMOUNT, H_DATA) VALUES (4,1,766,4,1,'2015-12-18 15:18:29',1954.67,'sxvnj vivbun')
|
||||
1450469909::127.0.0.1::root::14::UPDATE WAREHOUSE SET W_YTD = W_YTD + 3969.43 WHERE W_ID = 2 @@SELECT W_STREET_1, W_STREET_2, W_CITY, W_STATE, W_ZIP, W_NAME FROM WAREHOUSE WHERE W_ID = 2@@UPDATE DISTRICT SET D_YTD = D_YTD + 3969.43 WHERE D_W_ID = 2 AND D_ID = 5@@SELECT D_STREET_1, D_STREET_2, D_CITY, D_STATE, D_ZIP, D_NAME FROM DISTRICT WHERE D_W_ID = 2 AND D_ID = 5@@SELECT C_FIRST, C_MIDDLE, C_LAST, C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, C_PHONE, C_CREDIT, C_CREDIT_LIM, C_DISCOUNT, C_BALANCE, C_YTD_PAYMENT, C_PAYMENT_CNT, C_SINCE FROM CUSTOMER WHERE C_W_ID = 1 AND C_D_ID = 6 AND C_ID = 1789@@UPDATE CUSTOMER SET C_BALANCE = 169626.31, C_YTD_PAYMENT = 111249.43, C_PAYMENT_CNT = 49 WHERE C_W_ID = 1 AND C_D_ID = 6 AND C_ID = 1789@@INSERT INTO HISTORY (H_C_D_ID, H_C_W_ID, H_C_ID, H_D_ID, H_W_ID, H_DATE, H_AMOUNT, H_DATA) VALUES (6,1,1789,5,2,'2015-12-18 15:18:29',3969.43,'gqfla adopdon')
|
||||
...
|
||||
```
|
||||
|
||||
Note that 5 and 14 are latencies of each transaction in milliseconds.
|
@ -6,6 +6,7 @@ requirements are as follows:
|
||||
* CMake version 2.8 or later (Packaging requires version 2.8.12 or later)
|
||||
* GCC version 4.4.7 or later
|
||||
* libaio
|
||||
* libcurl
|
||||
* OpenSSL
|
||||
* Bison 2.7 or later
|
||||
* Flex 2.5.35 or later
|
||||
|
@ -2,6 +2,12 @@
|
||||
|
||||
MariaDB MaxScale is also made available as a tarball, which is named like `maxscale-x.y.z.OS.tar.gz` where `x.y.z` is the same as the corresponding version and `OS` identifies the operating system, e.g. `maxscale-2.0.1.centos.7.tar.gz`.
|
||||
|
||||
In order to use the tarball, the following libraries are required:
|
||||
|
||||
- libcurl
|
||||
- libaio
|
||||
- OpenSSL
|
||||
|
||||
The tarball has been built with the assumption that it will be installed in `/usr/local`. However, it is possible to install it in any directory, but in that case MariaDB MaxScale must be invoked with a flag.
|
||||
|
||||
## Installing as root in `/usr/local`
|
||||
@ -18,7 +24,7 @@ The required steps are as follows:
|
||||
$ cd maxscale
|
||||
$ sudo chown -R maxscale var
|
||||
|
||||
Creating the symbolic link is necessary, since MariaDB MaxScale has been built with with the assumption that its base-directory, that is, the directory under which all its sub-directories are found, is `/usr/local/maxscale`.
|
||||
Creating the symbolic link is necessary, since MariaDB MaxScale has been built with with the assumption that the plugin directory is `/usr/local/maxscale/lib/maxscale`.
|
||||
|
||||
The symbolic link also makes it easy to switch between different versions of MariaDB MaxScale that have been installed side by side in `/usr/local`; just make the symbolic link point to another installation.
|
||||
|
||||
@ -46,6 +52,12 @@ The `-d` flag causes maxscale _not_ to turn itself into a daemon, which is advis
|
||||
|
||||
If you want to place the configuration file somewhere else but in `/etc` you can invoke MariaDB MaxScale with the `--config` flag, for instance, `--config=/usr/local/maxscale/etc/maxscale.cnf`.
|
||||
|
||||
Note also that if you want to keep _everything_ under `/usr/local/maxscale` you can invoke MariaDB MaxScale using the flag `--basedir`.
|
||||
|
||||
$ sudo bin/maxscale --user=maxscale --basedir=/usr/local/maxscale -d
|
||||
|
||||
That will cause MariaDB MaxScale to look for its configuration file in `/usr/local/maxscale/etc` and to store all runtime files under `/usr/local/maxscale/var`.
|
||||
|
||||
## Installing in any Directory
|
||||
|
||||
Enter a directory where you have the right to create a subdirectory. Then do as follows.
|
||||
|
@ -139,9 +139,8 @@ Packages can be downloaded [here](https://mariadb.com/resources/downloads).
|
||||
|
||||
## Source Code
|
||||
|
||||
The source code of MaxScale is tagged at GitHub with a tag, which is identical
|
||||
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
|
||||
is maxscale-X.Y.Z. Further, *master* always refers to the latest released
|
||||
non-beta version.
|
||||
The source code of MaxScale is tagged at GitHub with a tag, which is derived
|
||||
from the version of MaxScale. For instance, the tag of version `X.Y.Z` of MaxScale
|
||||
is `maxscale-X.Y.Z`.
|
||||
|
||||
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).
|
||||
|
@ -94,12 +94,13 @@ This defines whether (on | off) MariaDB MaxScale sends to the slave the heartbea
|
||||
### `semisync`
|
||||
|
||||
This parameter controls whether binlog server could ask Master server to start the Semi-Synchronous replication.
|
||||
In order to get semi-sync working the Master server must have the *rpl_semi_sync_master* plugin installed.
|
||||
The available plugin and the value of GLOBAL VARIABLE *rpl_semi_sync_master_enabled* are checked in the Master registration phase: if plugin is installed in the Master database the binlog server then requests the semi-sync option.
|
||||
In order to get semi-sync working, the Master server must have the *rpl_semi_sync_master* plugin installed.
|
||||
The availability of the plugin and the value of the GLOBAL VARIABLE *rpl_semi_sync_master_enabled* are checked in the Master registration phase: if the plugin is installed in the Master database, the binlog server subsequently requests the semi-sync option.
|
||||
|
||||
Note:
|
||||
- the network replication stream from Master has two additional bytes before each binlog event.
|
||||
- the Semi-Sync protocol requires an acknoledge packet to be sent back to Master only when requested: the semi-sync flag will have value of 1.
|
||||
This flag is set only if *rpl_semi_sync_master_enabled=1* in the Master, otherwise it will always have value of 0 and no ack packet is sent back.
|
||||
- the Semi-Sync protocol requires an acknowledge packet to be sent back to Master only when requested: the semi-sync flag will have value of 1.
|
||||
This flag is set only if *rpl_semi_sync_master_enabled=1* is set in the Master, otherwise it will always have value of 0 and no ack packet is sent back.
|
||||
|
||||
Please note that semi-sync replication is only related to binlog server to Master communication.
|
||||
### `ssl_cert_verification_depth`
|
||||
|
@ -6,13 +6,13 @@ Last updated 24th June 2015
|
||||
|
||||
The purpose of this tutorial is to introduce the MariaDB MaxScale Administrator to a few of the common administration tasks that need to be performed with MariaDB MaxScale. It is not intended as a reference to all the tasks that may be performed, more this is aimed as an introduction for administrators who are new to MariaDB MaxScale.
|
||||
|
||||
[Starting MariaDB MaxScale](#starting)
|
||||
[Stopping MariaDB MaxScale](#stopping)
|
||||
[Checking The Status Of The MariaDB MaxScale Services](#checking)
|
||||
[Persistent Connections](#persistent)
|
||||
[What Clients Are Connected To MariaDB MaxScale](#clients)
|
||||
[Rotating the Log File](#rotating)
|
||||
[Taking A Database Server Out Of Use](#outofuse)
|
||||
- [Starting MariaDB MaxScale](#starting)
|
||||
- [Stopping MariaDB MaxScale](#stopping)
|
||||
- [Checking The Status Of The MariaDB MaxScale Services](#checking)
|
||||
- [Persistent Connections](#persistent)
|
||||
- [What Clients Are Connected To MariaDB MaxScale](#clients)
|
||||
- [Rotating the Log File](#rotating)
|
||||
- [Taking A Database Server Out Of Use](#outofuse)
|
||||
|
||||
<a name="starting"></a>
|
||||
### Starting MariaDB MaxScale
|
||||
|
@ -16,152 +16,185 @@ MariaDB MaxScale configuration is held in an ini file that is located in the fil
|
||||
|
||||
A global, maxscale, section is included within every MariaDB MaxScale configuration file; this is used to set the values of various MariaDB MaxScale wide parameters, perhaps the most important of these is the number of threads that MariaDB MaxScale will use to execute the code that forwards requests and handles responses for clients.
|
||||
|
||||
[maxscale]
|
||||
threads=4
|
||||
```
|
||||
[maxscale]
|
||||
threads=4
|
||||
```
|
||||
|
||||
Since we are using Galera Cluster and connection routing we want a single to which the client application can connect; MariaDB MaxScale will then route connections to this port onwards to the various nodes within the Galera Cluster. To achieve this within MariaDB MaxScale we need to define a service in the ini file. Create a section for each in your MariaDB MaxScale configuration file and set the type to service, the section name is the names of the service and should be meaningful to the administrator. Names may contain whitespace.
|
||||
|
||||
[Galera Service]
|
||||
type=service
|
||||
```
|
||||
[Galera Service]
|
||||
type=service
|
||||
```
|
||||
|
||||
The router for this section the readconnroute module, also the service should be provided with the list of servers that will be part of the cluster. The server names given here are actually the names of server sections in the configuration file and not the physical hostnames or addresses of the servers.
|
||||
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
```
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
```
|
||||
|
||||
In order to instruct the router to which servers it should route we must add router options to the service. The router options are compared to the status that the monitor collects from the servers and used to restrict the eligible set of servers to which that service may route. In our case we use the option that restricts us to servers that are fully functional members of the Galera cluster which are able to support SQL operations on the cluster. To achieve this we use the router option synced.
|
||||
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=synced
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
```
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=synced
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
```
|
||||
|
||||
The final step in the service section is to add the username and password that will be used to populate the user data from the database cluster. There are two options for representing the password, either plain text or encrypted passwords may be used. In order to use encrypted passwords a set of keys must be generated that will be used by the encryption and decryption process. To generate the keys use the maxkeys command and pass the name of the secrets file in which the keys are stored.
|
||||
|
||||
% maxkeys /var/lib/maxscale/.secrets
|
||||
%
|
||||
```
|
||||
% maxkeys /var/lib/maxscale/.secrets
|
||||
%
|
||||
```
|
||||
|
||||
Once the keys have been created the maxpasswd command can be used to generate the encrypted password.
|
||||
|
||||
% maxpasswd plainpassword
|
||||
96F99AA1315BDC3604B006F427DD9484
|
||||
%
|
||||
```
|
||||
% maxpasswd plainpassword
|
||||
96F99AA1315BDC3604B006F427DD9484
|
||||
%
|
||||
```
|
||||
|
||||
The username and password, either encrypted or plain text, are stored in the service section using the user and passwd parameters.
|
||||
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=synced
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
user=maxscale
|
||||
passwd=96F99AA1315BDC3604B006F427DD9484
|
||||
```
|
||||
[Galera Service]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=synced
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
user=maxscale
|
||||
passwd=96F99AA1315BDC3604B006F427DD9484
|
||||
```
|
||||
|
||||
This completes the definitions required by the service, however listening ports must be associated with a service in order to allow network connections. This is done by creating a series of listener sections. These sections again are named for the convenience of the administrator and should be of type listener with an entry labeled service which contains the name of the service to associate the listener with. Each service may have multiple listeners.
|
||||
|
||||
[Galera Listener]
|
||||
type=listener
|
||||
service=Galera Service
|
||||
```
|
||||
[Galera Listener]
|
||||
type=listener
|
||||
service=Galera Service
|
||||
```
|
||||
|
||||
A listener must also define the protocol module it will use for the incoming network protocol, currently this should be the MySQLClient protocol for all database listeners. The listener may then supply a network port to listen on and/or a socket within the file system.
|
||||
|
||||
[Galera Listener]
|
||||
type=listener
|
||||
service=Galera Service
|
||||
protocol=MySQLClient
|
||||
port=4306
|
||||
socket=/tmp/DB.Cluster
|
||||
```
|
||||
[Galera Listener]
|
||||
type=listener
|
||||
service=Galera Service
|
||||
protocol=MySQLClient
|
||||
port=4306
|
||||
socket=/tmp/DB.Cluster
|
||||
```
|
||||
|
||||
An address parameter may be given if the listener is required to bind to a particular network address when using hosts with multiple network addresses. The default behavior is to listen on all network interfaces.
|
||||
|
||||
The next stage is the configuration is to define the server information. This defines how to connect to each of the servers within the cluster, again a section is created for each server, with the type set to server, the network address and port to connect to and the protocol to use to connect to the server. Currently the protocol for all database connections in MySQLBackend.
|
||||
|
||||
[dbserv1]
|
||||
type=server
|
||||
address=192.168.2.1
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
[dbserv2]
|
||||
type=server
|
||||
address=192.168.2.2
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
[dbserv3]
|
||||
type=server
|
||||
address=192.168.2.3
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
```
|
||||
[dbserv1]
|
||||
type=server
|
||||
address=192.168.2.1
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
|
||||
[dbserv2]
|
||||
type=server
|
||||
address=192.168.2.2
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
|
||||
[dbserv3]
|
||||
type=server
|
||||
address=192.168.2.3
|
||||
port=3306
|
||||
protocol=MySQLBackend
|
||||
```
|
||||
|
||||
In order for MariaDB MaxScale to monitor the servers using the correct monitoring mechanisms a section should be provided that defines the monitor to use and the servers to monitor. Once again a section is created with a symbolic name for the monitor, with the type set to monitor. Parameters are added for the module to use, the list of servers to monitor and the username and password to use when connecting to the the servers with the monitor.
|
||||
|
||||
[Galera Monitor]
|
||||
type=monitor
|
||||
module=galeramon
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
user=maxscale
|
||||
passwd=96F99AA1315BDC3604B006F427DD9484
|
||||
```
|
||||
[Galera Monitor]
|
||||
type=monitor
|
||||
module=galeramon
|
||||
servers=dbserv1, dbserv2, dbserv3
|
||||
user=maxscale
|
||||
passwd=96F99AA1315BDC3604B006F427DD9484
|
||||
```
|
||||
|
||||
As with the password definition in the server either plain text or encrypted passwords may be used.
|
||||
|
||||
The final stage in the configuration is to add the option service which is used by the maxadmin command to connect to MariaDB MaxScale for monitoring and administration purposes. This creates a service section and a listener section.
|
||||
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
[CLI Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
[CLI Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
socket=default
|
||||
```
|
||||
|
||||
## Starting MariaDB MaxScale
|
||||
|
||||
Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface.
|
||||
|
||||
% maxscale
|
||||
```
|
||||
% maxscale
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
% service maxscale start
|
||||
```
|
||||
% service maxscale start
|
||||
```
|
||||
|
||||
Check the error log in /var/log/maxscale to see if any errors are detected in the configuration file and to confirm MariaDB MaxScale has been started. Also the maxadmin command may be used to confirm that MariaDB MaxScale is running and the services, listeners etc have been correctly configured.
|
||||
|
||||
% maxadmin list services
|
||||
```
|
||||
% maxadmin list services
|
||||
|
||||
Services.
|
||||
--------------------------+----------------------+--------+---------------
|
||||
Service Name | Router Module | #Users | Total Sessions
|
||||
--------------------------+----------------------+--------+---------------
|
||||
Galera Service | readconnroute | 1 | 1
|
||||
CLI | cli | 2 | 2
|
||||
--------------------------+----------------------+--------+---------------
|
||||
% maxadmin list servers
|
||||
Servers.
|
||||
-------------------+-----------------+-------+-------------+-------------------
|
||||
Server | Address | Port | Connections | Status
|
||||
-------------------+-----------------+-------+-------------+--------------------
|
||||
dbserv1 | 192.168.2.1 | 3306 | 0 | Running, Synced, Master
|
||||
dbserv2 | 192.168.2.2 | 3306 | 0 | Running, Synced, Slave
|
||||
dbserv3 | 192.168.2.3 | 3306 | 0 | Running, Synced, Slave
|
||||
-------------------+-----------------+-------+-------------+--------------------
|
||||
Services.
|
||||
--------------------------+----------------------+--------+---------------
|
||||
Service Name | Router Module | #Users | Total Sessions
|
||||
--------------------------+----------------------+--------+---------------
|
||||
Galera Service | readconnroute | 1 | 1
|
||||
CLI | cli | 2 | 2
|
||||
--------------------------+----------------------+--------+---------------
|
||||
% maxadmin list servers
|
||||
Servers.
|
||||
-------------------+-----------------+-------+-------------+-------------------
|
||||
Server | Address | Port | Connections | Status
|
||||
-------------------+-----------------+-------+-------------+--------------------
|
||||
dbserv1 | 192.168.2.1 | 3306 | 0 | Running, Synced, Master
|
||||
dbserv2 | 192.168.2.2 | 3306 | 0 | Running, Synced, Slave
|
||||
dbserv3 | 192.168.2.3 | 3306 | 0 | Running, Synced, Slave
|
||||
-------------------+-----------------+-------+-------------+--------------------
|
||||
```
|
||||
|
||||
A Galera Cluster is a multi-master clustering technology, however the monitor is able to impose false notions of master and slave roles within a Galera Cluster in order to facilitate the use of Galera as if it were a standard MySQL Replication setup. This is merely an internal MariaDB MaxScale convenience and has no impact on the behavior of the cluster.
|
||||
|
||||
You can control which Galera node is the master server by using the _priority_ mechanism of the Galera Monitor module. For more details, read the [Galera Monitor](../Monitors/Galera-Monitor.md) documentation.
|
||||
|
||||
% maxadmin list listeners
|
||||
```
|
||||
% maxadmin list listeners
|
||||
|
||||
Listeners.
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
Service Name | Protocol Module | Address | Port | State
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
Galera Service | MySQLClient | * | 4306 | Running
|
||||
CLI | maxscaled | localhost | 6603 | Running
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
%
|
||||
Listeners.
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
Service Name | Protocol Module | Address | Port | State
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
Galera Service | MySQLClient | * | 4306 | Running
|
||||
CLI | maxscaled | localhost | 6603 | Running
|
||||
---------------------+--------------------+-----------------+-------+--------
|
||||
%
|
||||
```
|
||||
|
||||
MariaDB MaxScale is now ready to start accepting client connections and routing them to the master or slaves within your cluster. Other configuration options are available that can alter the criteria used for routing, such as using weights to obtain unequal balancing operations. These options may be found in the MariaDB MaxScale Configuration Guide. More detail on the use of maxadmin can be found in the document ["MaxAdmin - The MariaDB MaxScale Administration & Monitoring Client Application"](../Reference/MaxAdmin.md).
|
||||
|
||||
|
@ -131,10 +131,11 @@ The default value is off, set *transaction_safety=on* to enable the incomplete t
|
||||
|
||||
This parameter controls whether binlog server could ask Master server to start the Semi-Synchronous replication.
|
||||
In order to get semi-sync working the Master server must have the *rpl_semi_sync_master* plugin installed.
|
||||
The available plugin and the value of GLOBAL VARIABLE *rpl_semi_sync_master_enabled* are checked in the Master registration phase: if plugin is installed in the Master database the binlog server then requests the semi-sync option.
|
||||
The availability of the plugin and the value of the GLOBAL VARIABLE *rpl_semi_sync_master_enabled* are checked in the Master registration phase: if the plugin is installed in the Master database the binlog server subsequently requests the semi-sync option.
|
||||
|
||||
Note:
|
||||
- the network replication stream from Master has two additional bytes before each binlog event.
|
||||
- the Semi-Sync protocol requires an acknoledge packet to be sent back to Master only when requested: the semi-sync flag will have value of 1.
|
||||
- the Semi-Sync protocol requires an acknowledge packet to be sent back to Master only when requested: the semi-sync flag will have value of 1.
|
||||
This flag is set only if *rpl_semi_sync_master_enabled=1* in the Master, otherwise it will always have value of 0 and no ack packet is sent back.
|
||||
|
||||
Please note that semi-sync replication is only related to binlog server to Master communication.
|
||||
|
@ -34,3 +34,5 @@ if(EXTRA_PACKAGE_DEPENDENCIES)
|
||||
endif()
|
||||
|
||||
message(STATUS "Generating RPM packages")
|
||||
# Installing this prevents RPM from deleting the /var/lib/maxscale folder
|
||||
install(DIRECTORY DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale)
|
||||
|
@ -15,4 +15,5 @@ if (BUILD_QC_MYSQLEMBEDDED)
|
||||
set_target_properties(qc_mysqlembedded PROPERTIES LINK_FLAGS -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/qc_mysqlembedded.map)
|
||||
#set_target_properties(qc_mysqlembedded PROPERTIES LINK_FLAGS -Wl,-z,defs)
|
||||
install_module(qc_mysqlembedded libmysqld-parser)
|
||||
install(PROGRAMS ${ERRMSG} DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale)
|
||||
endif()
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <platform.h>
|
||||
#include <query_classifier.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <modutil.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include "builtin_functions.h"
|
||||
|
||||
@ -130,6 +131,7 @@ static void info_finish(QC_SQLITE_INFO* info);
|
||||
static void info_free(QC_SQLITE_INFO* info);
|
||||
static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info);
|
||||
static bool is_submitted_query(const QC_SQLITE_INFO* info, const Parse* pParse);
|
||||
static void log_invalid_data(GWBUF* query, const char* message);
|
||||
static bool parse_query(GWBUF* query);
|
||||
static void parse_query_string(const char* query, size_t len);
|
||||
static bool query_is_parsed(GWBUF* query);
|
||||
@ -542,6 +544,33 @@ static bool is_submitted_query(const QC_SQLITE_INFO* info, const Parse* pParse)
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs information about invalid data.
|
||||
*
|
||||
* @param query The query that could not be parsed.
|
||||
* @param message What is being asked for.
|
||||
*/
|
||||
static void log_invalid_data(GWBUF* query, const char* message)
|
||||
{
|
||||
// At this point the query should be contiguous, but better safe than sorry.
|
||||
|
||||
if (GWBUF_LENGTH(query) >= MYSQL_HEADER_LEN + 1)
|
||||
{
|
||||
char *sql;
|
||||
int length;
|
||||
|
||||
if (modutil_extract_SQL(query, &sql, &length))
|
||||
{
|
||||
if (length > GWBUF_LENGTH(query) - MYSQL_HEADER_LEN - 1)
|
||||
{
|
||||
length = GWBUF_LENGTH(query) - MYSQL_HEADER_LEN - 1;
|
||||
}
|
||||
|
||||
MXS_INFO("qc_sqlite: Parsing the query failed, %s: %*s", message, length, sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void append_affected_field(QC_SQLITE_INFO* info, const char* s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
@ -805,7 +834,7 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info,
|
||||
update_affected_fields_from_exprlist(info, pSelect->pEList, NULL);
|
||||
}
|
||||
|
||||
if (pSelect->pWhere)
|
||||
if (pSelect->pWhere)
|
||||
{
|
||||
info->has_clause = true;
|
||||
update_affected_fields(info, 0, pSelect->pWhere, QC_TOKEN_MIDDLE, pSelect->pEList);
|
||||
@ -839,6 +868,7 @@ static void update_names(QC_SQLITE_INFO* info, const char* zDatabase, const char
|
||||
{
|
||||
char* zCopy = MXS_STRDUP(zTable);
|
||||
MXS_ABORT_IF_NULL(zCopy);
|
||||
// TODO: Is this call really needed. Check also sqlite3Dequote.
|
||||
exposed_sqlite3Dequote(zCopy);
|
||||
|
||||
enlarge_string_array(1, info->table_names_len, &info->table_names, &info->table_names_capacity);
|
||||
@ -1136,10 +1166,8 @@ void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcLi
|
||||
update_affected_fields(info, 0, pWhere, QC_TOKEN_MIDDLE, 0);
|
||||
}
|
||||
|
||||
//TODO: Figure out why the following statements, causes a crash
|
||||
//TODO: long down the road.
|
||||
//TODO: exposed_sqlite3SrcListDelete(pParse->db, pTabList);
|
||||
//TODO: exposed_sqlite3ExprDelete(pParse->db, pWhere);
|
||||
exposed_sqlite3ExprDelete(pParse->db, pWhere);
|
||||
exposed_sqlite3SrcListDelete(pParse->db, pTabList);
|
||||
exposed_sqlite3SrcListDelete(pParse->db, pUsing);
|
||||
}
|
||||
|
||||
@ -2503,9 +2531,9 @@ static uint32_t qc_sqlite_get_type(GWBUF* query)
|
||||
{
|
||||
types = info->types;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report query type");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2531,9 +2559,9 @@ static qc_query_op_t qc_sqlite_get_operation(GWBUF* query)
|
||||
{
|
||||
op = info->operation;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report query operation");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2563,9 +2591,9 @@ static char* qc_sqlite_get_created_table_name(GWBUF* query)
|
||||
MXS_ABORT_IF_NULL(created_table_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report created tables");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2591,9 +2619,9 @@ static bool qc_sqlite_is_drop_table_query(GWBUF* query)
|
||||
{
|
||||
is_drop_table = info->is_drop_table;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report whether query is drop table");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2619,9 +2647,9 @@ static bool qc_sqlite_is_real_query(GWBUF* query)
|
||||
{
|
||||
is_real_query = info->is_real_query;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report whether query is a real query");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2663,9 +2691,9 @@ static char** qc_sqlite_get_table_names(GWBUF* query, int* tblsize, bool fullnam
|
||||
*tblsize = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report what tables are accessed");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2702,9 +2730,9 @@ static bool qc_sqlite_query_has_clause(GWBUF* query)
|
||||
{
|
||||
has_clause = info->has_clause;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report whether the query has a where clause");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2730,9 +2758,9 @@ static char* qc_sqlite_get_affected_fields(GWBUF* query)
|
||||
{
|
||||
affected_fields = info->affected_fields;
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report what fields are affected");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2769,9 +2797,9 @@ static char** qc_sqlite_get_database_names(GWBUF* query, int* sizep)
|
||||
database_names = copy_string_array(info->database_names, sizep);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
MXS_ERROR("qc_sqlite: The query operation was not resolved. Response not valid.");
|
||||
log_invalid_data(query, "cannot report what databases are accessed");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -219,6 +219,21 @@ int sqlite3Dequote(char *z){
|
||||
}
|
||||
for(i=1, j=0;; i++){
|
||||
assert( z[i] );
|
||||
#ifdef MAXSCALE
|
||||
if ( z[i]==0 ){
|
||||
// TODO: This is needed only because exposed_sqlite3Dequote() is called
|
||||
// TODO: in qc_sqlite.c:update_names(). That call probably is not needed
|
||||
// TODO: and should be removed, in which case this check could also be
|
||||
// TODO: removed.
|
||||
break;
|
||||
}else if ( z[i]=='\\' ){
|
||||
z[j++] = '\\';
|
||||
if ( z[i+1]==quote || z[i+1]=='\\' ){
|
||||
z[j++] = quote;
|
||||
i++;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if( z[i]==quote ){
|
||||
if( z[i+1]==quote ){
|
||||
z[j++] = quote;
|
||||
|
@ -276,11 +276,16 @@ hkthread(void *data)
|
||||
ptr->nextdue = now + ptr->frequency;
|
||||
taskfn = ptr->task;
|
||||
taskdata = ptr->data;
|
||||
// We need to copy type and name, in case hktask_remove is called from
|
||||
// the callback. Otherwise we will access freed data.
|
||||
HKTASK_TYPE type = ptr->type;
|
||||
char name[strlen(ptr->name) + 1];
|
||||
strcpy(name, ptr->name);
|
||||
spinlock_release(&tasklock);
|
||||
(*taskfn)(taskdata);
|
||||
if (ptr->type == HK_ONESHOT)
|
||||
if (type == HK_ONESHOT)
|
||||
{
|
||||
hktask_remove(ptr->name);
|
||||
hktask_remove(name);
|
||||
}
|
||||
spinlock_acquire(&tasklock);
|
||||
ptr = tasks;
|
||||
|
@ -11,53 +11,13 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
|
||||
/** @file
|
||||
@brief (brief description)
|
||||
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
typedef struct thread_st
|
||||
{
|
||||
skygw_message_t* mes;
|
||||
simple_mutex_t* mtx;
|
||||
size_t* nactive;
|
||||
pthread_t tid;
|
||||
} thread_t;
|
||||
|
||||
static void* thr_run(void* data);
|
||||
static void* thr_run_morelog(void* data);
|
||||
|
||||
#define MAX_NTHR 256
|
||||
#define NITER 100
|
||||
|
||||
#if 1
|
||||
# define TEST1
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
# define TEST2
|
||||
#endif
|
||||
|
||||
#define TEST3
|
||||
#define TEST4
|
||||
|
||||
const char USAGE[] =
|
||||
"usage: %s [-t <#threads>]\n"
|
||||
"\n"
|
||||
"-t: Number of threads. Default is %d.\n";
|
||||
const int N_THR = 4;
|
||||
|
||||
#define TEST_ERROR(msg)\
|
||||
do { fprintf(stderr, "[%s:%d]: %s\n", basename(__FILE__), __LINE__, msg); } while (false)
|
||||
|
||||
static void skygw_log_enable(int priority)
|
||||
{
|
||||
mxs_log_set_priority_enabled(priority, true);
|
||||
@ -78,60 +38,13 @@ int main(int argc, char* argv[])
|
||||
skygw_message_t* mes;
|
||||
simple_mutex_t* mtx;
|
||||
size_t nactive;
|
||||
thread_t** thr = NULL;
|
||||
time_t t;
|
||||
struct tm tm;
|
||||
char c;
|
||||
int nthr = N_THR;
|
||||
|
||||
while ((c = getopt(argc, argv, "t:")) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 't':
|
||||
nthr = atoi(optarg);
|
||||
if (nthr <= 0)
|
||||
{
|
||||
err = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != 0)
|
||||
{
|
||||
fprintf(stderr, USAGE, argv[0], N_THR);
|
||||
err = 1;
|
||||
goto return_err;
|
||||
}
|
||||
|
||||
printf("Using %d threads.\n", nthr);
|
||||
|
||||
thr = (thread_t **)MXS_CALLOC(1, nthr * sizeof(thread_t*));
|
||||
|
||||
if (thr == NULL)
|
||||
{
|
||||
err = 1;
|
||||
goto return_err;
|
||||
}
|
||||
i = atexit(mxs_log_finish);
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
fprintf(stderr, "Couldn't register exit function.\n");
|
||||
}
|
||||
|
||||
succp = mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
fprintf(stderr, "Log manager initialization failed.\n");
|
||||
}
|
||||
ss_dassert(succp);
|
||||
ss_info_dassert(succp, "Log manager initialization failed");
|
||||
|
||||
t = time(NULL);
|
||||
localtime_r(&t, &tm);
|
||||
@ -143,63 +56,63 @@ int main(int argc, char* argv[])
|
||||
tm.tm_min,
|
||||
tm.tm_sec);
|
||||
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("First write with flush.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("Second write with flush.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("Third write, no flush.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("Fourth write, no flush. Next flush only.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
err = mxs_log_flush();
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "My name is %s %d years and %d months.";
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
err = MXS_INFO(logstr, "TraceyTracey", 3, 7);
|
||||
mxs_log_flush();
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
ss_dassert(err == 0);
|
||||
|
||||
err = mxs_log_flush();
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "My name is Tracey Tracey 47 years and 7 months.";
|
||||
err = MXS_INFO("%s", logstr);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "My name is Stacey %s";
|
||||
err = MXS_INFO(logstr, " ");
|
||||
mxs_log_finish();
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "My name is Philip";
|
||||
err = MXS_INFO("%s", logstr);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "Philip.";
|
||||
err = MXS_INFO("%s", logstr);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = "Ph%dlip.";
|
||||
err = MXS_INFO(logstr, 1);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("A terrible error has occurred!");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("Hi, how are you?");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("I'm doing fine!");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("Rather more surprising, at least at first sight, is the fact that a reference to "
|
||||
"a[i] can also be written as *(a+i). In evaluating a[i], C converts it to *(a+i) "
|
||||
@ -207,142 +120,14 @@ int main(int argc, char* argv[])
|
||||
"of this equivalence, it follows that &a[i] and a+i are also identical: a+i is the "
|
||||
"address of the i-th element beyond a.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("I was wondering, you know, it has been such a lovely weather whole morning and I "
|
||||
"thought that would you like to come to my place and have a little piece of cheese "
|
||||
"with us. Just me and my mom - and you, of course. Then, if you wish, we could "
|
||||
"listen to the radio and keep company for our little Steven, my mom's cat, you know.");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
mxs_log_finish();
|
||||
|
||||
#if defined(TEST1)
|
||||
mes = skygw_message_init();
|
||||
mtx = simple_mutex_init(NULL, "testmtx");
|
||||
/** Test starts */
|
||||
|
||||
fprintf(stderr, "\nStarting test #1 \n");
|
||||
|
||||
/** 1 */
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
thr[i] = (thread_t*)MXS_CALLOC(1, sizeof(thread_t));
|
||||
MXS_ABORT_IF_NULL(thr[i]);
|
||||
thr[i]->mes = mes;
|
||||
thr[i]->mtx = mtx;
|
||||
thr[i]->nactive = &nactive;
|
||||
}
|
||||
nactive = nthr;
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
pthread_t p;
|
||||
pthread_create(&p, NULL, thr_run, thr[i]);
|
||||
thr[i]->tid = p;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
skygw_message_wait(mes);
|
||||
simple_mutex_lock(mtx, true);
|
||||
if (nactive > 0)
|
||||
{
|
||||
simple_mutex_unlock(mtx);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
pthread_join(thr[i]->tid, NULL);
|
||||
}
|
||||
/** This is to release memory */
|
||||
mxs_log_finish();
|
||||
|
||||
simple_mutex_unlock(mtx);
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
MXS_FREE(thr[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(TEST2)
|
||||
|
||||
fprintf(stderr, "\nStarting test #2 \n");
|
||||
|
||||
/** 2 */
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
thr[i] = (thread_t*)MXS_CALLOC(1, sizeof(thread_t));
|
||||
MXS_ABORT_IF_NULL(thr[i]);
|
||||
thr[i]->mes = mes;
|
||||
thr[i]->mtx = mtx;
|
||||
thr[i]->nactive = &nactive;
|
||||
}
|
||||
nactive = nthr;
|
||||
|
||||
fprintf(stderr,
|
||||
"\nLaunching %d threads, each iterating %d times.",
|
||||
nthr,
|
||||
NITER);
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
pthread_t p;
|
||||
pthread_create(&p, NULL, thr_run_morelog, thr[i]);
|
||||
thr[i]->tid = p;
|
||||
}
|
||||
|
||||
fprintf(stderr, ".. done");
|
||||
|
||||
fprintf(stderr, "\nStarting to wait threads.\n");
|
||||
|
||||
do
|
||||
{
|
||||
skygw_message_wait(mes);
|
||||
simple_mutex_lock(mtx, true);
|
||||
if (nactive > 0)
|
||||
{
|
||||
simple_mutex_unlock(mtx);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
pthread_join(thr[i]->tid, NULL);
|
||||
}
|
||||
/** This is to release memory */
|
||||
mxs_log_finish();
|
||||
|
||||
simple_mutex_unlock(mtx);
|
||||
|
||||
fprintf(stderr, "\nFreeing thread memory.");
|
||||
|
||||
for (i = 0; i < nthr; i++)
|
||||
{
|
||||
MXS_FREE(thr[i]);
|
||||
}
|
||||
|
||||
/** Test ended here */
|
||||
skygw_message_done(mes);
|
||||
simple_mutex_done(mtx);
|
||||
#endif /* TEST 2 */
|
||||
|
||||
#if defined(TEST3)
|
||||
|
||||
/**
|
||||
* Test enable/disable log.
|
||||
*/
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
succp = mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
ss_dassert(succp);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
logstr = ("\tTEST 3 - test enabling and disabling logs.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
@ -401,16 +186,8 @@ int main(int argc, char* argv[])
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
mxs_log_finish();
|
||||
|
||||
#endif /* TEST 3 */
|
||||
|
||||
#if defined(TEST4)
|
||||
succp = mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
ss_dassert(succp);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
logstr = ("\tTEST 4 - test spreading logs down to other logs.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
@ -440,13 +217,9 @@ int main(int argc, char* argv[])
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
|
||||
mxs_log_finish();
|
||||
|
||||
succp = mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
ss_dassert(succp);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
logstr = ("6.\tWrite to ERROR and thus also to MESSAGE and TRACE logs.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
ss_dassert(err == 0);
|
||||
@ -479,240 +252,24 @@ int main(int argc, char* argv[])
|
||||
(int)3,
|
||||
"foo",
|
||||
(int)3);
|
||||
ss_dassert(err == 0);
|
||||
err = MXS_ERROR("12.\tWrite to MESSAGE and TRACE log some "
|
||||
"formattings "
|
||||
": %d %s %d",
|
||||
(int)3,
|
||||
"foo",
|
||||
(int)3);
|
||||
ss_dassert(err == 0);
|
||||
err = MXS_ERROR("13.\tWrite to TRACE log some formattings "
|
||||
": %d %s %d",
|
||||
(int)3,
|
||||
"foo",
|
||||
(int)3);
|
||||
|
||||
ss_dassert(err == 0);
|
||||
|
||||
mxs_log_finish();
|
||||
|
||||
#endif /* TEST 4 */
|
||||
fprintf(stderr, ".. done.\n");
|
||||
return_err:
|
||||
if (thr != NULL)
|
||||
{
|
||||
MXS_FREE(thr);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static void* thr_run(void* data)
|
||||
{
|
||||
thread_t* td = (thread_t *)data;
|
||||
char* logstr;
|
||||
int err;
|
||||
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
mxs_log_flush();
|
||||
logstr = ("Hi, how are you?");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_flush();
|
||||
logstr = ("I was wondering, you know, it has been such a lovely weather whole morning and "
|
||||
"I thought that would you like to come to my place and have a little piece of "
|
||||
"cheese with us. Just me and my mom - and you, of course. Then, if you wish, "
|
||||
"we could listen to the radio and keep company for our little Steven, my mom's "
|
||||
"cat, you know.");
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("Testing. One, two, three\n");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
mxs_log_flush();
|
||||
logstr = ("For automatic and register variables, it is done each time the function or block is entered.");
|
||||
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
err = MXS_INFO("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("Rather more surprising, at least at first sight, is the fact that a reference "
|
||||
"to a[i] can also be written as *(a+i). In evaluating a[i], C converts it to *(a+i) "
|
||||
"immediately; the two forms are equivalent. Applying the operatos & to both parts "
|
||||
"of this equivalence, it follows that &a[i] and a+i are also identical: a+i is the "
|
||||
"address of the i-th element beyond a.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
mxs_log_finish();
|
||||
mxs_log_flush();
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("..and you?");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("For automatic and register variables, it is done each time the function or block is entered.");
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
err = MXS_INFO("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("Rather more surprising, at least at first sight, is the fact that a reference to "
|
||||
"a[i] can also be written as *(a+i). In evaluating a[i], C converts it to *(a+i) "
|
||||
"immediately; the two forms are equivalent. Applying the operatos & to both parts "
|
||||
"of this equivalence, it follows that &a[i] and a+i are also identical: a+i is the "
|
||||
"address of the i-th element beyond a.");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("..... and you too?");
|
||||
err = MXS_NOTICE("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
mxs_log_flush();
|
||||
logstr = ("For automatic and register variables, it is done each time the function or block is entered.");
|
||||
#if !defined(SS_DEBUG)
|
||||
skygw_log_enable(LOG_INFO);
|
||||
#endif
|
||||
err = MXS_INFO("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("Testing. One, two, three, four\n");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
logstr = ("Testing. One, two, three, .. where was I?\n");
|
||||
err = MXS_ERROR("%s", logstr);
|
||||
if (err != 0)
|
||||
{
|
||||
TEST_ERROR("Error, log write failed.");
|
||||
}
|
||||
ss_dassert(err == 0);
|
||||
mxs_log_finish();
|
||||
mxs_log_init(NULL, "/tmp", MXS_LOG_TARGET_FS);
|
||||
mxs_log_finish();
|
||||
simple_mutex_lock(td->mtx, true);
|
||||
*td->nactive -= 1;
|
||||
simple_mutex_unlock(td->mtx);
|
||||
skygw_message_send(td->mes);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static int nstr(char** str_arr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; str_arr[i] != NULL; i++)
|
||||
{
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
char* logs[] =
|
||||
{
|
||||
"foo",
|
||||
"bar",
|
||||
"done",
|
||||
"critical test logging",
|
||||
"longer test l o g g g i n g",
|
||||
"reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
||||
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally looooooooooooooooooooooooooooooooooooooo"
|
||||
"ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line",
|
||||
"shoorter one",
|
||||
"two",
|
||||
"scrap : 834nuft984pnw8ynup4598yp8wup8upwn48t5gpn45",
|
||||
"more the same : f98uft5p8ut2p44449upnt5",
|
||||
"asdasd987987asdasd987987asdasd987987asdasd987987asdasd987987asdasd987987asdasd987987asdasd98987",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static void* thr_run_morelog(void* data)
|
||||
{
|
||||
thread_t* td = (thread_t *)data;
|
||||
int err;
|
||||
int i;
|
||||
int nmsg;
|
||||
|
||||
nmsg = nstr(logs);
|
||||
|
||||
for (i = 0; i < NITER; i++)
|
||||
{
|
||||
char* str = logs[rand() % nmsg];
|
||||
err = MXS_LOG_MESSAGE((int)(rand() % (LOG_DEBUG + 1)),
|
||||
"%s - iteration # %d",
|
||||
str,
|
||||
i);
|
||||
if (err != 0)
|
||||
{
|
||||
fprintf(stderr, "Error, log write failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
simple_mutex_lock(td->mtx, true);
|
||||
*td->nactive -= 1;
|
||||
simple_mutex_unlock(td->mtx);
|
||||
skygw_message_send(td->mes);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -265,6 +265,12 @@ cdc_auth_set_client_data(CDC_session *client_data,
|
||||
uint8_t *client_auth_packet,
|
||||
int client_auth_packet_size)
|
||||
{
|
||||
if (client_auth_packet_size % 2 != 0)
|
||||
{
|
||||
/** gw_hex2bin expects an even number of bytes */
|
||||
client_auth_packet_size--;
|
||||
}
|
||||
|
||||
int rval = CDC_STATE_AUTH_ERR;
|
||||
int decoded_size = client_auth_packet_size / 2;
|
||||
char decoded_buffer[decoded_size + 1]; // Extra for terminating null
|
||||
|
@ -11,3 +11,4 @@ add_subdirectory(regexfilter)
|
||||
add_subdirectory(tee)
|
||||
add_subdirectory(testfilter)
|
||||
add_subdirectory(topfilter)
|
||||
add_subdirectory(tpmfilter)
|
||||
|
@ -184,7 +184,7 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
|
||||
if (!status.ok())
|
||||
{
|
||||
MXS_ERROR("Could not store version information to created RocksDB database \"%s\". "
|
||||
"You may need to delete the database and retry. RocksDB error: %s",
|
||||
"You may need to delete the database and retry. RocksDB error: \"%s\"",
|
||||
path.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
@ -236,7 +236,7 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Could not read version information from RocksDB database %s. "
|
||||
"You may need to delete the database and retry. RocksDB error: %s",
|
||||
"You may need to delete the database and retry. RocksDB error: \"%s\"",
|
||||
path.c_str(),
|
||||
status.ToString().c_str());
|
||||
delete pDb;
|
||||
@ -244,8 +244,13 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Could not open/initialize RocksDB database %s. RocksDB error: %s",
|
||||
MXS_ERROR("Could not open/initialize RocksDB database %s. RocksDB error: \"%s\"",
|
||||
path.c_str(), status.ToString().c_str());
|
||||
|
||||
if (status.IsIOError())
|
||||
{
|
||||
MXS_ERROR("Is an other MaxScale process running?");
|
||||
}
|
||||
}
|
||||
|
||||
return pStorage;
|
||||
|
@ -484,7 +484,7 @@ regex_replace(const char *sql, pcre2_code *re, pcre2_match_data *match_data, con
|
||||
size_t result_size;
|
||||
|
||||
/** This should never fail with rc == 0 because we used pcre2_match_data_create_from_pattern() */
|
||||
if (pcre2_match(re, (PCRE2_SPTR) sql, PCRE2_ZERO_TERMINATED, 0, 0, match_data, NULL))
|
||||
if (pcre2_match(re, (PCRE2_SPTR) sql, PCRE2_ZERO_TERMINATED, 0, 0, match_data, NULL) > 0)
|
||||
{
|
||||
result_size = strlen(sql) + strlen(replace);
|
||||
result = MXS_MALLOC(result_size);
|
||||
|
4
server/modules/filter/tpmfilter/CMakeLists.txt
Normal file
4
server/modules/filter/tpmfilter/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(tpmfilter SHARED tpmfilter.c)
|
||||
target_link_libraries(tpmfilter maxscale-common)
|
||||
set_target_properties(tpmfilter PROPERTIES VERSION "1.0.0")
|
||||
install_module(tpmfilter experimental)
|
559
server/modules/filter/tpmfilter/tpmfilter.c
Normal file
559
server/modules/filter/tpmfilter/tpmfilter.c
Normal file
@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
|
||||
*
|
||||
* Change Date: 2019-07-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tpmfilter.c - Transaction Performance Monitoring Filter
|
||||
* @verbatim
|
||||
*
|
||||
* A simple filter that groups queries into a transaction with the latency.
|
||||
*
|
||||
* The filter reads the routed queries, groups them into a transaction by
|
||||
* detecting 'commit' statement at the end. The transactions are timestamped with a
|
||||
* unix-timestamp and the latency of a transaction is recorded in milliseconds.
|
||||
* The filter will not record transactions that are rolled back.
|
||||
* Please note that the filter only works with 'autocommit' option disabled.
|
||||
*
|
||||
* The filter makes no attempt to deal with query packets that do not fit
|
||||
* in a single GWBUF.
|
||||
*
|
||||
* Optional parameters:
|
||||
* filename=<name of the file to which transaction performance logs are written (default=tpm.log)>
|
||||
* delimiter=<delimiter for columns in a log (default='|')>
|
||||
* query_delimiter=<delimiter for query statements in a transaction (default=';')>
|
||||
* source=<source address to limit filter>
|
||||
* user=<username to limit filter>
|
||||
*
|
||||
* Date Who Description
|
||||
* 06/12/2015 Dong Young Yoon Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <filter.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <regex.h>
|
||||
#include <atomic.h>
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
|
||||
MODULE_INFO info =
|
||||
{
|
||||
MODULE_API_FILTER,
|
||||
MODULE_GA,
|
||||
FILTER_VERSION,
|
||||
"Transaction Performance Monitoring filter"
|
||||
};
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static size_t buf_size = 10;
|
||||
static size_t sql_size_limit = 64 * 1024 *
|
||||
1024; /* The maximum size for query statements in a transaction (64MB) */
|
||||
|
||||
/*
|
||||
* The filter entry points
|
||||
*/
|
||||
static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **);
|
||||
static void *newSession(FILTER *instance, SESSION *session);
|
||||
static void closeSession(FILTER *instance, void *session);
|
||||
static void freeSession(FILTER *instance, void *session);
|
||||
static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream);
|
||||
static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream);
|
||||
static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue);
|
||||
static int clientReply(FILTER *instance, void *fsession, GWBUF *queue);
|
||||
static void diagnostic(FILTER *instance, void *fsession, DCB *dcb);
|
||||
|
||||
static FILTER_OBJECT MyObject =
|
||||
{
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
setDownstream,
|
||||
setUpstream,
|
||||
routeQuery,
|
||||
clientReply,
|
||||
diagnostic,
|
||||
};
|
||||
|
||||
/**
|
||||
* A instance structure, every instance will write to a same file.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int sessions; /* Session count */
|
||||
char *source; /* The source of the client connection */
|
||||
char *user; /* The user name to filter on */
|
||||
char *filename; /* filename */
|
||||
char *delimiter; /* delimiter for columns in a log */
|
||||
char *query_delimiter; /* delimiter for query statements in a transaction */
|
||||
|
||||
int query_delimiter_size; /* the length of the query delimiter */
|
||||
FILE* fp;
|
||||
} TPM_INSTANCE;
|
||||
|
||||
/**
|
||||
* The session structure for this TPM filter.
|
||||
* This stores the downstream filter information, such that the
|
||||
* filter is able to pass the query on to the next filter (or router)
|
||||
* in the chain.
|
||||
*
|
||||
* It also holds the file descriptor to which queries are written.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
DOWNSTREAM down;
|
||||
UPSTREAM up;
|
||||
int active;
|
||||
char *clientHost;
|
||||
char *userName;
|
||||
char* sql;
|
||||
struct timeval start;
|
||||
char *current;
|
||||
int n_statements;
|
||||
struct timeval total;
|
||||
struct timeval current_start;
|
||||
bool query_end;
|
||||
char *buf;
|
||||
int sql_index;
|
||||
size_t max_sql_size;
|
||||
} TPM_SESSION;
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
FILTER_OBJECT *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the filter for a particular service
|
||||
* within MaxScale.
|
||||
*
|
||||
* @param options The options for this filter
|
||||
* @param params The array of name/value pair parameters for the filter
|
||||
*
|
||||
* @return The instance data for this new instance
|
||||
*/
|
||||
static FILTER *
|
||||
createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
{
|
||||
int i;
|
||||
TPM_INSTANCE *my_instance;
|
||||
|
||||
if ((my_instance = calloc(1, sizeof(TPM_INSTANCE))) != NULL)
|
||||
{
|
||||
my_instance->source = NULL;
|
||||
my_instance->user = NULL;
|
||||
|
||||
/* set default log filename */
|
||||
my_instance->filename = strdup("tpm.log");
|
||||
/* set default delimiter */
|
||||
my_instance->delimiter = strdup("|");
|
||||
/* set default query delimiter */
|
||||
my_instance->query_delimiter = strdup(";");
|
||||
my_instance->query_delimiter_size = 1;
|
||||
|
||||
for (i = 0; params && params[i]; i++)
|
||||
{
|
||||
if (!strcmp(params[i]->name, "filename"))
|
||||
{
|
||||
free(my_instance->filename);
|
||||
my_instance->filename = strdup(params[i]->value);
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "source"))
|
||||
{
|
||||
my_instance->source = strdup(params[i]->value);
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "user"))
|
||||
{
|
||||
my_instance->user = strdup(params[i]->value);
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "delimiter"))
|
||||
{
|
||||
free(my_instance->delimiter);
|
||||
my_instance->delimiter = strdup(params[i]->value);
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "query_delimiter"))
|
||||
{
|
||||
free(my_instance->query_delimiter);
|
||||
my_instance->query_delimiter = strdup(params[i]->value);
|
||||
my_instance->query_delimiter_size = strlen(my_instance->query_delimiter);
|
||||
}
|
||||
}
|
||||
my_instance->sessions = 0;
|
||||
my_instance->fp = fopen(my_instance->filename, "w");
|
||||
if (my_instance->fp == NULL)
|
||||
{
|
||||
MXS_ERROR("Opening output file '%s' for tpmfilter failed due to %d, %s", my_instance->filename, errno,
|
||||
strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return (FILTER *)my_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new session with this instance of the filter.
|
||||
*
|
||||
* Every session uses the same log file.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
newSession(FILTER *instance, SESSION *session)
|
||||
{
|
||||
TPM_INSTANCE *my_instance = (TPM_INSTANCE *)instance;
|
||||
TPM_SESSION *my_session;
|
||||
int i;
|
||||
char *remote, *user;
|
||||
|
||||
if ((my_session = calloc(1, sizeof(TPM_SESSION))) != NULL)
|
||||
{
|
||||
atomic_add(&my_instance->sessions, 1);
|
||||
|
||||
my_session->max_sql_size = 4 * 1024; // default max query size of 4k.
|
||||
my_session->sql = (char*)malloc(my_session->max_sql_size);
|
||||
memset(my_session->sql, 0x00, my_session->max_sql_size);
|
||||
my_session->buf = (char*)malloc(buf_size);
|
||||
my_session->sql_index = 0;
|
||||
my_session->n_statements = 0;
|
||||
my_session->total.tv_sec = 0;
|
||||
my_session->total.tv_usec = 0;
|
||||
my_session->current = NULL;
|
||||
if ((remote = session_get_remote(session)) != NULL)
|
||||
{
|
||||
my_session->clientHost = strdup(remote);
|
||||
}
|
||||
else
|
||||
{
|
||||
my_session->clientHost = NULL;
|
||||
}
|
||||
if ((user = session_getUser(session)) != NULL)
|
||||
{
|
||||
my_session->userName = strdup(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
my_session->userName = NULL;
|
||||
}
|
||||
my_session->active = 1;
|
||||
if (my_instance->source && my_session->clientHost && strcmp(my_session->clientHost,
|
||||
my_instance->source))
|
||||
{
|
||||
my_session->active = 0;
|
||||
}
|
||||
if (my_instance->user && my_session->userName && strcmp(my_session->userName,
|
||||
my_instance->user))
|
||||
{
|
||||
my_session->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return my_session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a session with the filter, this is the mechanism
|
||||
* by which a filter may cleanup data structure etc.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(FILTER *instance, void *session)
|
||||
{
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
TPM_INSTANCE *my_instance = (TPM_INSTANCE *)instance;
|
||||
if (my_instance->fp != NULL)
|
||||
{
|
||||
// flush FP when a session is closed.
|
||||
fflush(my_instance->fp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the memory associated with the session
|
||||
*
|
||||
* @param instance The filter instance
|
||||
* @param session The filter session
|
||||
*/
|
||||
static void
|
||||
freeSession(FILTER *instance, void *session)
|
||||
{
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
|
||||
free(my_session->clientHost);
|
||||
free(my_session->userName);
|
||||
free(my_session->sql);
|
||||
free(my_session->buf);
|
||||
free(session);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the downstream filter or router to which queries will be
|
||||
* passed from this filter.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param downstream The downstream filter or router.
|
||||
*/
|
||||
static void
|
||||
setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
|
||||
{
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
|
||||
my_session->down = *downstream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upstream filter or session to which results will be
|
||||
* passed from this filter.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param upstream The upstream filter or session.
|
||||
*/
|
||||
static void
|
||||
setUpstream(FILTER *instance, void *session, UPSTREAM *upstream)
|
||||
{
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
|
||||
my_session->up = *upstream;
|
||||
}
|
||||
|
||||
/**
|
||||
* The routeQuery entry point. This is passed the query buffer
|
||||
* to which the filter should be applied. Once applied the
|
||||
* query should normally be passed to the downstream component
|
||||
* (filter or router) in the filter chain.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param queue The query data
|
||||
*/
|
||||
static int
|
||||
routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
||||
{
|
||||
TPM_INSTANCE *my_instance = (TPM_INSTANCE *)instance;
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
char *ptr = NULL;
|
||||
size_t i;
|
||||
|
||||
if (my_session->active)
|
||||
{
|
||||
if (queue->next != NULL)
|
||||
{
|
||||
queue = gwbuf_make_contiguous(queue);
|
||||
}
|
||||
if ((ptr = modutil_get_SQL(queue)) != NULL)
|
||||
{
|
||||
my_session->query_end = false;
|
||||
/* check for commit and rollback */
|
||||
if (strlen(ptr) > 5)
|
||||
{
|
||||
size_t ptr_size = strlen(ptr) + 1;
|
||||
char* buf = my_session->buf;
|
||||
for (i = 0; i < ptr_size && i < buf_size; ++i)
|
||||
{
|
||||
buf[i] = tolower(ptr[i]);
|
||||
}
|
||||
if (strncmp(buf, "commit", 6) == 0)
|
||||
{
|
||||
my_session->query_end = true;
|
||||
}
|
||||
else if (strncmp(buf, "rollback", 8) == 0)
|
||||
{
|
||||
my_session->query_end = true;
|
||||
my_session->sql_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* for normal sql statements */
|
||||
if (!my_session->query_end)
|
||||
{
|
||||
/* check and expand buffer size first. */
|
||||
size_t new_sql_size = my_session->max_sql_size;
|
||||
size_t len = my_session->sql_index + strlen(ptr) + my_instance->query_delimiter_size + 1;
|
||||
|
||||
/* if the total length of query statements exceeds the maximum limit, print an error and return */
|
||||
if (len > sql_size_limit)
|
||||
{
|
||||
MXS_ERROR("The size of query statements exceeds the maximum buffer limit of 64MB.");
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
/* double buffer size until the buffer fits the query */
|
||||
while (len > new_sql_size)
|
||||
{
|
||||
new_sql_size *= 2;
|
||||
}
|
||||
if (new_sql_size > my_session->max_sql_size)
|
||||
{
|
||||
char* new_sql = (char*)malloc(new_sql_size);
|
||||
if (new_sql == NULL)
|
||||
{
|
||||
MXS_ERROR("Memory allocation failure.");
|
||||
goto retblock;
|
||||
}
|
||||
memcpy(new_sql, my_session->sql, my_session->sql_index);
|
||||
free(my_session->sql);
|
||||
my_session->sql = new_sql;
|
||||
my_session->max_sql_size = new_sql_size;
|
||||
}
|
||||
|
||||
/* first statement */
|
||||
if (my_session->sql_index == 0)
|
||||
{
|
||||
memcpy(my_session->sql, ptr, strlen(ptr));
|
||||
my_session->sql_index += strlen(ptr);
|
||||
gettimeofday(&my_session->current_start, NULL);
|
||||
}
|
||||
/* otherwise, append the statement with semicolon as a statement delimiter */
|
||||
else
|
||||
{
|
||||
/* append a query delimiter */
|
||||
memcpy(my_session->sql + my_session->sql_index, my_instance->query_delimiter,
|
||||
my_instance->query_delimiter_size);
|
||||
/* append the next query statement */
|
||||
memcpy(my_session->sql + my_session->sql_index + my_instance->query_delimiter_size, ptr, strlen(ptr));
|
||||
/* set new pointer for the buffer */
|
||||
my_session->sql_index += (my_instance->query_delimiter_size + strlen(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retblock:
|
||||
|
||||
free(ptr);
|
||||
/* Pass the query downstream */
|
||||
return my_session->down.routeQuery(my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
}
|
||||
|
||||
static int
|
||||
clientReply(FILTER *instance, void *session, GWBUF *reply)
|
||||
{
|
||||
TPM_INSTANCE *my_instance = (TPM_INSTANCE *)instance;
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)session;
|
||||
struct timeval tv, diff;
|
||||
int i, inserted;
|
||||
|
||||
/* found 'commit' and sql statements exist. */
|
||||
if (my_session->query_end && my_session->sql_index > 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
timersub(&tv, &(my_session->current_start), &diff);
|
||||
|
||||
/* get latency */
|
||||
uint64_t millis = (diff.tv_sec * (uint64_t)1000 + diff.tv_usec / 1000);
|
||||
/* get timestamp */
|
||||
uint64_t timestamp = (tv.tv_sec + (tv.tv_usec / (1000 * 1000)));
|
||||
|
||||
*(my_session->sql + my_session->sql_index) = '\0';
|
||||
|
||||
/* print to log. */
|
||||
fprintf(my_instance->fp, "%ld%s%s%s%s%s%ld%s%s\n",
|
||||
timestamp,
|
||||
my_instance->delimiter,
|
||||
my_session->clientHost,
|
||||
my_instance->delimiter,
|
||||
my_session->userName,
|
||||
my_instance->delimiter,
|
||||
millis,
|
||||
my_instance->delimiter,
|
||||
my_session->sql);
|
||||
|
||||
my_session->sql_index = 0;
|
||||
}
|
||||
|
||||
/* Pass the result upstream */
|
||||
return my_session->up.clientReply(my_session->up.instance,
|
||||
my_session->up.session, reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnostics routine
|
||||
*
|
||||
* If fsession is NULL then print diagnostics on the filter
|
||||
* instance as a whole, otherwise print diagnostics for the
|
||||
* particular session.
|
||||
*
|
||||
* @param instance The filter instance
|
||||
* @param fsession Filter session, may be NULL
|
||||
* @param dcb The DCB for diagnostic output
|
||||
*/
|
||||
static void
|
||||
diagnostic(FILTER *instance, void *fsession, DCB *dcb)
|
||||
{
|
||||
TPM_INSTANCE *my_instance = (TPM_INSTANCE *)instance;
|
||||
TPM_SESSION *my_session = (TPM_SESSION *)fsession;
|
||||
int i;
|
||||
|
||||
if (my_instance->source)
|
||||
dcb_printf(dcb, "\t\tLimit logging to connections from %s\n",
|
||||
my_instance->source);
|
||||
if (my_instance->user)
|
||||
dcb_printf(dcb, "\t\tLimit logging to user %s\n",
|
||||
my_instance->user);
|
||||
if (my_instance->filename)
|
||||
dcb_printf(dcb, "\t\tLogging to file %s.\n",
|
||||
my_instance->filename);
|
||||
if (my_instance->delimiter)
|
||||
dcb_printf(dcb, "\t\tLogging with delimiter %s.\n",
|
||||
my_instance->delimiter);
|
||||
if (my_instance->query_delimiter)
|
||||
dcb_printf(dcb, "\t\tLogging with query delimiter %s.\n",
|
||||
my_instance->query_delimiter);
|
||||
}
|
@ -449,7 +449,10 @@ avro_client_process_command(AVRO_INSTANCE *router, AVRO_CLIENT *client, GWBUF *q
|
||||
const char req_last_gtid[] = "QUERY-LAST-TRANSACTION";
|
||||
const char req_gtid[] = "QUERY-TRANSACTION";
|
||||
const size_t req_data_len = sizeof(req_data) - 1;
|
||||
uint8_t *data = GWBUF_DATA(queue);
|
||||
size_t buflen = gwbuf_length(queue);
|
||||
uint8_t data[buflen + 1];
|
||||
gwbuf_copy_data(queue, 0, buflen, data);
|
||||
data[buflen] = '\0';
|
||||
char *command_ptr = strstr((char *)data, req_data);
|
||||
|
||||
if (command_ptr != NULL)
|
||||
|
@ -667,6 +667,15 @@ static void closeSession(ROUTER *instance, void *router_session)
|
||||
else
|
||||
{
|
||||
ss_dassert(!BREF_IS_WAITING_RESULT(bref));
|
||||
|
||||
/** This should never be true unless a backend reference is taken
|
||||
* out of use before clearing the BREF_WAITING_RESULT state */
|
||||
if (BREF_IS_WAITING_RESULT(bref))
|
||||
{
|
||||
MXS_WARNING("A closed backend was expecting a result, this should not be possible. "
|
||||
"Decrementing active operation counter for this backend.");
|
||||
bref_clear_state(bref, BREF_WAITING_RESULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Unlock */
|
||||
@ -1044,160 +1053,6 @@ lock_failed:
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Router error handling routine (API)
|
||||
*
|
||||
* Error Handler routine to resolve _backend_ failures. If it succeeds then
|
||||
* there are enough operative backends available and connected. Otherwise it
|
||||
* fails, and session is terminated.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session
|
||||
* @param errmsgbuf The error message to reply
|
||||
* @param backend_dcb The backend DCB
|
||||
* @param action The action: ERRACT_NEW_CONNECTION or
|
||||
* ERRACT_REPLY_CLIENT
|
||||
* @param succp Result of action: true iff router can continue
|
||||
*
|
||||
* Even if succp == true connecting to new slave may have failed. succp is to
|
||||
* tell whether router has enough master/slave connections to continue work.
|
||||
*/
|
||||
static void handleError(ROUTER *instance, void *router_session,
|
||||
GWBUF *errmsgbuf, DCB *problem_dcb,
|
||||
error_action_t action, bool *succp)
|
||||
{
|
||||
SESSION *session;
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session;
|
||||
|
||||
CHK_DCB(problem_dcb);
|
||||
|
||||
/** Don't handle same error twice on same DCB */
|
||||
if (problem_dcb->dcb_errhandle_called)
|
||||
{
|
||||
/** we optimistically assume that previous call succeed */
|
||||
/*
|
||||
* The return of true is potentially misleading, but appears to
|
||||
* be safe with the code as it stands on 9 Sept 2015 - MNB
|
||||
*/
|
||||
*succp = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
session = problem_dcb->session;
|
||||
|
||||
if (session == NULL || rses == NULL)
|
||||
{
|
||||
*succp = false;
|
||||
}
|
||||
else if (DCB_ROLE_CLIENT_HANDLER == problem_dcb->dcb_role)
|
||||
{
|
||||
*succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
CHK_SESSION(session);
|
||||
CHK_CLIENT_RSES(rses);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case ERRACT_NEW_CONNECTION:
|
||||
{
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
{
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* If master has lost its Master status error can't be
|
||||
* handled so that session could continue.
|
||||
*/
|
||||
if (rses->rses_master_ref && rses->rses_master_ref->bref_dcb == problem_dcb &&
|
||||
!SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server))
|
||||
{
|
||||
SERVER *srv = rses->rses_master_ref->bref_backend->backend_server;
|
||||
backend_ref_t *bref;
|
||||
bref = get_bref_from_dcb(rses, problem_dcb);
|
||||
if (bref != NULL)
|
||||
{
|
||||
CHK_BACKEND_REF(bref);
|
||||
if (BREF_IS_WAITING_RESULT(bref))
|
||||
{
|
||||
bref_clear_state(bref, BREF_WAITING_RESULT);
|
||||
}
|
||||
bref_clear_state(bref, BREF_IN_USE);
|
||||
bref_set_state(bref, BREF_CLOSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("server %s:%d lost the "
|
||||
"master status but could not locate the "
|
||||
"corresponding backend ref.",
|
||||
srv->name, srv->port);
|
||||
}
|
||||
|
||||
if (rses->rses_config.rw_master_failure_mode != RW_FAIL_INSTANTLY &&
|
||||
(bref == NULL || !BREF_IS_WAITING_RESULT(bref)))
|
||||
{
|
||||
/** The failure of a master is not considered a critical
|
||||
* failure as partial functionality still remains. Reads
|
||||
* are allowed as long as slave servers are available
|
||||
* and writes will cause an error to be returned.
|
||||
*
|
||||
* If we were waiting for a response from the master, we
|
||||
* can't be sure whether it was executed or not. In this
|
||||
* case the safest thing to do is to close the client
|
||||
* connection. */
|
||||
*succp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!srv->master_err_is_logged)
|
||||
{
|
||||
MXS_ERROR("server %s:%d lost the "
|
||||
"master status. Readwritesplit "
|
||||
"service can't locate the master. "
|
||||
"Client sessions will be closed.",
|
||||
srv->name, srv->port);
|
||||
srv->master_err_is_logged = true;
|
||||
}
|
||||
*succp = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/**
|
||||
* This is called in hope of getting replacement for
|
||||
* failed slave(s). This call may free rses.
|
||||
*/
|
||||
*succp = handle_error_new_connection(inst, &rses, problem_dcb, errmsgbuf);
|
||||
}
|
||||
/* Free the lock if rses still exists */
|
||||
if (rses)
|
||||
{
|
||||
rses_end_locked_router_action(rses);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ERRACT_REPLY_CLIENT:
|
||||
{
|
||||
handle_error_reply_client(session, rses, problem_dcb, errmsgbuf);
|
||||
*succp = false; /*< no new backend servers were made available */
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dcb_close(problem_dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get router capabilities (API)
|
||||
@ -1674,6 +1529,175 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router,
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Router error handling routine (API)
|
||||
*
|
||||
* Error Handler routine to resolve _backend_ failures. If it succeeds then
|
||||
* there are enough operative backends available and connected. Otherwise it
|
||||
* fails, and session is terminated.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session
|
||||
* @param errmsgbuf The error message to reply
|
||||
* @param backend_dcb The backend DCB
|
||||
* @param action The action: ERRACT_NEW_CONNECTION or
|
||||
* ERRACT_REPLY_CLIENT
|
||||
* @param succp Result of action: true iff router can continue
|
||||
*
|
||||
* Even if succp == true connecting to new slave may have failed. succp is to
|
||||
* tell whether router has enough master/slave connections to continue work.
|
||||
*/
|
||||
static void handleError(ROUTER *instance, void *router_session,
|
||||
GWBUF *errmsgbuf, DCB *problem_dcb,
|
||||
error_action_t action, bool *succp)
|
||||
{
|
||||
SESSION *session;
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session;
|
||||
|
||||
CHK_DCB(problem_dcb);
|
||||
|
||||
/** Don't handle same error twice on same DCB */
|
||||
if (problem_dcb->dcb_errhandle_called)
|
||||
{
|
||||
/** we optimistically assume that previous call succeed */
|
||||
/*
|
||||
* The return of true is potentially misleading, but appears to
|
||||
* be safe with the code as it stands on 9 Sept 2015 - MNB
|
||||
*/
|
||||
*succp = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
session = problem_dcb->session;
|
||||
|
||||
bool close_dcb = true;
|
||||
|
||||
if (session == NULL || rses == NULL)
|
||||
{
|
||||
*succp = false;
|
||||
}
|
||||
else if (DCB_ROLE_CLIENT_HANDLER == problem_dcb->dcb_role)
|
||||
{
|
||||
*succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
CHK_SESSION(session);
|
||||
CHK_CLIENT_RSES(rses);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case ERRACT_NEW_CONNECTION:
|
||||
{
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
{
|
||||
close_dcb = false; /* With the assumption that if the router session is closed,
|
||||
* then so is the dcb.
|
||||
*/
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* If master has lost its Master status error can't be
|
||||
* handled so that session could continue.
|
||||
*/
|
||||
if (rses->rses_master_ref && rses->rses_master_ref->bref_dcb == problem_dcb &&
|
||||
!SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server))
|
||||
{
|
||||
SERVER *srv = rses->rses_master_ref->bref_backend->backend_server;
|
||||
backend_ref_t *bref;
|
||||
bref = get_bref_from_dcb(rses, problem_dcb);
|
||||
if (bref != NULL)
|
||||
{
|
||||
CHK_BACKEND_REF(bref);
|
||||
if (BREF_IS_WAITING_RESULT(bref))
|
||||
{
|
||||
bref_clear_state(bref, BREF_WAITING_RESULT);
|
||||
}
|
||||
bref_clear_state(bref, BREF_IN_USE);
|
||||
bref_set_state(bref, BREF_CLOSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("server %s:%d lost the "
|
||||
"master status but could not locate the "
|
||||
"corresponding backend ref.",
|
||||
srv->name, srv->port);
|
||||
}
|
||||
|
||||
if (rses->rses_config.rw_master_failure_mode != RW_FAIL_INSTANTLY &&
|
||||
(bref == NULL || !BREF_IS_WAITING_RESULT(bref)))
|
||||
{
|
||||
/** The failure of a master is not considered a critical
|
||||
* failure as partial functionality still remains. Reads
|
||||
* are allowed as long as slave servers are available
|
||||
* and writes will cause an error to be returned.
|
||||
*
|
||||
* If we were waiting for a response from the master, we
|
||||
* can't be sure whether it was executed or not. In this
|
||||
* case the safest thing to do is to close the client
|
||||
* connection. */
|
||||
*succp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!srv->master_err_is_logged)
|
||||
{
|
||||
MXS_ERROR("server %s:%d lost the "
|
||||
"master status. Readwritesplit "
|
||||
"service can't locate the master. "
|
||||
"Client sessions will be closed.",
|
||||
srv->name, srv->port);
|
||||
srv->master_err_is_logged = true;
|
||||
}
|
||||
*succp = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/**
|
||||
* This is called in hope of getting replacement for
|
||||
* failed slave(s). This call may free rses.
|
||||
*/
|
||||
*succp = handle_error_new_connection(inst, &rses, problem_dcb, errmsgbuf);
|
||||
}
|
||||
|
||||
dcb_close(problem_dcb);
|
||||
close_dcb = false;
|
||||
/* Free the lock if rses still exists */
|
||||
if (rses)
|
||||
{
|
||||
rses_end_locked_router_action(rses);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ERRACT_REPLY_CLIENT:
|
||||
{
|
||||
handle_error_reply_client(session, rses, problem_dcb, errmsgbuf);
|
||||
close_dcb = false;
|
||||
*succp = false; /*< no new backend servers were made available */
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ss_dassert(!true);
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (close_dcb)
|
||||
{
|
||||
dcb_close(problem_dcb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle an error reply for a client
|
||||
*
|
||||
@ -1694,18 +1718,39 @@ static void handle_error_reply_client(SESSION *ses, ROUTER_CLIENT_SES *rses,
|
||||
client_dcb = ses->client_dcb;
|
||||
spinlock_release(&ses->ses_lock);
|
||||
|
||||
/**
|
||||
* If bref exists, mark it closed
|
||||
*/
|
||||
if ((bref = get_bref_from_dcb(rses, backend_dcb)) != NULL)
|
||||
if (rses_begin_locked_router_action(rses))
|
||||
{
|
||||
CHK_BACKEND_REF(bref);
|
||||
bref_clear_state(bref, BREF_IN_USE);
|
||||
bref_set_state(bref, BREF_CLOSED);
|
||||
if (BREF_IS_WAITING_RESULT(bref))
|
||||
/**
|
||||
* If bref exists, mark it closed
|
||||
*/
|
||||
if ((bref = get_bref_from_dcb(rses, backend_dcb)) != NULL)
|
||||
{
|
||||
bref_clear_state(bref, BREF_WAITING_RESULT);
|
||||
CHK_BACKEND_REF(bref);
|
||||
|
||||
if (BREF_IS_IN_USE(bref))
|
||||
{
|
||||
bref_clear_state(bref, BREF_IN_USE);
|
||||
bref_set_state(bref, BREF_CLOSED);
|
||||
if (BREF_IS_WAITING_RESULT(bref))
|
||||
{
|
||||
bref_clear_state(bref, BREF_WAITING_RESULT);
|
||||
}
|
||||
|
||||
dcb_close(backend_dcb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// All dcbs should be associated with a backend reference.
|
||||
ss_dassert(!true);
|
||||
}
|
||||
|
||||
rses_end_locked_router_action(rses);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The session has already been closed, hence the dcb has been
|
||||
// closed as well.
|
||||
}
|
||||
|
||||
if (sesstate == SESSION_STATE_ROUTER_READY)
|
||||
|
Reference in New Issue
Block a user