Merge branch '2.1' into develop
This commit is contained in:
commit
b3e8ea9b5a
@ -190,6 +190,9 @@ add_subdirectory(include/maxscale)
|
||||
if(NOT WITHOUT_MAXADMIN)
|
||||
add_subdirectory(client)
|
||||
endif()
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
# Generate text versions of some documents
|
||||
execute_process(COMMAND perl ${CMAKE_SOURCE_DIR}/Documentation/format.pl
|
||||
@ -224,6 +227,9 @@ 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)
|
||||
|
||||
# For devel package, these files are put to the base folder
|
||||
install_header(${CMAKE_SOURCE_DIR}/COPYRIGHT devel)
|
||||
install_header(${CMAKE_SOURCE_DIR}/LICENSE.TXT devel)
|
||||
|
||||
# Install startup scripts and ldconfig files
|
||||
if(WITH_SCRIPTS)
|
||||
|
566
Documentation/Design-Documents/Plugin-development-guide.md
Normal file
566
Documentation/Design-Documents/Plugin-development-guide.md
Normal file
@ -0,0 +1,566 @@
|
||||
# MariaDB MaxScale plugin development guide
|
||||
|
||||
This document and the attached example code explain prospective plugin
|
||||
developers the MariaDB MaxScale plugin API and also present and explain some
|
||||
best practices and possible pitfalls in module development. We predict that
|
||||
*filters* and *routers* are the module types developers are most likely to work
|
||||
on, so the APIs of these two are discussed in detail.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
* [Module categories](#module-categories)
|
||||
* [Common definitions and headers](#common-definitions-and-headers)
|
||||
1. [Module information container](#module-information-container)
|
||||
* [Module API](#module-api)
|
||||
1. [Overview](#overview)
|
||||
* [General module management](#general-module-management)
|
||||
* [Protocol](#protocol)
|
||||
* [Authenticator](#authenticator)
|
||||
* [Filter and Router](#filter-and-router)
|
||||
* [Monitor](#monitor)
|
||||
* [Compiling, installing and running](#compiling-installing-and-running)
|
||||
1. [Hands-on example: RoundRobinRouter](#hands-on-example-roundrobinrouter)
|
||||
* [Summary and conclusion](#summary-and-conclusion)
|
||||
|
||||
## Introduction
|
||||
|
||||
MariaDB MaxScale is designed to be an extensible program. Much, if not most, of
|
||||
the actual processing is done by plugin modules. Plugins receive network data,
|
||||
process it and relay it to its destination. The MaxScale core loads plugins,
|
||||
manages client sessions and threads and, most importantly, offers a selection of
|
||||
functions for the plugins to call upon. This collection of functions is called
|
||||
the *MaxScale Public Interface* or just **MPI** for short.
|
||||
|
||||
The plugin modules are shared libraries (.so-files) implementing a set of
|
||||
interface functions, the plugin API. Different plugin types have different APIs,
|
||||
although there are similarities. The MPI is a set of C and C++ header files,
|
||||
from which the module code includes the ones required. MariaDB MaxScale is
|
||||
written in C/C++ and the plugin API is in pure C. Although it is possible
|
||||
to write plugins in any language capable of exposing a C interface and
|
||||
dynamically binding to the core program, in this document we assume plugin
|
||||
modules are written in C++.
|
||||
|
||||
The *RoundRobinRouter* is a practical example of a simple router plugin. The
|
||||
RoundRobinRouter is compiled, installed and ran in [section
|
||||
5.1](#hands-on-example:-roundrobinrouter). The source for the router is located
|
||||
in the `examples`-folder.
|
||||
|
||||
## Module categories
|
||||
|
||||
This section lists all the module types and summarises their core tasks. The
|
||||
modules are listed in the order a client packet would typically travel through.
|
||||
For more information about a particular module type, see the corresponding
|
||||
folder in `MaxScale/Documentation/`, located in the main MariaDB MaxScale
|
||||
repository.
|
||||
|
||||
**Protocol** modules implement I/O between clients and MaxScale, and between
|
||||
MaxScale and backend servers. Protocol modules read and write to socket
|
||||
descriptors using raw I/O functions provided by the MPI, and implement
|
||||
protocol-specific I/O functions to be used through a common interface. The
|
||||
Protocol module API is defined in `protocol.h`. Currently, the only implemented
|
||||
database protocol is *MySQL*. Other protocols currently in use include *HTTPD*
|
||||
and *maxscaled*, which are used by the MaxInfo and MaxAdmin modules.
|
||||
|
||||
**Authenticator** modules retrieve user account information from the backend
|
||||
databases, store it and use it to authenticate connecting clients. MariaDB
|
||||
MaxScale includes authenticators for MySQL (normal and GSSApi). The
|
||||
authenticator API is defined in `authenticator.h`.
|
||||
|
||||
**Filter** modules process data from clients before routing. A data buffer may
|
||||
travel through multiple filters before arriving in a router. For a data buffer
|
||||
going from a backend to the client, the router receives it first and the
|
||||
filters receive it in reverse order. MaxScale includes a healthly selection of
|
||||
filters ranging from logging, overwriting query data and caching. The filter
|
||||
API is defined in `filter.h`.
|
||||
|
||||
**Router** modules route packets from the last filter in the filter chain to
|
||||
backends and reply data from backends to the last filter. The routing decisions
|
||||
may be based on a variety of conditions; typically packet contents and backend
|
||||
status are the most significant factors. Routers are often used for load
|
||||
balancing, dividing clients and even individual queries between backends.
|
||||
Routers use protocol functions to write to backends, making them somewhat
|
||||
protocol-agnostic. The router API is defined in `router.h`.
|
||||
|
||||
**Monitor** modules do not process data flowing through MariaDB MaxScale, but
|
||||
support the other modules in their operation by updating the status of the
|
||||
backend servers. Monitors are ran in their own threads to minimize
|
||||
interference to the worker threads. They periodically connect to all their
|
||||
assigned backends, query their status and write the results in global structs.
|
||||
The monitor API is defined in `monitor.h`.
|
||||
|
||||
## Common definitions and headers
|
||||
|
||||
Generally, most type definitions, macros and functions exposed by the MPI to be
|
||||
used by modules are prefixed with **MXS**. This should avoid name collisions
|
||||
in the case a module includes many symbols from the MPI.
|
||||
|
||||
Every compilation unit in a module should begin with `#define MXS_MODULE_NAME
|
||||
"<name>"`. This definition will be used by log macros for clarity, prepending
|
||||
`<name>` to every log message. Next, the module should
|
||||
`#include <maxscale/cppdefs.h>` (for C++) or `#include <maxscale/cdefs.h>` (for
|
||||
C). These headers contain compilation environment dependent definitions and
|
||||
global constants, and include some generally useful headers. Including one of
|
||||
them first in every source file enables later global redefinitions across all
|
||||
MaxScale modules. If your module is composed of multiple source files, the above
|
||||
should be placed to a common header file included in the beginning of the source
|
||||
files. The file with the module API definition should also include the header
|
||||
for the module type, e.g. `filter.h`.
|
||||
|
||||
Other common MPI header files required by most modules are listed in the table
|
||||
below.
|
||||
|
||||
Header | Contents
|
||||
-------|----------
|
||||
`alloc.h` | Malloc, calloc etc. replacements
|
||||
`buffer.h` | Packet buffer management
|
||||
`config.h` | Configuration settings
|
||||
`dcb.h` | I/O using descriptor control blocks
|
||||
`debug.h` | Debugging macros
|
||||
`modinfo.h` | Module information structure
|
||||
`server.h` | Backend server information
|
||||
`service.h` | Service definition
|
||||
`session.h` | Client session definition
|
||||
`logmanager.h` | Logging macros and functions
|
||||
|
||||
### Module information container
|
||||
|
||||
A module must implement the `MXS_CREATE_MODULE()`-function, which returns a
|
||||
pointer to a `MXS_MODULE`-structure. This function is called by the module
|
||||
loader during program startup. `MXS_MODULE` (type defined in `modinfo.h`)
|
||||
contains function pointers to further module entrypoints, miscellaneous
|
||||
information about the module and the configuration parameters accepted by the
|
||||
module. This function must be exported without C++ name mangling, so in C++ code
|
||||
it should be defined `extern "C"`.
|
||||
|
||||
The information container describes the module in general and is constructed
|
||||
once during program excecution. A module may have multiple *instances* with
|
||||
different values for configuration parameters. For example, a filter module can
|
||||
be used with two different configurations in different services (or even in the
|
||||
same service). In this case the loader uses the same module information
|
||||
container for both but creates two module instances.
|
||||
|
||||
The MariaDB MaxScale configuration file `maxscale.cnf` is parsed by the core.
|
||||
The core also checks that all the defined parameters are of the correct type for
|
||||
the module. For this, the `MXS_MODULE`-structure includes a list of parameters
|
||||
accepted by the module, defining parameter names, types and default values. In
|
||||
the actual module code, parameter values should be extracted using functions
|
||||
defined in `config.h`.
|
||||
|
||||
## Module API
|
||||
|
||||
### Overview
|
||||
|
||||
This section explains some general concepts encountered when implementing a
|
||||
module API. For more detailed information, see the module specific subsection,
|
||||
header files or the doxygen documentation.
|
||||
|
||||
Modules with configuration data define an *INSTANCE* object, which is created by
|
||||
the module code in a `createInstance`-function or equivalent. The instance
|
||||
creation function is called during MaxScale startup, usually when creating
|
||||
services. MaxScale core holds the module instance data in the
|
||||
`SERVICE`-structure (or other higher level construct) and gives it as a
|
||||
parameter when calling functions from the module in question. The instance
|
||||
structure should contain all non-client-specific information required by the
|
||||
functions of the module. The core does not know what the object contains (since
|
||||
it is defined by the module itself), nor will it modify the pointer or the
|
||||
referenced object in any way.
|
||||
|
||||
Modules dealing with client-specific data require a *SESSION* object for every
|
||||
client. As with the instance data, the definition of the module session
|
||||
structure is up to the module writer and MaxScale treats it as an opaque type.
|
||||
Usually the session contains status indicators and any resources required by the
|
||||
client. MaxScale core has its own `MXS_SESSION` object, which tracks a variety
|
||||
of client related information. The `MXS_SESSION` is given as a parameter to
|
||||
module-specific session creation functions and is required for several typical
|
||||
operations such as connecting to backends.
|
||||
|
||||
Descriptor control blocks (`DCB`), are generalized I/O descriptor types. DCBs
|
||||
store the file descriptor, state, remote address, username, session, and other
|
||||
data. DCBs are created whenever a new socket is created. Typically this happens
|
||||
when a new client connects or MaxScale connects the client session to backend
|
||||
servers. The module writer should use DCB handling functions provided by the MPI
|
||||
to manage connections instead of calling general networking libraries. This
|
||||
ensures that I/O is handled asynchronously by epoll. In general, module code
|
||||
should avoid blocking I/O, *sleep*, *yield* or other potentially costly
|
||||
operations, as the same thread is typically used for many client sessions.
|
||||
|
||||
Network data such as client queries and backend replies are held in a buffer
|
||||
container called `GWBUF`. Multiple GWBUFs can form a linked list with type
|
||||
information and properties in each GWBUF-node. Each node includes a pointer to a
|
||||
reference counted shared buffer (`SHARED_BUF`), which finally points to a slice
|
||||
of the actual data. In effect, multiple GWBUF-chains can share some data while
|
||||
keeping some parts private. This construction is meant to minimize the need for
|
||||
data copying and makes it easy to append more data to partially received data
|
||||
packets. Plugin writers should use the MPI to manipulate GWBUFs. For more
|
||||
information on the GWBUF, see [Filter and Router](#filter-and-router).
|
||||
|
||||
### General module management
|
||||
|
||||
```java
|
||||
int process_init()
|
||||
void process_finish()
|
||||
int thread_init()
|
||||
void thread_finish()
|
||||
```
|
||||
|
||||
These four functions are present in all `MXS_MODULE` structs and are not part of
|
||||
the API of any individual module type. `process_init` and `process_finish` are
|
||||
called by the module loader right after loading a module and just before
|
||||
MaxScale terminates, respectively. Usually, these can be set to null in
|
||||
`MXS_MODULE` unless the module needs some general initializations before
|
||||
creating any instances. `thread_init` and `thread_finish` are thread-specific
|
||||
equivalents.
|
||||
|
||||
```java
|
||||
void diagnostics(INSTANCE *instance, DCB *dcb)
|
||||
```
|
||||
|
||||
A diagnostics printing routine is present in nearly all module types, although
|
||||
with varying signatures. This entrypoint should print various statistics and
|
||||
status information about the module instance `instance` in string form. The
|
||||
target of the printing is the given DCB, and printing should be implemented by
|
||||
calling `dcb_printf`. The diagnostics function is used by the *MaxInfo* and
|
||||
*MaxAdmin* features.
|
||||
|
||||
### Protocol
|
||||
|
||||
```java
|
||||
int32_t read(struct dcb *)
|
||||
int32_t write(struct dcb *, GWBUF *)
|
||||
int32_t write_ready(struct dcb *)
|
||||
int32_t error(struct dcb *)
|
||||
int32_t hangup(struct dcb *)
|
||||
int32_t accept(struct dcb *)
|
||||
int32_t connect(struct dcb *, struct server *, struct session *)
|
||||
int32_t close(struct dcb *)
|
||||
int32_t listen(struct dcb *, char *)
|
||||
int32_t auth(struct dcb *, struct server *, struct session *, GWBUF *)
|
||||
int32_t session(struct dcb *, void *)
|
||||
char auth_default()
|
||||
int32_t connlimit(struct dcb *, int limit)
|
||||
```
|
||||
|
||||
Protocol modules are laborous to implement due to their low level nature. Each
|
||||
DCB maintains pointers to the correct protocol functions to be used with it,
|
||||
allowing the DCB to be used in a protocol-independent manner.
|
||||
|
||||
`read`, `write_ready`, `error` and `hangup` are *epoll* handlers for their
|
||||
respective events. `write` implements writing and is usually called in a router
|
||||
module. `accept` is a listener socker handler. `connect` is used during session
|
||||
creation when connecting to backend servers. `listen` creates a listener socket.
|
||||
`close` closes a DCB created by `accept`, `connect` or `listen`.
|
||||
|
||||
In the ideal case modules other than the protocol modules themselves should not
|
||||
be protocol-specific. This is currently difficult to achieve, since many actions
|
||||
in the modules are dependent on protocol-speficic details. In the future,
|
||||
protocol modules may be expanded to implement a generic query parsing and
|
||||
information API, allowing filters and routers to be used with different SQL
|
||||
variants.
|
||||
|
||||
### Authenticator
|
||||
|
||||
```java
|
||||
void* initialize(char **options)
|
||||
void* create(void* instance)
|
||||
int extract(struct dcb *, GWBUF *)
|
||||
bool connectssl(struct dcb *)
|
||||
int authenticate(struct dcb *)
|
||||
void free(struct dcb *)
|
||||
void destroy(void *)
|
||||
int loadusers(struct servlistener *)
|
||||
void diagnostic(struct dcb*, struct servlistener *)
|
||||
int reauthenticate(struct dcb *, const char *user, uint8_t *token,
|
||||
size_t token_len, uint8_t *scramble, size_t scramble_len,
|
||||
uint8_t *output, size_t output_len);
|
||||
```
|
||||
|
||||
Authenticators must communicate with the client or the backends and implement
|
||||
authentication. The authenticators can be divided to client and backend modules,
|
||||
although the two types are linked and must be used together. Authenticators are
|
||||
also dependent on the protocol modules.
|
||||
|
||||
### Filter and Router
|
||||
|
||||
Filter and router APIs are nearly identical and are presented together. Since
|
||||
these are the modules most likely to be implemented by plugin developers, their
|
||||
APIs are discussed in more detail.
|
||||
|
||||
```java
|
||||
INSTANCE* createInstance(SERVICE* service, char** options)
|
||||
void destroyInstance(INSTANCE* instance)
|
||||
```
|
||||
|
||||
`createInstance` should read the `options` and initialize an instance object for
|
||||
use with `service`. Often, simply saving the configuration values to fields is
|
||||
enough. `destroyInstance` is called when the service using the module is
|
||||
deallocated. It should free any resources claimed by the instance. All sessions
|
||||
created by this instance should be closed before calling the destructor.
|
||||
|
||||
```java
|
||||
SESSION* newSession(INSTANCE* instance, MXS_SESSION* mxs_session)
|
||||
void closeSession(INSTANCE* instance, SESSION* session)
|
||||
void freeSession(INSTANCE* instance, SESSION* session)
|
||||
```
|
||||
|
||||
These functions manage sessions. `newSession` should allocate a router or filter
|
||||
session attached to the client session represented by `mxs_session`. MaxScale
|
||||
will pass the returned pointer to all the API entrypoints that process user data
|
||||
for the particular client. `closeSession` should close connections the session
|
||||
has opened and release any resources specific to the served client. The
|
||||
*SESSION* structure allocated in `newSession` should not be deallocated by
|
||||
`closeSession` but in `freeSession`. These two are called in succession
|
||||
by the core.
|
||||
|
||||
```java
|
||||
int routeQuery(INSTANCE *instance, SESSION session, GWBUF* queue) void
|
||||
clientReply(INSTANCE* instance, SESSION session, GWBUF* queue, DCB *backend_dcb)
|
||||
uint64_t getCapabilities(INSTANCE* instance)
|
||||
```
|
||||
|
||||
`routeQuery` is called for client requests which should be routed to backends,
|
||||
and `clientReply` for backend reply packets which should be routed to the
|
||||
client. For some modules, MaxScale itself is the backend. For filters, these can
|
||||
be NULL, in which case the filter will be skipped for that packet type.
|
||||
|
||||
`routeQuery` is often the most complicated function in a router, as it
|
||||
implements the routing logic. It typically considers the client request `queue`,
|
||||
the router settings in `instance` and the session state in `session` when making
|
||||
a routing decision. For filters aswell, `routeQuery` typically implements the
|
||||
main logic, although the routing target is constant. For router modules,
|
||||
`routeQuery` should send data forward with `dcb->func.write()`. Filters should
|
||||
directly call `routeQuery` for the next filter or router in the chain.
|
||||
|
||||
`clientReply` processes data flowing from backend back to client. For routers,
|
||||
this function is often much simpler than `routeQuery`, since there is only one
|
||||
client to route to. Depending on the router, some packets may not be routed to
|
||||
the client. For example, if a client query was routed to multiple backends,
|
||||
MaxScale will receive multiple replies while the client only expects one.
|
||||
Routers should pass the reply packet to the last filter in the chain (reversed
|
||||
order) using the macro `MXS_SESSION_ROUTE_REPLY`. Filters should call the
|
||||
`clientReply` of the previous filter in the chain. There is no need for filters
|
||||
to worry about being the first filter in the chain, as this is handled
|
||||
transparently by the session creation routine.
|
||||
|
||||
Application data is not always received in complete packets from the network
|
||||
stack. How partial packets are handled by the receiving protocol module depends
|
||||
on the attached filters and the router, communicated by their
|
||||
`getCapabilities`-functions. `getCapabilities` should return a bitfield
|
||||
resulting from ORring the individual capabilities. `routing.h` lists the allowed
|
||||
capability flags.
|
||||
|
||||
If a router or filter sets no capabilities, `routeQuery` or `clientReply` may be
|
||||
called to route partial packets. If the routing logic does not require any
|
||||
information on the contents of the packets or even tracking the number of
|
||||
packets, this may be fine. For many cases though, receiving a data packet in a
|
||||
complete GWBUF chain or in one contiguos GWBUF is required. The former can be
|
||||
requested by `getCapabilities` returning *RCAP_TYPE_STMT*, the latter by
|
||||
*RCAP_TYPE_CONTIGUOUS*. Separate settings exist for queries and replies. For
|
||||
replies, an additional value, *RCAP_TYPE_RESULTSET_OUTPUT* is defined. This
|
||||
requests the protocol module to gather partial results into one result set.
|
||||
Enforcing complete packets will delay processing, since the protocol module will
|
||||
have to wait for the entire data packet to arrive before sending it down the
|
||||
processing chain.
|
||||
|
||||
```java
|
||||
void handleError(INSTANCE* instance,SESSION* session, GWBUF* errmsgbuf,
|
||||
DCB* problem_dcb, mxs_error_action_t action, bool* succp);
|
||||
```
|
||||
|
||||
This router-only entrypoint is called if `routeQuery` returns an error value or
|
||||
if an error occurs in one of the connections listened to by the session. The
|
||||
steps an error handler typically takes depend on the nature of the `problem_dcb`
|
||||
and the error encountered. If `problem_dcb` is a client socket, then the session
|
||||
is lost and should be closed. The error handler should not do this by itself and
|
||||
just report the failure by setting `succp` to false. If `problem_dcb` is a
|
||||
backend socket, then the error handler should try to connect to another backend
|
||||
if the routing logic allows this. If the error is simply a failed authentication
|
||||
on the backend, then it is usually best to send the message directly to the
|
||||
client and close the session.
|
||||
|
||||
### Monitor
|
||||
|
||||
```java
|
||||
MONITOR* startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER *params)
|
||||
void stopMonitor(MXS_MONITOR *monitor)
|
||||
void diagnostics(DCB *, const MXS_MONITOR *)
|
||||
```
|
||||
|
||||
Monitor modules typically run a repeated monitor routine with a used defined
|
||||
interval. The `MXS_MONITOR` is a standard monitor definition used for all
|
||||
monitors and contains a void pointer for storing module specific data.
|
||||
`startMonitor` should create a new thread for itself using functions in the MPI
|
||||
and have it regularly run a monitor loop. In the beginning of every monitor
|
||||
loop, the monitor should lock the `SERVER`-structures of its servers. This
|
||||
prevents any administrative action from interfering with the monitor during its
|
||||
pass.
|
||||
|
||||
## Compiling, installing and running
|
||||
|
||||
The requirements for compiling a module are:
|
||||
* The public headers (MPI)
|
||||
* A compatible compiler, typically GCC
|
||||
* Libraries required by the public headers
|
||||
|
||||
Some of the public header files themselves include headers from other libraries.
|
||||
These libraries need to be installed and it may be required to point out their
|
||||
location to gcc. Some of the more commonly required libraries are:
|
||||
* *MySQL Connector-C*, used by the MySQL protocol module
|
||||
* *pcre2 regular expressions* (libpcre2-dev), used for example by the header
|
||||
`modutil.h`
|
||||
|
||||
After all dependencies are accounted for, the module should compile with a
|
||||
command similar to
|
||||
```
|
||||
gcc -I /usr/local/include/mariadb -shared -fPIC -g -o libmymodule.so mymodule.cpp
|
||||
```
|
||||
Large modules composed of several source files and using additional libraries
|
||||
may require a more complicated compilation scheme, but that is outside the scope
|
||||
of this document. The result of compiling a plugin should be a single shared
|
||||
library file.
|
||||
|
||||
The compiled .so-file needs to be copied to the MaxScale library folder, which
|
||||
is `/usr/local/lib/maxscale` by default. MaxScale expects the filename to be
|
||||
`lib<name>.so`, where `<name>` must match the module name given in the
|
||||
configuration file.
|
||||
|
||||
### Hands-on example: RoundRobinRouter
|
||||
|
||||
In this example, the RoundRobinRouter is compiled, installed and tested. The
|
||||
software environment this section was written and tested is listed below. Any
|
||||
recent Linux setup should be applicaple.
|
||||
|
||||
* Linux Mint 18
|
||||
* gcc 5.4.0, glibc 2.23
|
||||
* MariaDB MaxScale 2.1.0 debug build (binaries in `usr/local/maxscale`, modules in
|
||||
`/usr/local/lib/maxscale`)
|
||||
* MariaDB Connector-C 2.3.2 (installed to `/usr/local/lib/mariadb`, headers in
|
||||
`/usr/local/include/mariadb`)
|
||||
* `roundrobinrouter.cpp` in the current directory
|
||||
* MaxScale plugin development headers (in `usr/include/maxscale`)
|
||||
|
||||
**Step 1** Compile RoundRobinRouter with `$gcc -I /usr/local/include/mariadb
|
||||
-shared -fPIC -g -o libroundrobinrouter.so roundrobinrouter.cpp`.
|
||||
Assuming all headers were found, the shared library `libroundrobinrouter.so`
|
||||
is produced.
|
||||
|
||||
**Step 2** Copy the compiled module to the MaxScale module directory: `$sudo cp
|
||||
libroundrobinrouter.so /usr/local/lib/maxscale`.
|
||||
|
||||
**Step 3** Modify the MaxScale configuration file to use the RoundRobinRouter as
|
||||
a router. Example service and listener definitions are below. The *servers*
|
||||
and *write_backend*-lines should be configured according to the actual backend
|
||||
configuration.
|
||||
|
||||
```
|
||||
[RR Service]
|
||||
type=service
|
||||
router=roundrobinrouter
|
||||
servers=LocalMaster1,LocalSlave1,LocalSlave2
|
||||
user=maxscale
|
||||
passwd=maxscale
|
||||
filters=MyLogFilter1
|
||||
max_backends=10
|
||||
write_backend=LocalMaster1
|
||||
print_on_routing=true
|
||||
dummy_setting=two
|
||||
|
||||
[RR Listener]
|
||||
type=listener
|
||||
service=RR Service
|
||||
protocol=MySQLClient
|
||||
port=4009
|
||||
```
|
||||
|
||||
**Step 4** Start MaxScale: `$ maxscale -d`. Output:
|
||||
```
|
||||
MariaDB Corporation MaxScale 2.1.0 Mon Feb 20 17:22:18 2017
|
||||
------------------------------------------------------
|
||||
Info : MaxScale will be run in the terminal process.
|
||||
See the log from the following log files :
|
||||
|
||||
Configuration file : /etc/maxscale.cnf
|
||||
Log directory : /var/log/maxscale
|
||||
Data directory : /var/lib/maxscale
|
||||
Module directory : /usr/local/lib/maxscale
|
||||
Service cache : /var/cache/maxscale
|
||||
```
|
||||
|
||||
**Step 5** Test with a MySQL client. The RoundRobinRouter has been tested with both a command line and a GUI client. With `DEBUG_RRROUTER` defined and `print_on_routing` enabled, the `/var/log/maxscale/maxscale.log` file will report nearly every action taken by the router.
|
||||
|
||||
```
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] Creating instance.
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] Settings read:
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] 'max_backends': 10
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] 'write_backend': 0xf0ce70
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] 'print_on_routing': 1
|
||||
2017-02-21 10:37:23 notice : [RoundRobinRouter] 'dummy_setting': 2
|
||||
.
|
||||
.
|
||||
.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Session with 4 connections created.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: SHOW VARIABLES WHERE Variable_name in ('max_allowed_packet', 'system_time_zone', 'time_zone', 'sql_mode')
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 110u to backend 'LocalMaster1'.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: set session autocommit=1,sql_mode='NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES'
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 103u to 4 backends.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: SET @ApplicationName='DBeaver 3.8.5 - Main'
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 48u to 4 backends.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: select @@lower_case_table_names
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 36u to backend 'LocalSlave1'.
|
||||
2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client.
|
||||
```
|
||||
|
||||
**Step 5** Connect with MaxAdmin, print diagnostics and call a custom command.
|
||||
```
|
||||
$sudo maxadmin
|
||||
MaxScale> show service "RR Service"
|
||||
Service: RR Service
|
||||
Router: roundrobinrouter
|
||||
State: Started
|
||||
Queries routed successfully: 37
|
||||
Failed routing attempts: 0
|
||||
Client replies routed: 38
|
||||
Started: Tue Feb 21 11:52:08 2017
|
||||
Root user access: Disabled
|
||||
Filter chain: MyLogFilter1
|
||||
Backend databases:
|
||||
127.0.0.1:3001 Protocol: MySQLBackend Name: LocalMaster1
|
||||
127.0.0.1:3002 Protocol: MySQLBackend Name: LocalSlave1
|
||||
127.0.0.1:3003 Protocol: MySQLBackend Name: LocalSlave2
|
||||
Total connections: 2
|
||||
Currently connected: 2
|
||||
MaxScale> call command rrrouter test_command "one" 0
|
||||
```
|
||||
|
||||
The result of the `test_command "one" 0` is printed to the terminal MaxScale is
|
||||
running in:
|
||||
```
|
||||
RoundRobinRouter wishes the Admin a good day.
|
||||
The module got 2 arguments.
|
||||
Argument 0: type 'string' value 'one'
|
||||
Argument 1: type 'boolean' value 'false'
|
||||
```
|
||||
|
||||
## Summary and conclusion
|
||||
|
||||
Plugins offer a way to extend MariaDB MaxScale whenever the standard modules are
|
||||
found insufficient. The plugins need only implement a set API, can be
|
||||
independently compiled and installation is simply a file copy with some
|
||||
configuration file modifications.
|
||||
|
||||
Out of the different plugin types, filters are the easiest to implement. They
|
||||
work independently and have few requirements. Protocol and authenticator modules
|
||||
require indepth knowledge of the database protocol they implement. Router module
|
||||
complexity depends on the routing logic requirements.
|
||||
|
||||
The provided RoundRobinRouter example code should serve as a valid starting
|
||||
point for both filters and routers. Studying the MaxScale Public Interface
|
||||
headers to get a general idea of what services the core provides for plugins,
|
||||
is also highly recommeded.
|
||||
|
||||
Lastly, MariaDB MaxScale is an open-source project, so code contributions can be
|
||||
accepted if they fulfill the
|
||||
[requirements](https://github.com/mariadb-corporation/MaxScale/wiki/Contributing).
|
@ -128,6 +128,7 @@ document.
|
||||
- [Binlog Router Design (in development)](http://mariadb-corporation.github.io/MaxScale/Design-Documents/binlog-router-html-docs)
|
||||
- [DCB States (to be replaced in StarUML)](Design-Documents/DCB-States.pdf)
|
||||
- [Schema Sharding Router Technical Documentation](Design-Documents/SchemaRouter-technical.md)
|
||||
- [Plugin development guide](Design-Documents/Plugin-development-guide.md)
|
||||
|
||||
## Earlier Release Notes
|
||||
|
||||
@ -145,4 +146,3 @@ document.
|
||||
- [MariaDB MaxScale 0.7 Release Notes](Release-Notes/MaxScale-0.7-Release-Notes.md)
|
||||
- [MariaDB MaxScale 0.6 Release Notes](Release-Notes/MaxScale-0.6-Release-Notes.md)
|
||||
- [MariaDB MaxScale 0.5 Release Notes](Release-Notes/MaxScale-0.5-Release-Notes.md)
|
||||
|
||||
|
@ -3,12 +3,19 @@
|
||||
This filter was introduced in MariaDB MaxScale 2.1.
|
||||
|
||||
## Overview
|
||||
_Note that the cache is still experimental and that non-backward compatible
|
||||
changes may be made._
|
||||
|
||||
The cache filter is a simple cache that is capable of caching the result of
|
||||
SELECTs, so that subsequent identical SELECTs are served directly by MaxScale,
|
||||
without the queries being routed to any server.
|
||||
|
||||
_Note that the cache is still experimental and that non-backward compatible
|
||||
changes may be made._
|
||||
SELECTs using the following functions will not be cached: `BENCHMARK`,
|
||||
`CONNECTION_ID`, `CONVERT_TZ`, `CURDATE`, `CURRENT_DATE`, `CURRENT_TIMESTAMP`,
|
||||
`CURTIME`, `DATABASE`, `ENCRYPT`, `FOUND_ROWS`, `GET_LOCK`, `IS_FREE_LOCK`,
|
||||
`IS_USED_LOCK`, `LAST_INSERT_ID`, `LOAD_FILE`, `LOCALTIME`, `LOCALTIMESTAMP`,
|
||||
`MASTER_POS_WAIT`, `NOW`, `RAND`, `RELEASE_LOCK`, `SESSION_USER`, `SLEEP`,
|
||||
`SYSDATE`, `SYSTEM_USER`, `UNIX_TIMESTAMP`, `USER`, `UUID`, `UUID_SHORT`.
|
||||
|
||||
Note that installing the cache causes all statements to be parsed. The
|
||||
implication of that is that unless statements _already_ need to be parsed,
|
||||
@ -26,40 +33,24 @@ Currently there is **no** cache invalidation, apart from _time-to-live_.
|
||||
Resultsets of prepared statements are **not** cached.
|
||||
|
||||
### Transactions
|
||||
The cache will be used and populated **only** if there is _no_ on-going
|
||||
transaction or if an on-going transaction is _explicitly_ read-only (that is,
|
||||
`START TRANSACTION READ ONLY`).
|
||||
The cache will be used and populated in the following circumstances:
|
||||
|
||||
* There is _no_ explicit transaction active, that is, _autocommit_ is used,
|
||||
* there is an _explicitly_ read-only transaction (that is,`START TRANSACTION
|
||||
READ ONLY`) active, or
|
||||
* there is a transaction active and _no_ statement that modify the database
|
||||
has been performed.
|
||||
|
||||
In practice, the last bullet point basically means that if a transaction has
|
||||
been started with `BEGIN` or `START TRANSACTION READ WRITE`, then the cache
|
||||
will be used and populated until the first `UPDATE`, `INSERT` or `DELETE`
|
||||
statement is encountered.
|
||||
|
||||
### Variables
|
||||
The cache key is effectively the entire _SELECT_ statement. However, the
|
||||
value of any variables used in the select is **not** considered. For instance,
|
||||
if a variable is used in the _WHERE_ clause of the select, a subsequent
|
||||
identical select will return the wrong result, if the value of the variable
|
||||
has been changed in between.
|
||||
```
|
||||
MySQL [testdb]> create table tbl (a int, b int);
|
||||
MySQL [testdb]> insert into tbl values (1, 2), (3, 4);
|
||||
|
||||
MySQL [testdb]> set @var=2;
|
||||
MySQL [testdb]> select a from tbl where b=@var;
|
||||
+------+
|
||||
| a |
|
||||
+------+
|
||||
| 1 |
|
||||
+------+
|
||||
|
||||
MySQL [testdb]> set @var=4;
|
||||
MySQL [testdb]> select a from tbl where b=@var;
|
||||
+------+
|
||||
| a |
|
||||
+------+
|
||||
| 1 |
|
||||
+------+
|
||||
```
|
||||
In the second case, the correct answer would have been `3` and not `1`.
|
||||
If user or system variables are used in the _SELECT_ statement, the result
|
||||
will not be cached.
|
||||
|
||||
### Security
|
||||
|
||||
The cache is **not** aware of grants.
|
||||
|
||||
The implication is that unless the cache has been explicitly configured
|
||||
|
@ -138,6 +138,7 @@ initiated, the following conditions must have been met:
|
||||
- The monitor has repeatedly failed to connect to the failed servers
|
||||
- There is only one running server among the monitored servers
|
||||
- @@read_only is not enabled on the last running server
|
||||
- The last running server is not configured as a slave
|
||||
|
||||
When these conditions are met, the monitor assigns the last remaining server the
|
||||
master status and puts all other servers into maintenance mode. This is done to
|
||||
@ -167,12 +168,12 @@ milliseconds, the value of _failcount_ must be 10.
|
||||
### `failover_recovery`
|
||||
|
||||
Allow recovery after failover. This feature takes a boolean parameter is
|
||||
disabled by default.
|
||||
enabled by default.
|
||||
|
||||
Normally if a failover has been triggered and the last remaining server is
|
||||
chosen as the master, the monitor will set all of the failed servers into
|
||||
maintenance mode. When this option is enabled, the failed servers are allowed to
|
||||
rejoin the cluster.
|
||||
When this parameter is disabled, if a failover has been triggered and the last
|
||||
remaining server is chosen as the master, the monitor will set all of the failed
|
||||
servers into maintenance mode. When this option is enabled, the failed servers
|
||||
are allowed to rejoin the cluster.
|
||||
|
||||
This option should be enabled when failover in MaxScale is used in conjunction
|
||||
with an external agent that resets the slave status for new master servers. One
|
||||
|
@ -8,6 +8,32 @@ release 2.1.0.
|
||||
For any problems you encounter, please consider submitting a bug
|
||||
report at [Jira](https://jira.mariadb.org).
|
||||
|
||||
## Changed Features
|
||||
|
||||
### Cache
|
||||
|
||||
The cache will now _also_ be used and populated in a transaction that is
|
||||
_not_ explicitly read only, but only until the first statement that modifies
|
||||
the database is encountered.
|
||||
|
||||
## Dropped Features
|
||||
|
||||
### MaxAdmin
|
||||
|
||||
The following deprecated commands have been removed:
|
||||
|
||||
* `enable log [debug|trace|message]`
|
||||
* `disable log [debug|trace|message]`
|
||||
* `enable sessionlog [debug|trace|message]`
|
||||
* `disable sessionlog [debug|trace|message]`
|
||||
|
||||
The following commands have been deprecated:
|
||||
|
||||
* `enable sessionlog-priority <session-id> [debug|info|notice|warning]`
|
||||
* `disable sessionlog-priority <session-id> [debug|info|notice|warning]`
|
||||
|
||||
The commands can be issued, but have no effect.
|
||||
|
||||
## New Features
|
||||
|
||||
### Failover Recovery for MySQL Monitor
|
||||
|
@ -100,6 +100,25 @@ The `LEAST_GLOBAL_CONNECTIONS` and `LEAST_ROUTER_CONNECTIONS` use the connection
|
||||
|
||||
`LEAST_BEHIND_MASTER` does not take server weights into account when choosing a server.
|
||||
|
||||
#### Interaction Between `slave_selection_criteria` and `max_slave_connections`
|
||||
|
||||
Depending on the value of `max_slave_connections`, the slave selection criteria
|
||||
behave in different ways. Here are a few example cases of how the different
|
||||
criteria work with different amounts of slave connections.
|
||||
|
||||
* With `slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS` and `max_slave_connections=1`, each session picks
|
||||
one slave and one master
|
||||
|
||||
* With `slave_selection_criteria=LEAST_CURRENT_OPERATIONS` and `max_slave_connections=100%`, each session
|
||||
picks one master and as many slaves as possible
|
||||
|
||||
* With `slave_selection_criteria=LEAST_CURRENT_OPERATIONS` each read is load balanced based on how many
|
||||
queries are active on a particular slave
|
||||
|
||||
* With `slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS` each read is sent to the slave with the least
|
||||
amount of connections
|
||||
|
||||
|
||||
### `max_sescmd_history`
|
||||
|
||||
**`max_sescmd_history`** sets a limit on how many session commands each session can execute before the session command history is disabled. The default is an unlimited number of session commands.
|
||||
|
@ -2,11 +2,24 @@
|
||||
|
||||
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE)
|
||||
|
||||
# Check target
|
||||
set(PACK_TARGETS "core" "devel" "external" "all")
|
||||
if(DEFINED TARGET_COMPONENT AND NOT TARGET_COMPONENT STREQUAL "")
|
||||
set(LIST_INDEX -1)
|
||||
list(FIND PACK_TARGETS ${TARGET_COMPONENT} LIST_INDEX)
|
||||
if (${LIST_INDEX} EQUAL -1)
|
||||
message(FATAL_ERROR "Unrecognized TARGET_COMPONENT value. Allowed values: ${PACK_TARGETS}.")
|
||||
endif()
|
||||
else()
|
||||
set(TARGET_COMPONENT "core")
|
||||
message(STATUS "No TARGET_COMPONENT defined, using default value 'core'")
|
||||
endif()
|
||||
|
||||
# Generic CPack configuration variables
|
||||
set(CPACK_SET_DESTDIR ON)
|
||||
set(CPACK_PACKAGE_RELOCATABLE FALSE)
|
||||
set(CPACK_STRIP_FILES FALSE)
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale - The Dynamic Data Routing Platform")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "${MAXSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "${MAXSCALE_VERSION_MINOR}")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "${MAXSCALE_VERSION_PATCH}")
|
||||
@ -15,6 +28,15 @@ set(CPACK_PACKAGE_VENDOR "MariaDB Corporation Ab")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_SOURCE_DIR}/etc/DESCRIPTION)
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
# If building devel package, change the description. Deb- and rpm-specific parameters are set in their
|
||||
# dedicated files "package_(deb/rpm).cmake"
|
||||
if (TARGET_COMPONENT STREQUAL "devel")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale plugin development headers")
|
||||
set(DESCRIPTION_TEXT "\
|
||||
This package contains header files required for plugin module development for MariaDB MaxScale. \
|
||||
The source of MariaDB MaxScale is not required.")
|
||||
endif()
|
||||
|
||||
# If we're building something other than the main package, append the target name
|
||||
# to the package name.
|
||||
if(DEFINED TARGET_COMPONENT AND NOT TARGET_COMPONENT STREQUAL "core" AND NOT TARGET_COMPONENT STREQUAL "")
|
||||
|
@ -7,6 +7,8 @@ set(CPACK_DEBIAN_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}")
|
||||
if(TARGET_COMPONENT STREQUAL "core")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst;${CMAKE_BINARY_DIR}/postrm")
|
||||
elseif(TARGET_COMPONENT STREQUAL "devel")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n${DESCRIPTION_TEXT}")
|
||||
endif()
|
||||
|
||||
if(EXTRA_PACKAGE_DEPENDENCIES)
|
||||
|
@ -30,6 +30,8 @@ if(TARGET_COMPONENT STREQUAL "core")
|
||||
|
||||
# Installing this prevents RPM from deleting the /var/lib/maxscale folder
|
||||
install(DIRECTORY DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale)
|
||||
elseif(TARGET_COMPONENT STREQUAL "devel")
|
||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n${DESCRIPTION_TEXT}")
|
||||
endif()
|
||||
|
||||
if(EXTRA_PACKAGE_DEPENDENCIES)
|
||||
|
20
examples/CMakeLists.txt
Normal file
20
examples/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR})
|
||||
|
||||
add_library(roundrobinrouter SHARED roundrobinrouter.cpp)
|
||||
target_link_libraries(roundrobinrouter maxscale-common)
|
||||
set_target_properties(roundrobinrouter PROPERTIES VERSION "1.0.0")
|
||||
install_module(roundrobinrouter core)
|
||||
|
||||
add_library(testroute SHARED testroute.c)
|
||||
target_link_libraries(testroute maxscale-common)
|
||||
set_target_properties(testroute PROPERTIES VERSION "1.0.0")
|
||||
install_module(testroute core)
|
||||
|
||||
add_library(testfilter SHARED testfilter.c)
|
||||
target_link_libraries(testfilter maxscale-common)
|
||||
set_target_properties(testfilter PROPERTIES VERSION "1.0.0")
|
||||
install_module(testfilter core)
|
||||
|
||||
add_library(testprotocol SHARED testprotocol.c)
|
||||
set_target_properties(testprotocol PROPERTIES VERSION "1.0.0")
|
||||
install_module(testprotocol core)
|
897
examples/roundrobinrouter.cpp
Normal file
897
examples/roundrobinrouter.cpp
Normal file
@ -0,0 +1,897 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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 roundrobinrouter.c - Round-robin router load balancer
|
||||
*
|
||||
* This is an implementation of a simple query router that balances reads on a
|
||||
* query level. The router is configured with a set of slaves and optionally
|
||||
* a master. The router balances the client read queries over the set of slave
|
||||
* servers, sending write operations to the master. Session-operations are sent
|
||||
* to all slaves and the master. The read query balancing is done in round robin
|
||||
* style: in each session, the slave servers (and the master if inserted into the
|
||||
* slave list) take turns processing read queries.
|
||||
*
|
||||
* This router is intended to be a rather straightforward example on how to
|
||||
* program a module for MariaDB MaxScale. The router does not yet support all
|
||||
* SQL-commands and there are bound to be various limitations yet unknown. It
|
||||
* does work on basic reads and writes.
|
||||
*
|
||||
*/
|
||||
|
||||
/* The log macros use this definition. */
|
||||
#define MXS_MODULE_NAME "RoundRobinRouter"
|
||||
|
||||
#include <maxscale/cppdefs.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/buffer.h>
|
||||
#include <maxscale/dcb.h>
|
||||
#include <maxscale/modinfo.h>
|
||||
#include <maxscale/modulecmd.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/protocol/mysql.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include <maxscale/router.h>
|
||||
|
||||
//#define DEBUG_RRROUTER
|
||||
#undef DEBUG_RROUTER
|
||||
|
||||
#ifdef DEBUG_RRROUTER
|
||||
#define RR_DEBUG(msg, ...) MXS_NOTICE(msg, ##__VA_ARGS__)
|
||||
#else
|
||||
#define RR_DEBUG(msg, ...)
|
||||
#endif
|
||||
|
||||
/* This router handles different query types in a different manner. Some queries
|
||||
* require that a "write_backend" is set. */
|
||||
const uint32_t q_route_to_rr = (QUERY_TYPE_LOCAL_READ | QUERY_TYPE_READ |
|
||||
QUERY_TYPE_MASTER_READ | QUERY_TYPE_USERVAR_READ |
|
||||
QUERY_TYPE_SYSVAR_READ | QUERY_TYPE_GSYSVAR_READ |
|
||||
QUERY_TYPE_SHOW_DATABASES | QUERY_TYPE_SHOW_TABLES);
|
||||
|
||||
const uint32_t q_route_to_all = (QUERY_TYPE_SESSION_WRITE | QUERY_TYPE_USERVAR_WRITE |
|
||||
QUERY_TYPE_GSYSVAR_WRITE | QUERY_TYPE_ENABLE_AUTOCOMMIT |
|
||||
QUERY_TYPE_DISABLE_AUTOCOMMIT);
|
||||
|
||||
const uint32_t q_trx_begin = QUERY_TYPE_BEGIN_TRX;
|
||||
|
||||
const uint32_t q_trx_end = (QUERY_TYPE_ROLLBACK | QUERY_TYPE_COMMIT);
|
||||
|
||||
const uint32_t q_route_to_write = (QUERY_TYPE_WRITE | QUERY_TYPE_PREPARE_NAMED_STMT |
|
||||
QUERY_TYPE_PREPARE_STMT | QUERY_TYPE_EXEC_STMT |
|
||||
QUERY_TYPE_CREATE_TMP_TABLE | QUERY_TYPE_READ_TMP_TABLE);
|
||||
|
||||
const char MAX_BACKENDS[] = "max_backends";
|
||||
const char WRITE_BACKEND[] = "write_backend";
|
||||
const char PRINT_ON_ROUTING[] = "print_on_routing";
|
||||
const char DUMMY[] = "dummy_setting";
|
||||
|
||||
/* Enum setting definition example */
|
||||
static const MXS_ENUM_VALUE enum_example[] =
|
||||
{
|
||||
{"two", 2},
|
||||
{"zero", 0},
|
||||
{NULL} /* Last must be NULL */
|
||||
};
|
||||
|
||||
static modulecmd_arg_type_t custom_cmd_args[] =
|
||||
{
|
||||
{MODULECMD_ARG_STRING, "Example string"},
|
||||
{(MODULECMD_ARG_BOOLEAN | MODULECMD_ARG_OPTIONAL), "This is an optional bool parameter"}
|
||||
};
|
||||
|
||||
bool custom_cmd_example(const MODULECMD_ARG *argv);
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
|
||||
typedef std::vector<DCB*> DCB_VEC;
|
||||
|
||||
class RRRouter;
|
||||
class RRRouterSession;
|
||||
|
||||
/* Each service using this router will have a router object instance. */
|
||||
class RRRouter : public MXS_ROUTER
|
||||
{
|
||||
private:
|
||||
SERVICE* m_service; /* Service this router is part of */
|
||||
/* Router settings */
|
||||
unsigned int m_max_backends; /* How many backend servers to use */
|
||||
SERVER* m_write_server; /* Where to send write etc. "unsafe" queries */
|
||||
bool m_print_on_routing; /* Print a message on every packet routed? */
|
||||
uint64_t m_example_enum; /* Not used */
|
||||
|
||||
void decide_target(RRRouterSession* rses, GWBUF* querybuf, DCB*& target, bool& route_to_all);
|
||||
public:
|
||||
/* Statistics, written to by multiple threads */
|
||||
volatile unsigned long int m_routing_s; /* Routing success */
|
||||
volatile unsigned long int m_routing_f; /* Routing fail */
|
||||
volatile unsigned long int m_routing_c; /* Client packets routed */
|
||||
|
||||
/* Methods */
|
||||
RRRouter(SERVICE* service);
|
||||
~RRRouter();
|
||||
RRRouterSession* create_session(MXS_SESSION* session);
|
||||
int route_query(RRRouterSession* rses, GWBUF* querybuf);
|
||||
void client_reply(RRRouterSession* rses, GWBUF* buf, DCB* backend_dcb);
|
||||
void handle_error(RRRouterSession* rses, GWBUF* message, DCB* problem_dcb,
|
||||
mxs_error_action_t action, bool* succp);
|
||||
};
|
||||
|
||||
/* Every client connection has a corresponding session. */
|
||||
class RRRouterSession : public MXS_ROUTER_SESSION
|
||||
{
|
||||
public:
|
||||
bool m_closed; /* true when closeSession is called */
|
||||
DCB_VEC m_backend_dcbs; /* backends */
|
||||
DCB* m_write_dcb; /* write backend */
|
||||
DCB* m_client_dcb; /* client */
|
||||
unsigned int m_route_count; /* how many packets have been routed */
|
||||
bool m_on_transaction; /* Is the session in transaction mode? */
|
||||
unsigned int m_replies_to_ignore; /* Counts how many replies should be ignored. */
|
||||
|
||||
RRRouterSession(DCB_VEC&, DCB*, DCB*);
|
||||
~RRRouterSession();
|
||||
void close();
|
||||
};
|
||||
|
||||
RRRouter::RRRouter(SERVICE* service)
|
||||
: m_service(service)
|
||||
, m_routing_s(0)
|
||||
, m_routing_f(0)
|
||||
, m_routing_c(0)
|
||||
{
|
||||
RR_DEBUG("Creating instance.");
|
||||
/* Read options specific to round robin router. */
|
||||
MXS_CONFIG_PARAMETER* params = service->svc_config_param;
|
||||
m_max_backends = config_get_integer(params, MAX_BACKENDS);
|
||||
m_write_server = config_get_server(params, WRITE_BACKEND);
|
||||
m_print_on_routing = config_get_bool(params, PRINT_ON_ROUTING);
|
||||
m_example_enum = config_get_enum(params, DUMMY, enum_example);
|
||||
|
||||
RR_DEBUG("Settings read:");
|
||||
RR_DEBUG("'%s': %d", MAX_BACKENDS, m_max_backends);
|
||||
RR_DEBUG("'%s': %p", WRITE_BACKEND, m_write_server);
|
||||
RR_DEBUG("'%s': %d", PRINT_ON_ROUTING, m_print_on_routing);
|
||||
RR_DEBUG("'%s': %lu", DUMMY, m_example_enum);
|
||||
}
|
||||
RRRouter::~RRRouter()
|
||||
{
|
||||
RR_DEBUG("Deleting router instance.");
|
||||
RR_DEBUG("Queries routed successfully: %lu", m_routing_s);
|
||||
RR_DEBUG("Failed routing attempts: %lu", m_routing_f);
|
||||
RR_DEBUG("Client replies: %lu", m_routing_c);
|
||||
}
|
||||
|
||||
RRRouterSession* RRRouter::create_session(MXS_SESSION* session)
|
||||
{
|
||||
DCB_VEC backends;
|
||||
DCB* write_dcb = NULL;
|
||||
RRRouterSession* rses = NULL;
|
||||
try
|
||||
{
|
||||
/* Try to connect to as many backends as required. */
|
||||
SERVER_REF* sref;
|
||||
for (sref = m_service->dbref; sref != NULL; sref = sref->next)
|
||||
{
|
||||
if (SERVER_REF_IS_ACTIVE(sref) && (backends.size() < m_max_backends))
|
||||
{
|
||||
/* Connect to server */
|
||||
DCB* conn = dcb_connect(sref->server, session,
|
||||
sref->server->protocol);
|
||||
if (conn)
|
||||
{
|
||||
/* Success */
|
||||
atomic_add(&sref->connections, 1);
|
||||
conn->service = session->service;
|
||||
backends.push_back(conn);
|
||||
} /* Any error by dcb_connect is reported by the function itself */
|
||||
}
|
||||
}
|
||||
if (m_write_server)
|
||||
{
|
||||
/* Connect to write backend server. This is not essential. */
|
||||
write_dcb = dcb_connect(m_write_server, session, m_write_server->protocol);
|
||||
if (write_dcb)
|
||||
{
|
||||
/* Success */
|
||||
write_dcb->service = session->service;
|
||||
}
|
||||
}
|
||||
if (backends.size() < 1)
|
||||
{
|
||||
MXS_ERROR("Session creation failed, could not connect to any "
|
||||
"read backends.");
|
||||
}
|
||||
else
|
||||
{
|
||||
rses = new RRRouterSession(backends, write_dcb, session->client_dcb);
|
||||
RR_DEBUG("Session with %lu connections created.",
|
||||
backends.size() + (write_dcb ? 1 : 0));
|
||||
}
|
||||
}
|
||||
catch (const std::exception& x)
|
||||
{
|
||||
MXS_ERROR("Caught exception: %s", x.what());
|
||||
/* Close any connections already made */
|
||||
for (unsigned int i = 0; i < backends.size(); i++)
|
||||
{
|
||||
DCB* dcb = backends[i];
|
||||
dcb_close(dcb);
|
||||
atomic_add(&(m_service->dbref->connections), -1);
|
||||
}
|
||||
backends.clear();
|
||||
if (write_dcb)
|
||||
{
|
||||
dcb_close(write_dcb);
|
||||
}
|
||||
}
|
||||
return rses;
|
||||
}
|
||||
|
||||
int RRRouter::route_query(RRRouterSession* rses, GWBUF* querybuf)
|
||||
{
|
||||
int rval = 0;
|
||||
const bool print = m_print_on_routing;
|
||||
DCB* target = NULL;
|
||||
bool route_to_all = false;
|
||||
|
||||
if (!rses->m_closed)
|
||||
{
|
||||
decide_target(rses, querybuf, target, route_to_all);
|
||||
}
|
||||
|
||||
/* Target selection done, write to dcb. */
|
||||
if (target)
|
||||
{
|
||||
/* We have one target backend */
|
||||
if (print)
|
||||
{
|
||||
MXS_NOTICE("Routing statement of length %du to backend '%s'.",
|
||||
gwbuf_length(querybuf), target->server->unique_name);
|
||||
}
|
||||
/* Do not use dcb_write() to output to a dcb. dcb_write() is used only
|
||||
* for raw write in the procol modules. */
|
||||
rval = target->func.write(target, querybuf);
|
||||
/* After write, the buffer points to non-existing data. */
|
||||
querybuf = NULL;
|
||||
}
|
||||
else if (route_to_all)
|
||||
{
|
||||
int n_targets = rses->m_backend_dcbs.size() + (rses->m_write_dcb ? 1 : 0);
|
||||
if (print)
|
||||
{
|
||||
MXS_NOTICE("Routing statement of length %du to %d backends.",
|
||||
gwbuf_length(querybuf), n_targets);
|
||||
}
|
||||
int route_success = 0;
|
||||
for (unsigned int i = 0; i < rses->m_backend_dcbs.size(); i++)
|
||||
{
|
||||
DCB* dcb = rses->m_backend_dcbs[i];
|
||||
/* Need to clone the buffer since write consumes it */
|
||||
GWBUF* copy = gwbuf_clone(querybuf);
|
||||
if (copy)
|
||||
{
|
||||
route_success += dcb->func.write(dcb, copy);
|
||||
}
|
||||
}
|
||||
if (rses->m_write_dcb)
|
||||
{
|
||||
GWBUF* copy = gwbuf_clone(querybuf);
|
||||
if (copy)
|
||||
{
|
||||
route_success += rses->m_write_dcb->func.write(rses->m_write_dcb,
|
||||
copy);
|
||||
}
|
||||
}
|
||||
rses->m_replies_to_ignore += route_success - 1;
|
||||
rval = (route_success == n_targets) ? 1 : 0;
|
||||
gwbuf_free(querybuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Could not find a valid routing backend. Either the "
|
||||
"'%s' is not set or the command is not recognized.",
|
||||
WRITE_BACKEND);
|
||||
gwbuf_free(querybuf);
|
||||
}
|
||||
if (rval == 1)
|
||||
{
|
||||
/* Non-atomic update of shared data, but contents are non-essential */
|
||||
m_routing_s++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_routing_f++;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
void RRRouter::client_reply(RRRouterSession* rses, GWBUF* buf, DCB* backend_dcb)
|
||||
{
|
||||
if (rses->m_replies_to_ignore > 0)
|
||||
{
|
||||
/* In this case MaxScale cloned the message to many backends but the client
|
||||
* expects just one reply. Assume that client does not send next query until
|
||||
* previous has been answered.
|
||||
*/
|
||||
rses->m_replies_to_ignore--;
|
||||
gwbuf_free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
MXS_SESSION_ROUTE_REPLY(backend_dcb->session, buf);
|
||||
|
||||
m_routing_c++;
|
||||
if (m_print_on_routing)
|
||||
{
|
||||
MXS_NOTICE("Replied to client.\n");
|
||||
}
|
||||
}
|
||||
void RRRouter::handle_error(RRRouterSession* rses, GWBUF* message, DCB* problem_dcb,
|
||||
mxs_error_action_t action, bool* succp)
|
||||
{
|
||||
/* Don't handle same error twice on same DCB */
|
||||
if (problem_dcb->dcb_errhandle_called)
|
||||
{
|
||||
/* Assume that previous call succeed. */
|
||||
*succp = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
|
||||
MXS_SESSION* session = problem_dcb->session;
|
||||
DCB* client_dcb = session->client_dcb;
|
||||
mxs_session_state_t sesstate = session->state;
|
||||
|
||||
/* If the erroneous dcb is a client handler, close it. Setting succp to
|
||||
* false will cause the entire attached session to be closed.
|
||||
*/
|
||||
if (problem_dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER)
|
||||
{
|
||||
dcb_close(problem_dcb);
|
||||
*succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ERRACT_REPLY_CLIENT:
|
||||
{
|
||||
/* React to failed authentication, send message to client */
|
||||
if (sesstate == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
/* Send error report to client */
|
||||
GWBUF* copy = gwbuf_clone(message);
|
||||
if (copy)
|
||||
{
|
||||
client_dcb->func.write(client_dcb, copy);
|
||||
}
|
||||
}
|
||||
*succp = false;
|
||||
}
|
||||
break;
|
||||
case ERRACT_NEW_CONNECTION:
|
||||
{
|
||||
/* React to a failed backend */
|
||||
if (problem_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER)
|
||||
{
|
||||
if (problem_dcb == rses->m_write_dcb)
|
||||
{
|
||||
dcb_close(rses->m_write_dcb);
|
||||
rses->m_write_dcb = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Find dcb in the list of backends */
|
||||
DCB_VEC::iterator iter = rses->m_backend_dcbs.begin();
|
||||
while (iter != rses->m_backend_dcbs.end())
|
||||
{
|
||||
if (*iter == problem_dcb)
|
||||
{
|
||||
dcb_close(*iter);
|
||||
rses->m_backend_dcbs.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If there is still backends remaining, return true since
|
||||
* router can still function.
|
||||
*/
|
||||
*succp = (rses->m_backend_dcbs.size() > 0) ? true : false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ss_dassert(!true);
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RRRouterSession::RRRouterSession(DCB_VEC& backends, DCB* write, DCB* client)
|
||||
: m_closed(false)
|
||||
, m_route_count(0)
|
||||
, m_on_transaction(false)
|
||||
, m_replies_to_ignore(0)
|
||||
{
|
||||
m_backend_dcbs = backends;
|
||||
m_write_dcb = write;
|
||||
m_client_dcb = client;
|
||||
}
|
||||
|
||||
RRRouterSession::~RRRouterSession()
|
||||
{
|
||||
/* Shouldn't happen. */
|
||||
ss_dassert(m_closed);
|
||||
}
|
||||
|
||||
void RRRouterSession::close()
|
||||
{
|
||||
if (!m_closed)
|
||||
{
|
||||
/**
|
||||
* Mark router session as closed. @c m_closed is checked at the start
|
||||
* of most API functions to quickly stop the processing of closed sessions.
|
||||
*/
|
||||
m_closed = true;
|
||||
int closed_conns = 0;
|
||||
for (unsigned int i = 0; i < m_backend_dcbs.size(); i++)
|
||||
{
|
||||
DCB* dcb = m_backend_dcbs[i];
|
||||
SERVER_REF* sref = dcb->service->dbref;
|
||||
dcb_close(dcb);
|
||||
closed_conns++;
|
||||
atomic_add(&(sref->connections), -1);
|
||||
}
|
||||
m_backend_dcbs.clear();
|
||||
if (m_write_dcb)
|
||||
{
|
||||
dcb_close(m_write_dcb);
|
||||
m_write_dcb = NULL;
|
||||
closed_conns++;
|
||||
}
|
||||
RR_DEBUG("Session with %d connections closed.", closed_conns);
|
||||
}
|
||||
}
|
||||
|
||||
void RRRouter::decide_target(RRRouterSession* rses, GWBUF* querybuf, DCB*& target, bool& route_to_all)
|
||||
{
|
||||
/* Extract the command type from the SQL-buffer */
|
||||
mysql_server_cmd_t cmd_type = MYSQL_GET_COMMAND(GWBUF_DATA(querybuf));
|
||||
/* The "query_types" is only really valid for query-commands but let's use
|
||||
* it here for all command types.
|
||||
*/
|
||||
uint32_t query_types = 0;
|
||||
|
||||
switch (cmd_type)
|
||||
{
|
||||
case MYSQL_COM_QUERY:
|
||||
{
|
||||
/* Use the inbuilt query_classifier to get information about
|
||||
* the query. The default qc works with mySQL-queries.
|
||||
*/
|
||||
query_types = qc_get_type_mask(querybuf);
|
||||
|
||||
#ifdef DEBUG_RRROUTER
|
||||
char* zSql_query = NULL;
|
||||
int length = 0;
|
||||
modutil_extract_SQL(querybuf, &zSql_query, &length);
|
||||
string sql_query(zSql_query, length);
|
||||
RR_DEBUG("QUERY: %s", sql_query.c_str());
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case MYSQL_COM_INIT_DB:
|
||||
query_types = q_route_to_all;
|
||||
RR_DEBUG("MYSQL_COM_INIT_DB");
|
||||
break;
|
||||
case MYSQL_COM_QUIT:
|
||||
query_types = q_route_to_all;
|
||||
RR_DEBUG("MYSQL_COM_QUIT");
|
||||
break;
|
||||
case MYSQL_COM_FIELD_LIST:
|
||||
query_types = q_route_to_rr;
|
||||
RR_DEBUG("MYSQL_COM_FIELD_LIST");
|
||||
break;
|
||||
default:
|
||||
/* TODO: Add support for other commands if needed. */
|
||||
/* This error message will only print the number of the cmd. */
|
||||
MXS_ERROR("Received unexpected sql command type: '%d'.", cmd_type);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((query_types & q_route_to_write) != 0)
|
||||
{
|
||||
target = rses->m_write_dcb;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This is not yet sufficient for handling transactions. */
|
||||
if ((query_types & q_trx_begin) != 0)
|
||||
{
|
||||
rses->m_on_transaction = true;
|
||||
}
|
||||
if (rses->m_on_transaction)
|
||||
{
|
||||
/* If a transaction is going on, route all to write backend */
|
||||
target = rses->m_write_dcb;
|
||||
}
|
||||
if ((query_types & q_trx_end) != 0)
|
||||
{
|
||||
rses->m_on_transaction = false;
|
||||
}
|
||||
|
||||
if (!target && ((query_types & q_route_to_rr) != 0))
|
||||
{
|
||||
/* Round robin backend. */
|
||||
unsigned int index = (rses->m_route_count++) % rses->m_backend_dcbs.size();
|
||||
target = rses->m_backend_dcbs[index];
|
||||
}
|
||||
/* Some commands and queries are routed to all backends. */
|
||||
else if (!target && ((query_types & q_route_to_all) != 0))
|
||||
{
|
||||
route_to_all = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The functions implementing the router module API. These do not need to be
|
||||
* "extern C", but they do need to be callable from C code.
|
||||
*/
|
||||
static MXS_ROUTER* createInstance(SERVICE* service, char** options);
|
||||
static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* instance, MXS_SESSION* session);
|
||||
static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session);
|
||||
static void freeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session);
|
||||
static int routeQuery(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* querybuf);
|
||||
static void diagnostics(MXS_ROUTER* instance, DCB* dcb);
|
||||
static void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* resultbuf, DCB* backend_dcb);
|
||||
static void handleError(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* errmsgbuf, DCB* backend_dcb, mxs_error_action_t action,
|
||||
bool* succp);
|
||||
static uint64_t getCapabilities(MXS_ROUTER *instance);
|
||||
static void destroyInstance(MXS_ROUTER* instance);
|
||||
/* The next two entry points are usually optional. */
|
||||
static int process_init();
|
||||
static void process_finish();
|
||||
|
||||
/*
|
||||
* This is called by the module loader during MaxScale startup. A module
|
||||
* description, including entrypoints and allowed configuration parameters,
|
||||
* is returned. This function must be exported.
|
||||
*/
|
||||
extern "C" MXS_MODULE* MXS_CREATE_MODULE();
|
||||
MXS_MODULE* MXS_CREATE_MODULE()
|
||||
{
|
||||
static MXS_ROUTER_OBJECT entryPoints =
|
||||
{
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
routeQuery,
|
||||
diagnostics,
|
||||
clientReply,
|
||||
handleError,
|
||||
getCapabilities,
|
||||
destroyInstance
|
||||
};
|
||||
|
||||
static MXS_MODULE moduleObject =
|
||||
{
|
||||
MXS_MODULE_API_ROUTER, /* Module type */
|
||||
MXS_MODULE_BETA_RELEASE, /* Release status */
|
||||
MXS_ROUTER_VERSION, /* Implemented module API version */
|
||||
"A simple round robin router", /* Description */
|
||||
"V1.1.0", /* Module version */
|
||||
&entryPoints, /* Defined above */
|
||||
process_init, /* Process init, can be null */
|
||||
process_finish, /* Process finish, can be null */
|
||||
NULL, /* Thread init */
|
||||
NULL, /* Thread finish */
|
||||
{
|
||||
/* Next is an array of MODULE_PARAM structs, max 64 items. These define all
|
||||
* the possible parameters that this module accepts. This is required
|
||||
* since the module loader also parses the configuration file for the module.
|
||||
* Any unrecognised parameters in the config file are discarded.
|
||||
*
|
||||
* Note that many common parameters, such as backend servers, are
|
||||
* already set to the upper level "service"-object.
|
||||
*/
|
||||
{ /* For simple types, only 3 of the 5 struct fields need to be
|
||||
* defined. */
|
||||
MAX_BACKENDS, /* Setting identifier in maxscale.cnf */
|
||||
MXS_MODULE_PARAM_INT, /* Setting type */
|
||||
"0" /* Default value */
|
||||
},
|
||||
{PRINT_ON_ROUTING, MXS_MODULE_PARAM_BOOL, "false"},
|
||||
{WRITE_BACKEND, MXS_MODULE_PARAM_SERVER, NULL},
|
||||
{ /* Enum types require an array with allowed values. */
|
||||
DUMMY,
|
||||
MXS_MODULE_PARAM_ENUM,
|
||||
"the_answer",
|
||||
MXS_MODULE_OPT_NONE,
|
||||
enum_example
|
||||
},
|
||||
{MXS_END_MODULE_PARAMS}
|
||||
}
|
||||
};
|
||||
return &moduleObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an instance of the router (API).
|
||||
*
|
||||
* Create an instance of the round robin router. One instance of the router is
|
||||
* created for each service that is defined in the configuration as using this
|
||||
* router. One instance of the router will handle multiple connections
|
||||
* (router sessions).
|
||||
*
|
||||
* @param service The service this router is being created for
|
||||
* @param options The options for this query router
|
||||
* @return NULL in failure, pointer to router in success.
|
||||
*/
|
||||
static MXS_ROUTER* createInstance(SERVICE* service, char** options)
|
||||
{
|
||||
RRRouter* instance = NULL;
|
||||
/* The core of MaxScale is written in C and does not understand exceptions.
|
||||
* The macro catches all exceptions. Add custom handling here if required. */
|
||||
MXS_EXCEPTION_GUARD(instance = new RRRouter(service));
|
||||
/* The MXS_ROUTER is just a void*. Only the router module will dereference
|
||||
* the pointer. */
|
||||
|
||||
/* Register a custom command */
|
||||
if (!modulecmd_register_command("rrrouter", "test_command", custom_cmd_example,
|
||||
2, custom_cmd_args))
|
||||
{
|
||||
MXS_ERROR("Module command registration failed.");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new router session for this router instance (API).
|
||||
*
|
||||
* Connect a client session to the router instance and return a router session.
|
||||
* The router session stores all client specific data required by the router.
|
||||
*
|
||||
* @param instance The router object instance
|
||||
* @param session The MaxScale session (generic client connection data)
|
||||
* @return Client specific data for this router
|
||||
*/
|
||||
static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* instance, MXS_SESSION* session)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
RRRouterSession* rses = NULL;
|
||||
MXS_EXCEPTION_GUARD(rses = router->create_session(session));
|
||||
return rses;
|
||||
}
|
||||
/**
|
||||
* @brief Close an existing router session for this router instance (API).
|
||||
*
|
||||
* Close a client session attached to the router instance. This function should
|
||||
* close connections and release other resources allocated in "newSession" or
|
||||
* otherwise held by the router session. This function should NOT free the
|
||||
* session object itself.
|
||||
*
|
||||
* @param instance The router object instance
|
||||
* @param session The router session
|
||||
*/
|
||||
static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session)
|
||||
{
|
||||
RRRouterSession* rses = static_cast<RRRouterSession*>(session);
|
||||
MXS_EXCEPTION_GUARD(rses->close());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free a router session (API).
|
||||
*
|
||||
* When a router session has been closed, freeSession may be called to free
|
||||
* allocated resources.
|
||||
*
|
||||
* @param instance The router instance the session belongs to
|
||||
* @param session Router client session
|
||||
*
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session)
|
||||
{
|
||||
RRRouterSession* rses = static_cast<RRRouterSession*>(session);
|
||||
delete rses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Route a packet (API)
|
||||
*
|
||||
* The routeQuery function receives a packet and makes the routing decision
|
||||
* based on the contents of the router instance, router session and the query
|
||||
* itself. It then sends the query to the target backend(s).
|
||||
*
|
||||
* @param instance Router instance
|
||||
* @param session Router session associated with the client
|
||||
* @param buffer Buffer containing the query (or command)
|
||||
* @return 1 on success, 0 on error
|
||||
*/
|
||||
static int routeQuery(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* buffer)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
RRRouterSession* rses = static_cast<RRRouterSession*>(session);
|
||||
int rval = 0;
|
||||
MXS_EXCEPTION_GUARD(rval = router->route_query(rses, buffer));
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Diagnostics routine (API)
|
||||
*
|
||||
* Print router statistics to the DCB passed in. This is usually called by the
|
||||
* MaxInfo or MaxAdmin modules.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param dcb The DCB for diagnostic output
|
||||
*/
|
||||
static void diagnostics(MXS_ROUTER* instance, DCB* dcb)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
dcb_printf(dcb, "\t\tQueries routed successfully: %lu\n", router->m_routing_s);
|
||||
dcb_printf(dcb, "\t\tFailed routing attempts: %lu\n", router->m_routing_f);
|
||||
dcb_printf(dcb, "\t\tClient replies routed: %lu\n", router->m_routing_c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Client Reply routine (API)
|
||||
*
|
||||
* This routine receives a packet from a backend server meant for the client.
|
||||
* Often, there is little logic needed and the packet can just be forwarded to
|
||||
* the next element in the processing chain.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param session The router session
|
||||
* @param backend_dcb The backend DCB (data source)
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* queue,
|
||||
DCB* backend_dcb)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
RRRouterSession* rses = static_cast<RRRouterSession*>(session);
|
||||
MXS_EXCEPTION_GUARD(router->client_reply(rses, queue, backend_dcb));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Handler routine (API)
|
||||
*
|
||||
* This routine will handle errors that occurred with the session. This function
|
||||
* is called if routeQuery() returns 0 instead of 1. The client or a backend
|
||||
* unexpectedly closing a connection also triggers this routine.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param session The router session
|
||||
* @param message The error message to reply
|
||||
* @param problem_dcb The DCB related to the error
|
||||
* @param action The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT
|
||||
* @param succp Output result of action, true if router can continue
|
||||
*/
|
||||
static void handleError(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session,
|
||||
GWBUF* message, DCB* problem_dcb,
|
||||
mxs_error_action_t action, bool* succp)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
RRRouterSession* rses = static_cast<RRRouterSession*>(session);
|
||||
MXS_EXCEPTION_GUARD(router->handle_error(rses, message, problem_dcb, action, succp));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get router capabilities (API)
|
||||
*
|
||||
* Return a bit map indicating the characteristics of this router type.
|
||||
* In this case, the only bit set indicates that the router wants to receive
|
||||
* data for routing as whole SQL statements.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @return RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_STMT_OUTPUT
|
||||
*/
|
||||
static uint64_t getCapabilities(MXS_ROUTER *instance)
|
||||
{
|
||||
/* This router needs to parse client queries, so it should set RCAP_TYPE_CONTIGUOUS_INPUT.
|
||||
* For output, parsing is not required but counting SQL replies is. RCAP_TYPE_RESULTSET_OUTPUT
|
||||
* should be sufficient.
|
||||
*/
|
||||
return RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_RESULTSET_OUTPUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy router instance (API)
|
||||
*
|
||||
* A destroy-function is rarely required, since this is usually called only at
|
||||
* program exit.
|
||||
*
|
||||
* @param instance Router instance
|
||||
*/
|
||||
static void destroyInstance(MXS_ROUTER* instance)
|
||||
{
|
||||
RRRouter* router = static_cast<RRRouter*>(instance);
|
||||
delete router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make any initializations required by the router module as a whole and not
|
||||
* specific to any individual router instance.
|
||||
*
|
||||
* @return 0 on success
|
||||
*/
|
||||
static int process_init()
|
||||
{
|
||||
RR_DEBUG("Module loaded.");
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Undo module initializations.
|
||||
*/
|
||||
static void process_finish()
|
||||
{
|
||||
RR_DEBUG("Module unloaded.");
|
||||
}
|
||||
|
||||
/**
|
||||
* A function executed as a custom module command through MaxAdmin
|
||||
* @param argv The arguments
|
||||
*/
|
||||
bool custom_cmd_example(const MODULECMD_ARG *argv)
|
||||
{
|
||||
cout << MXS_MODULE_NAME << " wishes the Admin a good day.\n";
|
||||
int n_args = argv->argc;
|
||||
cout << "The module got " << n_args << " arguments.\n";
|
||||
for (int i = 0; i < n_args; i++)
|
||||
{
|
||||
arg_node node = argv->argv[i];
|
||||
string type_str;
|
||||
string val_str;
|
||||
switch (MODULECMD_GET_TYPE(&node.type))
|
||||
{
|
||||
case MODULECMD_ARG_STRING:
|
||||
{
|
||||
type_str = "string";
|
||||
val_str.assign(node.value.string);
|
||||
}
|
||||
break;
|
||||
case MODULECMD_ARG_BOOLEAN:
|
||||
{
|
||||
type_str = "boolean";
|
||||
val_str.assign((node.value.boolean) ? "true" : "false");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
type_str = "other";
|
||||
val_str.assign("unknown");
|
||||
}
|
||||
break;
|
||||
}
|
||||
cout << "Argument " << i << ": type '" << type_str << "' value '" << val_str <<
|
||||
"'\n";
|
||||
}
|
||||
return true;
|
||||
}
|
@ -15,20 +15,20 @@
|
||||
#include <maxscale/router.h>
|
||||
#include <maxscale/modinfo.h>
|
||||
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue);
|
||||
static void clientReply(MXS_ROUTER *instance, void *session, GWBUF *queue, DCB*);
|
||||
static void diagnostic(MXS_ROUTER *instance, DCB *dcb);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue);
|
||||
static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue, DCB*);
|
||||
static void diagnostic(MXS_ROUTER *instance, DCB *dcb);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -104,10 +104,10 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
return (MXS_SESSION*)MXS_MALLOC(sizeof(TESTSESSION));
|
||||
return (MXS_ROUTER_SESSION*)MXS_MALLOC(sizeof(TESTSESSION));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,24 +118,23 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session)
|
||||
{
|
||||
}
|
||||
|
||||
static void freeSession(
|
||||
MXS_ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
static void freeSession(MXS_ROUTER* router_instance,
|
||||
MXS_ROUTER_SESSION* router_client_session)
|
||||
{
|
||||
MXS_FREE(router_client_session);
|
||||
}
|
||||
|
||||
static int
|
||||
routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue)
|
||||
routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clientReply(MXS_ROUTER* instance, void* session, GWBUF* queue, DCB* dcb)
|
||||
void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* queue, DCB* dcb)
|
||||
{
|
||||
}
|
||||
|
||||
@ -156,12 +155,11 @@ static uint64_t getCapabilities(MXS_ROUTER* instance)
|
||||
}
|
||||
|
||||
|
||||
static void handleError(
|
||||
MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp)
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp)
|
||||
{
|
||||
}
|
@ -23,3 +23,5 @@ file(GLOB GENERATD_HEADERS "${CMAKE_BINARY_DIR}/include/maxscale/*.hh")
|
||||
foreach(var ${GENERATD_HEADERS})
|
||||
install_header(${var} devel)
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(protocol)
|
||||
|
@ -33,7 +33,9 @@ MXS_BEGIN_DECLS
|
||||
* from the @c createInstance function of a filter module and subsequently
|
||||
* passing it back to the API functions of the filter.
|
||||
*/
|
||||
typedef void *MXS_FILTER;
|
||||
typedef struct mxs_filter
|
||||
{
|
||||
} MXS_FILTER;
|
||||
|
||||
/**
|
||||
* MXS_FILTER_SESSION is an opaque type representing the session related
|
||||
@ -43,7 +45,9 @@ typedef void *MXS_FILTER;
|
||||
* from the @c newSession function of a filter module and subsequently
|
||||
* passing it back to the API functions of the filter.
|
||||
*/
|
||||
typedef void *MXS_FILTER_SESSION;
|
||||
typedef struct mxs_filter_session
|
||||
{
|
||||
} MXS_FILTER_SESSION;
|
||||
|
||||
/**
|
||||
* @verbatim
|
||||
|
@ -28,7 +28,7 @@ namespace maxscale
|
||||
* are virtual. That is by design, as the class will be used in a context where
|
||||
* the concrete class is known. That is, there is no need for the virtual mechanism.
|
||||
*/
|
||||
class FilterSession
|
||||
class FilterSession : public MXS_FILTER_SESSION
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@ -200,7 +200,7 @@ protected:
|
||||
* @endcode
|
||||
*/
|
||||
template<class FilterType, class FilterSessionType>
|
||||
class Filter
|
||||
class Filter : public MXS_FILTER
|
||||
{
|
||||
public:
|
||||
static MXS_FILTER* createInstance(const char* zName, char** pzOptions, MXS_CONFIG_PARAMETER* ppParams)
|
||||
@ -209,36 +209,36 @@ public:
|
||||
|
||||
MXS_EXCEPTION_GUARD(pFilter = FilterType::create(zName, pzOptions, ppParams));
|
||||
|
||||
return reinterpret_cast<MXS_FILTER*>(pFilter);
|
||||
return pFilter;
|
||||
}
|
||||
|
||||
static MXS_FILTER_SESSION* newSession(MXS_FILTER* pInstance, MXS_SESSION* pSession)
|
||||
{
|
||||
FilterType* pFilter = reinterpret_cast<FilterType*>(pInstance);
|
||||
void* pFilterSession;
|
||||
FilterType* pFilter = static_cast<FilterType*>(pInstance);
|
||||
FilterSessionType* pFilterSession;
|
||||
|
||||
MXS_EXCEPTION_GUARD(pFilterSession = pFilter->newSession(pSession));
|
||||
|
||||
return reinterpret_cast<MXS_FILTER_SESSION*>(pFilterSession);
|
||||
return pFilterSession;
|
||||
}
|
||||
|
||||
static void closeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
MXS_EXCEPTION_GUARD(pFilterSession->close());
|
||||
}
|
||||
|
||||
static void freeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
MXS_EXCEPTION_GUARD(delete pFilterSession);
|
||||
}
|
||||
|
||||
static void setDownstream(MXS_FILTER*, MXS_FILTER_SESSION* pData, MXS_DOWNSTREAM* pDownstream)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
typename FilterSessionType::Downstream down(*pDownstream);
|
||||
|
||||
@ -247,7 +247,7 @@ public:
|
||||
|
||||
static void setUpstream(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, MXS_UPSTREAM* pUpstream)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
typename FilterSessionType::Upstream up(*pUpstream);
|
||||
|
||||
@ -256,7 +256,7 @@ public:
|
||||
|
||||
static int routeQuery(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
int rv = 0;
|
||||
MXS_EXCEPTION_GUARD(rv = pFilterSession->routeQuery(pPacket));
|
||||
@ -266,7 +266,7 @@ public:
|
||||
|
||||
static int clientReply(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
int rv = 0;
|
||||
MXS_EXCEPTION_GUARD(rv = pFilterSession->clientReply(pPacket));
|
||||
@ -278,13 +278,13 @@ public:
|
||||
{
|
||||
if (pData)
|
||||
{
|
||||
FilterSessionType* pFilterSession = reinterpret_cast<FilterSessionType*>(pData);
|
||||
FilterSessionType* pFilterSession = static_cast<FilterSessionType*>(pData);
|
||||
|
||||
MXS_EXCEPTION_GUARD(pFilterSession->diagnostics(pDcb));
|
||||
}
|
||||
else
|
||||
{
|
||||
FilterType* pFilter = reinterpret_cast<FilterType*>(pInstance);
|
||||
FilterType* pFilter = static_cast<FilterType*>(pInstance);
|
||||
|
||||
MXS_EXCEPTION_GUARD(pFilter->diagnostics(pDcb));
|
||||
}
|
||||
@ -294,7 +294,7 @@ public:
|
||||
{
|
||||
uint64_t rv = 0;
|
||||
|
||||
FilterType* pFilter = reinterpret_cast<FilterType*>(pInstance);
|
||||
FilterType* pFilter = static_cast<FilterType*>(pInstance);
|
||||
|
||||
MXS_EXCEPTION_GUARD(rv = pFilter->getCapabilities());
|
||||
|
||||
@ -303,7 +303,7 @@ public:
|
||||
|
||||
static void destroyInstance(MXS_FILTER* pInstance)
|
||||
{
|
||||
FilterType* pFilter = reinterpret_cast<FilterType*>(pInstance);
|
||||
FilterType* pFilter = static_cast<FilterType*>(pInstance);
|
||||
|
||||
MXS_EXCEPTION_GUARD(delete pFilter);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
file(GLOB HEADERS "*.h*")
|
||||
foreach(var ${HEADERS})
|
||||
get_filename_component(header ${var} NAME)
|
||||
install_header(${header} devel)
|
||||
install_custom_file(${header} ${CMAKE_INSTALL_INCLUDEDIR}/maxscale/protocol devel)
|
||||
endforeach()
|
||||
|
@ -84,13 +84,12 @@ MXS_BEGIN_DECLS
|
||||
#define MYSQL_CHARSET_OFFSET 12
|
||||
#define MYSQL_CLIENT_CAP_OFFSET 4
|
||||
#define MYSQL_CLIENT_CAP_SIZE 4
|
||||
#define MARIADB_CAP_OFFSET MYSQL_CHARSET_OFFSET + 19
|
||||
|
||||
#define GW_MYSQL_PROTOCOL_VERSION 10 // version is 10
|
||||
#define GW_MYSQL_HANDSHAKE_FILLER 0x00
|
||||
#define GW_MYSQL_SERVER_CAPABILITIES_BYTE1 0xff
|
||||
#define GW_MYSQL_SERVER_CAPABILITIES_BYTE2 0xf7
|
||||
#define GW_MYSQL_SERVER_LANGUAGE 0x08
|
||||
#define GW_MYSQL_MAX_PACKET_LEN 0xffffffL;
|
||||
#define GW_MYSQL_MAX_PACKET_LEN 0xffffffL
|
||||
#define GW_MYSQL_SCRAMBLE_SIZE 20
|
||||
#define GW_SCRAMBLE_LENGTH_323 8
|
||||
|
||||
@ -190,7 +189,8 @@ typedef struct mysql_session
|
||||
typedef enum
|
||||
{
|
||||
GW_MYSQL_CAPABILITIES_NONE = 0,
|
||||
GW_MYSQL_CAPABILITIES_LONG_PASSWORD = (1 << 0),
|
||||
/** This is sent by pre-10.2 clients */
|
||||
GW_MYSQL_CAPABILITIES_CLIENT_MYSQL = (1 << 0),
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS = (1 << 1),
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG = (1 << 2),
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB = (1 << 3),
|
||||
@ -210,35 +210,60 @@ typedef enum
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS = (1 << 17),
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS = (1 << 18),
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH = (1 << 19),
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_ATTRS = (1 << 20),
|
||||
GW_MYSQL_CAPABILITIES_AUTH_LENENC_DATA = (1 << 21),
|
||||
GW_MYSQL_CAPABILITIES_EXPIRE_PASSWORD = (1 << 22),
|
||||
GW_MYSQL_CAPABILITIES_SESSION_TRACK = (1 << 23),
|
||||
GW_MYSQL_CAPABILITIES_DEPRECATE_EOF = (1 << 24),
|
||||
GW_MYSQL_CAPABILITIES_SSL_VERIFY_SERVER_CERT = (1 << 30),
|
||||
GW_MYSQL_CAPABILITIES_REMEMBER_OPTIONS = (1 << 31),
|
||||
GW_MYSQL_CAPABILITIES_CLIENT = (GW_MYSQL_CAPABILITIES_LONG_PASSWORD |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_SECURE_CONNECTION),
|
||||
GW_MYSQL_CAPABILITIES_CLIENT_COMPRESS = (GW_MYSQL_CAPABILITIES_LONG_PASSWORD |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_COMPRESS
|
||||
),
|
||||
GW_MYSQL_CAPABILITIES_CLIENT = (
|
||||
GW_MYSQL_CAPABILITIES_CLIENT_MYSQL |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_SECURE_CONNECTION),
|
||||
GW_MYSQL_CAPABILITIES_SERVER = (
|
||||
GW_MYSQL_CAPABILITIES_CLIENT_MYSQL |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_NO_SCHEMA |
|
||||
GW_MYSQL_CAPABILITIES_ODBC |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_IGNORE_SPACE |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_INTERACTIVE |
|
||||
GW_MYSQL_CAPABILITIES_IGNORE_SIGPIPE |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_RESERVED |
|
||||
GW_MYSQL_CAPABILITIES_SECURE_CONNECTION |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH),
|
||||
} gw_mysql_capabilities_t;
|
||||
|
||||
/**
|
||||
* Capabilities supported by MariaDB 10.2 and later, stored in the last 4 bytes
|
||||
* of the 10 byte filler of the initial handshake packet.
|
||||
*
|
||||
* The actual capability bytes use by the server are left shifted by an extra 32
|
||||
* bits to get one 64 bit capability that combines the old and new capabilities.
|
||||
* Since we only use these in the non-shifted form, the definitions declared here
|
||||
* are right shifted by 32 bytes and can be directly copied into the extra capabilities.
|
||||
*/
|
||||
#define MXS_MARIA_CAP_PROGRESS (1 << 0)
|
||||
#define MXS_MARIA_CAP_COM_MULTI (1 << 1)
|
||||
#define MXS_MARIA_CAP_STMT_BULK_OPERATIONS (1 << 2)
|
||||
|
||||
typedef enum enum_server_command mysql_server_cmd_t;
|
||||
|
||||
static const mysql_server_cmd_t MYSQL_COM_UNDEFINED = (mysql_server_cmd_t) - 1;
|
||||
@ -277,6 +302,7 @@ typedef struct
|
||||
uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, created or received */
|
||||
uint32_t server_capabilities; /*< server capabilities, created or received */
|
||||
uint32_t client_capabilities; /*< client capabilities, created or received */
|
||||
uint32_t extra_capabilities; /*< MariaDB 10.2 capabilities */
|
||||
unsigned long tid; /*< MySQL Thread ID, in handshake */
|
||||
unsigned int charset; /*< MySQL character set at connect time */
|
||||
bool ignore_reply; /*< If the reply should be discarded */
|
||||
|
@ -34,7 +34,21 @@ MXS_BEGIN_DECLS
|
||||
* from the @c createInstance function of a router module and subsequently
|
||||
* passing it back to the API functions of the router.
|
||||
*/
|
||||
typedef void *MXS_ROUTER;
|
||||
typedef struct mxs_router
|
||||
{
|
||||
} MXS_ROUTER;
|
||||
|
||||
/**
|
||||
* MXS_ROUTER_SESSION is an opaque type representing the session related
|
||||
* data of a particular router instance.
|
||||
*
|
||||
* MaxScale itself does not do anything with it, except for receiving it
|
||||
* from the @c newSession function of a router module and subsequently
|
||||
* passing it back to the API functions of the router.
|
||||
*/
|
||||
typedef struct mxs_router_session
|
||||
{
|
||||
} MXS_ROUTER_SESSION;
|
||||
|
||||
typedef enum error_action
|
||||
{
|
||||
@ -66,19 +80,19 @@ typedef enum error_action
|
||||
typedef struct mxs_router_object
|
||||
{
|
||||
MXS_ROUTER *(*createInstance)(SERVICE *service, char **options);
|
||||
void *(*newSession)(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
void (*closeSession)(MXS_ROUTER *instance, void *router_session);
|
||||
void (*freeSession)(MXS_ROUTER *instance, void *router_session);
|
||||
int32_t (*routeQuery)(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
MXS_ROUTER_SESSION *(*newSession)(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
void (*closeSession)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
void (*freeSession)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
int32_t (*routeQuery)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
void (*diagnostics)(MXS_ROUTER *instance, DCB *dcb);
|
||||
void (*clientReply)(MXS_ROUTER* instance, void* router_session, GWBUF* queue,
|
||||
void (*clientReply)(MXS_ROUTER* instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
void (*handleError)(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
GWBUF* errmsgbuf,
|
||||
DCB* backend_dcb,
|
||||
void (*handleError)(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errmsgbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool* succp);
|
||||
bool* succp);
|
||||
uint64_t (*getCapabilities)(MXS_ROUTER *instance);
|
||||
void (*destroyInstance)(MXS_ROUTER *instance);
|
||||
} MXS_ROUTER_OBJECT;
|
||||
|
@ -42,7 +42,7 @@ MXS_BEGIN_DECLS
|
||||
*/
|
||||
typedef struct spinlock
|
||||
{
|
||||
int lock; /*< Is the lock held? */
|
||||
volatile int lock;/*< Is the lock held? */
|
||||
#if SPINLOCK_PROFILE
|
||||
int spins; /*< Number of spins on this lock */
|
||||
int maxspins; /*< Max no of spins to acquire lock */
|
||||
|
@ -18,7 +18,7 @@ include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR})
|
||||
|
||||
add_library(qc_sqlite SHARED qc_sqlite.c qc_sqlite3.c builtin_functions.c)
|
||||
add_dependencies(qc_sqlite maxscale_sqlite)
|
||||
add_definitions(-DMAXSCALE -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_OMIT_ATTACH -DSQLITE_OMIT_REINDEX -DSQLITE_OMIT_AUTOVACUUM -DSQLITE_OMIT_PRAGMA)
|
||||
add_definitions(-DMAXSCALE -DSQLITE_THREADSAFE=0 -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_OMIT_ATTACH -DSQLITE_OMIT_REINDEX -DSQLITE_OMIT_AUTOVACUUM -DSQLITE_OMIT_PRAGMA)
|
||||
|
||||
set_target_properties(qc_sqlite PROPERTIES VERSION "1.0.0")
|
||||
set_target_properties(qc_sqlite PROPERTIES LINK_FLAGS -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/qc_sqlite.map)
|
||||
|
@ -1,13 +1,13 @@
|
||||
add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c filter.cc externcmd.c paths.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c ssl.c mysql_utils.c mysql_binlog.c modulecmd.c )
|
||||
|
||||
target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++)
|
||||
|
||||
if(WITH_JEMALLOC)
|
||||
target_link_libraries(maxscale-common ${JEMALLOC_LIBRARIES})
|
||||
elseif(WITH_TCMALLOC)
|
||||
target_link_libraries(maxscale-common ${TCMALLOC_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++)
|
||||
|
||||
add_dependencies(maxscale-common pcre2 connector-c)
|
||||
set_target_properties(maxscale-common PROPERTIES VERSION "1.0.0")
|
||||
install_module(maxscale-common core)
|
||||
|
@ -143,7 +143,7 @@ static struct option long_options[] =
|
||||
static bool syslog_configured = false;
|
||||
static bool maxlog_configured = false;
|
||||
static bool log_to_shm_configured = false;
|
||||
static int last_signal = 0;
|
||||
static volatile sig_atomic_t last_signal = 0;
|
||||
|
||||
static int cnf_preparser(void* data, const char* section, const char* name, const char* value);
|
||||
static void log_flush_shutdown(void);
|
||||
@ -381,7 +381,7 @@ sigchld_handler (int i)
|
||||
}
|
||||
}
|
||||
|
||||
int fatal_handling = 0;
|
||||
volatile sig_atomic_t fatal_handling = 0;
|
||||
|
||||
static int signal_set(int sig, void (*handler)(int));
|
||||
|
||||
@ -405,12 +405,12 @@ sigfatal_handler(int i)
|
||||
|
||||
{
|
||||
void *addrs[128];
|
||||
int n, count = backtrace(addrs, 128);
|
||||
int count = backtrace(addrs, 128);
|
||||
char** symbols = backtrace_symbols(addrs, count);
|
||||
|
||||
if (symbols)
|
||||
{
|
||||
for (n = 0; n < count; n++)
|
||||
for (int n = 0; n < count; n++)
|
||||
{
|
||||
MXS_ALERT(" %s\n", symbols[n]);
|
||||
}
|
||||
@ -2023,6 +2023,9 @@ int main(int argc, char **argv)
|
||||
unlock_pidfile();
|
||||
unlink_pidfile();
|
||||
|
||||
ERR_free_strings();
|
||||
EVP_cleanup();
|
||||
|
||||
return_main:
|
||||
|
||||
mxs_log_flush_sync();
|
||||
@ -2277,6 +2280,12 @@ bool pid_file_exists()
|
||||
|
||||
static int write_pid_file()
|
||||
{
|
||||
if (!mxs_mkdir_all(get_piddir(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH))
|
||||
{
|
||||
MXS_ERROR("Failed to create PID directory.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
|
||||
char pidstr[STRING_BUFFER_SIZE];
|
||||
|
||||
|
@ -567,10 +567,11 @@ hashtable_read_lock(HASHTABLE *table)
|
||||
while (table->writelock)
|
||||
{
|
||||
spinlock_release(&table->spin);
|
||||
while (table->writelock)
|
||||
while (atomic_add(&table->writelock, 1) != 0)
|
||||
{
|
||||
;
|
||||
atomic_add(&table->writelock, -1);
|
||||
}
|
||||
atomic_add(&table->writelock, -1);
|
||||
spinlock_acquire(&table->spin);
|
||||
}
|
||||
atomic_add(&table->n_readers, 1);
|
||||
@ -614,10 +615,11 @@ hashtable_write_lock(HASHTABLE *table)
|
||||
spinlock_acquire(&table->spin);
|
||||
do
|
||||
{
|
||||
while (table->n_readers)
|
||||
while (atomic_add(&table->n_readers, 1) != 0)
|
||||
{
|
||||
;
|
||||
atomic_add(&table->n_readers, -1);
|
||||
}
|
||||
atomic_add(&table->n_readers, -1);
|
||||
available = atomic_add(&table->writelock, 1);
|
||||
if (available != 0)
|
||||
{
|
||||
|
@ -94,7 +94,7 @@ typedef struct fake_event
|
||||
struct fake_event *next; /*< The next event */
|
||||
} fake_event_t;
|
||||
|
||||
thread_local int thread_id; /**< This thread's ID */
|
||||
thread_local int current_thread_id; /**< This thread's ID */
|
||||
static int *epoll_fd; /*< The epoll file descriptor */
|
||||
static int next_epoll_fd = 0; /*< Which thread handles the next DCB */
|
||||
static fake_event_t **fake_events; /*< Thread-specific fake event queue */
|
||||
@ -656,9 +656,11 @@ poll_waitevents(void *arg)
|
||||
{
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
int i, nfds, timeout_bias = 1;
|
||||
thread_id = (intptr_t)arg;
|
||||
current_thread_id = (intptr_t)arg;
|
||||
int poll_spins = 0;
|
||||
|
||||
int thread_id = current_thread_id;
|
||||
|
||||
thread_data[thread_id].state = THREAD_IDLE;
|
||||
|
||||
while (1)
|
||||
@ -1613,7 +1615,7 @@ void poll_send_message(enum poll_message msg, void *data)
|
||||
|
||||
for (int i = 0; i < nthr; i++)
|
||||
{
|
||||
if (i != thread_id)
|
||||
if (i != current_thread_id)
|
||||
{
|
||||
while (poll_msg[i] & msg)
|
||||
{
|
||||
@ -1628,6 +1630,8 @@ void poll_send_message(enum poll_message msg, void *data)
|
||||
|
||||
static void poll_check_message()
|
||||
{
|
||||
int thread_id = current_thread_id;
|
||||
|
||||
if (poll_msg[thread_id] & POLL_MSG_CLEAN_PERSISTENT)
|
||||
{
|
||||
SERVER *server = (SERVER*)poll_msg_data;
|
||||
|
@ -10,7 +10,6 @@ add_subdirectory(nullfilter)
|
||||
add_subdirectory(qlafilter)
|
||||
add_subdirectory(regexfilter)
|
||||
add_subdirectory(tee)
|
||||
add_subdirectory(testfilter)
|
||||
add_subdirectory(topfilter)
|
||||
add_subdirectory(tpmfilter)
|
||||
add_subdirectory(masking)
|
||||
|
358
server/modules/filter/cache/cachefiltersession.cc
vendored
358
server/modules/filter/cache/cachefiltersession.cc
vendored
@ -15,8 +15,9 @@
|
||||
#include "cachefiltersession.hh"
|
||||
#include <new>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/mysql_utils.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include "storage.hh"
|
||||
|
||||
namespace
|
||||
@ -34,6 +35,108 @@ inline bool cache_max_resultset_size_exceeded(const CACHE_CONFIG& config, uint64
|
||||
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const char* NON_CACHEABLE_FUNCTIONS[] =
|
||||
{
|
||||
"benchmark",
|
||||
"connection_id",
|
||||
"convert_tz",
|
||||
"curdate",
|
||||
"current_date",
|
||||
"current_timestamp",
|
||||
"curtime",
|
||||
"database",
|
||||
"encrypt",
|
||||
"found_rows",
|
||||
"get_lock",
|
||||
"is_free_lock",
|
||||
"is_used_lock",
|
||||
"last_insert_id",
|
||||
"load_file",
|
||||
"localtime",
|
||||
"localtimestamp",
|
||||
"master_pos_wait",
|
||||
"now",
|
||||
"rand",
|
||||
"release_lock",
|
||||
"session_user",
|
||||
"sleep",
|
||||
"sysdate",
|
||||
"system_user",
|
||||
"unix_timestamp",
|
||||
"user",
|
||||
"uuid",
|
||||
"uuid_short",
|
||||
};
|
||||
|
||||
const char* NON_CACHEABLE_VARIABLES[] =
|
||||
{
|
||||
"current_date",
|
||||
"current_timestamp",
|
||||
"localtime",
|
||||
"localtimestamp",
|
||||
};
|
||||
|
||||
const size_t N_NON_CACHEABLE_FUNCTIONS = sizeof(NON_CACHEABLE_FUNCTIONS)/sizeof(NON_CACHEABLE_FUNCTIONS[0]);
|
||||
const size_t N_NON_CACHEABLE_VARIABLES = sizeof(NON_CACHEABLE_VARIABLES)/sizeof(NON_CACHEABLE_VARIABLES[0]);
|
||||
|
||||
int compare_name(const void* pLeft, const void* pRight)
|
||||
{
|
||||
return strcasecmp((const char*)pLeft, *(const char**)pRight);
|
||||
}
|
||||
|
||||
inline bool uses_name(const char* zName, const char** pzNames, size_t nNames)
|
||||
{
|
||||
return bsearch(zName, pzNames, nNames, sizeof(const char*), compare_name) != NULL;
|
||||
}
|
||||
|
||||
bool uses_non_cacheable_function(GWBUF* pPacket)
|
||||
{
|
||||
bool rv = false;
|
||||
|
||||
const QC_FUNCTION_INFO* pInfo;
|
||||
size_t nInfos;
|
||||
|
||||
qc_get_function_info(pPacket, &pInfo, &nInfos);
|
||||
|
||||
const QC_FUNCTION_INFO* pEnd = pInfo + nInfos;
|
||||
|
||||
while (!rv && (pInfo != pEnd))
|
||||
{
|
||||
rv = uses_name(pInfo->name, NON_CACHEABLE_FUNCTIONS, N_NON_CACHEABLE_FUNCTIONS);
|
||||
|
||||
++pInfo;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool uses_non_cacheable_variable(GWBUF* pPacket)
|
||||
{
|
||||
bool rv = false;
|
||||
|
||||
const QC_FIELD_INFO* pInfo;
|
||||
size_t nInfos;
|
||||
|
||||
qc_get_field_info(pPacket, &pInfo, &nInfos);
|
||||
|
||||
const QC_FIELD_INFO* pEnd = pInfo + nInfos;
|
||||
|
||||
while (!rv && (pInfo != pEnd))
|
||||
{
|
||||
rv = uses_name(pInfo->column, NON_CACHEABLE_VARIABLES, N_NON_CACHEABLE_VARIABLES);
|
||||
|
||||
++pInfo;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
CacheFilterSession::CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, char* zDefaultDb)
|
||||
: maxscale::FilterSession(pSession)
|
||||
, m_state(CACHE_EXPECTING_NOTHING)
|
||||
@ -41,6 +144,7 @@ CacheFilterSession::CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, cha
|
||||
, m_zDefaultDb(zDefaultDb)
|
||||
, m_zUseDb(NULL)
|
||||
, m_refreshing(false)
|
||||
, m_is_read_only(true)
|
||||
{
|
||||
memset(m_key.data, 0, CACHE_KEY_MAXLEN);
|
||||
|
||||
@ -129,105 +233,83 @@ int CacheFilterSession::routeQuery(GWBUF* pPacket)
|
||||
break;
|
||||
|
||||
case MYSQL_COM_QUERY:
|
||||
if (should_consult_cache(pPacket))
|
||||
{
|
||||
// We do not care whether the query was fully parsed or not.
|
||||
// If a query cannot be fully parsed, the worst thing that can
|
||||
// happen is that caching is not used, even though it would be
|
||||
// possible.
|
||||
if (qc_get_operation(pPacket) == QUERY_OP_SELECT)
|
||||
if (m_pCache->should_store(m_zDefaultDb, pPacket))
|
||||
{
|
||||
MXS_SESSION *session = m_pSession;
|
||||
|
||||
if ((session_is_autocommit(session) && !session_trx_is_active(session)) ||
|
||||
session_trx_is_read_only(session))
|
||||
if (m_pCache->should_use(m_pSession))
|
||||
{
|
||||
if (m_pCache->should_store(m_zDefaultDb, pPacket))
|
||||
GWBUF* pResponse;
|
||||
cache_result_t result = get_cached_response(pPacket, &pResponse);
|
||||
|
||||
if (CACHE_RESULT_IS_OK(result))
|
||||
{
|
||||
if (m_pCache->should_use(m_pSession))
|
||||
if (CACHE_RESULT_IS_STALE(result))
|
||||
{
|
||||
GWBUF* pResponse;
|
||||
cache_result_t result = get_cached_response(pPacket, &pResponse);
|
||||
// The value was found, but it was stale. Now we need to
|
||||
// figure out whether somebody else is already fetching it.
|
||||
|
||||
if (CACHE_RESULT_IS_OK(result))
|
||||
if (m_pCache->must_refresh(m_key, this))
|
||||
{
|
||||
if (CACHE_RESULT_IS_STALE(result))
|
||||
// We were the first ones who hit the stale item. It's
|
||||
// our responsibility now to fetch it.
|
||||
if (log_decisions())
|
||||
{
|
||||
// The value was found, but it was stale. Now we need to
|
||||
// figure out whether somebody else is already fetching it.
|
||||
|
||||
if (m_pCache->must_refresh(m_key, this))
|
||||
{
|
||||
// We were the first ones who hit the stale item. It's
|
||||
// our responsibility now to fetch it.
|
||||
if (log_decisions())
|
||||
{
|
||||
MXS_NOTICE("Cache data is stale, fetching fresh from server.");
|
||||
}
|
||||
|
||||
// As we don't use the response it must be freed.
|
||||
gwbuf_free(pResponse);
|
||||
|
||||
m_refreshing = true;
|
||||
fetch_from_server = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Somebody is already fetching the new value. So, let's
|
||||
// use the stale value. No point in hitting the server twice.
|
||||
if (log_decisions())
|
||||
{
|
||||
MXS_NOTICE("Cache data is stale but returning it, fresh "
|
||||
"data is being fetched already.");
|
||||
}
|
||||
fetch_from_server = false;
|
||||
}
|
||||
MXS_NOTICE("Cache data is stale, fetching fresh from server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
MXS_NOTICE("Using fresh data from cache.");
|
||||
}
|
||||
fetch_from_server = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// As we don't use the response it must be freed.
|
||||
gwbuf_free(pResponse);
|
||||
|
||||
m_refreshing = true;
|
||||
fetch_from_server = true;
|
||||
}
|
||||
|
||||
if (fetch_from_server)
|
||||
{
|
||||
m_state = CACHE_EXPECTING_RESPONSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = CACHE_EXPECTING_NOTHING;
|
||||
gwbuf_free(pPacket);
|
||||
DCB *dcb = m_pSession->client_dcb;
|
||||
|
||||
// TODO: This is not ok. Any filters before this filter, will not
|
||||
// TODO: see this data.
|
||||
rv = dcb->func.write(dcb, pResponse);
|
||||
// Somebody is already fetching the new value. So, let's
|
||||
// use the stale value. No point in hitting the server twice.
|
||||
if (log_decisions())
|
||||
{
|
||||
MXS_NOTICE("Cache data is stale but returning it, fresh "
|
||||
"data is being fetched already.");
|
||||
}
|
||||
fetch_from_server = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
MXS_NOTICE("Using fresh data from cache.");
|
||||
}
|
||||
fetch_from_server = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = CACHE_IGNORING_RESPONSE;
|
||||
fetch_from_server = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (log_decisions())
|
||||
|
||||
if (fetch_from_server)
|
||||
{
|
||||
MXS_NOTICE("autocommit = %s and transaction state %s => Not using or "
|
||||
"storing to cache.",
|
||||
session_is_autocommit(m_pSession) ? "ON" : "OFF",
|
||||
session_trx_state_to_string(session_get_trx_state(m_pSession)));
|
||||
m_state = CACHE_EXPECTING_RESPONSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = CACHE_EXPECTING_NOTHING;
|
||||
gwbuf_free(pPacket);
|
||||
DCB *dcb = m_pSession->client_dcb;
|
||||
|
||||
// TODO: This is not ok. Any filters before this filter, will not
|
||||
// TODO: see this data.
|
||||
rv = dcb->func.write(dcb, pResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = CACHE_IGNORING_RESPONSE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -664,3 +746,125 @@ void CacheFilterSession::store_result()
|
||||
m_refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the cache should be consulted.
|
||||
*
|
||||
* @param pParam The GWBUF being handled.
|
||||
*
|
||||
* @return True, if the cache should be consulted, false otherwise.
|
||||
*/
|
||||
bool CacheFilterSession::should_consult_cache(GWBUF* pPacket)
|
||||
{
|
||||
bool consult_cache = false;
|
||||
|
||||
uint32_t type_mask = qc_get_type_mask(pPacket);
|
||||
|
||||
const char* zReason = NULL;
|
||||
|
||||
if (qc_query_is_type(type_mask, QUERY_TYPE_BEGIN_TRX))
|
||||
{
|
||||
// When a transaction is started, we initially assume it is read-only.
|
||||
m_is_read_only = true;
|
||||
}
|
||||
else if (!qc_query_is_type(type_mask, QUERY_TYPE_READ))
|
||||
{
|
||||
// Thereafter, if there's any non-read statement we mark it as non-readonly.
|
||||
// Note that the state of m_is_read_only is not consulted if there is no
|
||||
// on-going transaction of if there is an explicitly read-only transaction.
|
||||
m_is_read_only = false;
|
||||
}
|
||||
|
||||
if (!session_trx_is_active(m_pSession))
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
zReason = "no transaction";
|
||||
}
|
||||
consult_cache = true;
|
||||
}
|
||||
else if (session_trx_is_read_only(m_pSession))
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
zReason = "explicitly read-only transaction";
|
||||
}
|
||||
consult_cache = true;
|
||||
}
|
||||
else if (m_is_read_only)
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
zReason = "ordinary transaction that has so far been read-only";
|
||||
}
|
||||
consult_cache = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (log_decisions())
|
||||
{
|
||||
zReason = "ordinary transaction with non-read statements";
|
||||
}
|
||||
}
|
||||
|
||||
if (consult_cache)
|
||||
{
|
||||
if (qc_get_operation(pPacket) == QUERY_OP_SELECT)
|
||||
{
|
||||
if (qc_query_is_type(type_mask, QUERY_TYPE_USERVAR_READ))
|
||||
{
|
||||
consult_cache = false;
|
||||
zReason = "user variables are read";
|
||||
}
|
||||
else if (qc_query_is_type(type_mask, QUERY_TYPE_SYSVAR_READ))
|
||||
{
|
||||
consult_cache = false;
|
||||
zReason = "system variables are read";
|
||||
}
|
||||
else if (uses_non_cacheable_function(pPacket))
|
||||
{
|
||||
consult_cache = false;
|
||||
zReason = "uses non-cacheable function";
|
||||
}
|
||||
else if (uses_non_cacheable_variable(pPacket))
|
||||
{
|
||||
consult_cache = false;
|
||||
zReason = "uses non-cacheable variable";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
consult_cache = false;
|
||||
zReason = "statement is not SELECT";
|
||||
}
|
||||
}
|
||||
|
||||
if (log_decisions())
|
||||
{
|
||||
char* pSql;
|
||||
int length;
|
||||
const int max_length = 40;
|
||||
|
||||
// At this point we know it's a COM_QUERY and that the buffer is contiguous
|
||||
modutil_extract_SQL(pPacket, &pSql, &length);
|
||||
|
||||
const char* zFormat;
|
||||
|
||||
if (length <= max_length)
|
||||
{
|
||||
zFormat = "%s, \"%.*s\", %s.";
|
||||
}
|
||||
else
|
||||
{
|
||||
zFormat = "%s, \"%.*s...\", %s.";
|
||||
length = max_length - 3; // strlen("...");
|
||||
}
|
||||
|
||||
const char* zDecision = (consult_cache ? "CONSULT" : "IGNORE ");
|
||||
|
||||
ss_dassert(zReason);
|
||||
MXS_NOTICE(zFormat, zDecision, length, pSql, zReason);
|
||||
}
|
||||
|
||||
return consult_cache;
|
||||
}
|
||||
|
@ -105,6 +105,8 @@ private:
|
||||
|
||||
void store_result();
|
||||
|
||||
bool should_consult_cache(GWBUF* pPacket);
|
||||
|
||||
private:
|
||||
CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, char* zDefaultDb);
|
||||
|
||||
@ -119,5 +121,6 @@ private:
|
||||
char* m_zDefaultDb; /**< The default database. */
|
||||
char* m_zUseDb; /**< Pending default database. Needs server response. */
|
||||
bool m_refreshing; /**< Whether the session is updating a stale cache entry. */
|
||||
bool m_is_read_only;/**< Whether the current trx has been read-only in pratice. */
|
||||
};
|
||||
|
||||
|
@ -147,6 +147,7 @@ typedef struct maxrows_response_state
|
||||
size_t n_fields; /**< How many fields we have received, <= n_totalfields. */
|
||||
size_t n_rows; /**< How many rows we have received. */
|
||||
size_t offset; /**< Where we are in the response buffer. */
|
||||
size_t rows_offset; /**< Offset to first row in result set */
|
||||
} MAXROWS_RESPONSE_STATE;
|
||||
|
||||
static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state);
|
||||
@ -174,7 +175,7 @@ static int handle_ignoring_response(MAXROWS_SESSION_DATA *csdata);
|
||||
static bool process_params(char **options, MXS_CONFIG_PARAMETER *params, MAXROWS_CONFIG* config);
|
||||
|
||||
static int send_upstream(MAXROWS_SESSION_DATA *csdata);
|
||||
static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata);
|
||||
static int send_eof_upstream(MAXROWS_SESSION_DATA *csdata, size_t offset);
|
||||
|
||||
/* API BEGIN */
|
||||
|
||||
@ -438,6 +439,7 @@ static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state)
|
||||
state->n_fields = 0;
|
||||
state->n_rows = 0;
|
||||
state->offset = 0;
|
||||
state->rows_offset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -511,6 +513,15 @@ static int handle_expecting_fields(MAXROWS_SESSION_DATA *csdata)
|
||||
{
|
||||
case 0xfe: // EOF, the one after the fields.
|
||||
csdata->res.offset += packetlen;
|
||||
|
||||
/* Now set the offset to the first resultset
|
||||
* this could be used for empty response handler
|
||||
*/
|
||||
if (!csdata->res.rows_offset)
|
||||
{
|
||||
csdata->res.rows_offset = csdata->res.offset;
|
||||
}
|
||||
|
||||
csdata->state = MAXROWS_EXPECTING_ROWS;
|
||||
rv = handle_rows(csdata);
|
||||
break;
|
||||
@ -599,7 +610,7 @@ static int handle_expecting_response(MAXROWS_SESSION_DATA *csdata)
|
||||
|
||||
if (csdata->discard_resultset)
|
||||
{
|
||||
rv = send_ok_upstream(csdata);
|
||||
rv = send_eof_upstream(csdata, csdata->res.rows_offset);
|
||||
csdata->state = MAXROWS_EXPECTING_NOTHING;
|
||||
}
|
||||
else
|
||||
@ -746,7 +757,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata)
|
||||
// Send data in buffer or empty resultset
|
||||
if (csdata->discard_resultset)
|
||||
{
|
||||
rv = send_ok_upstream(csdata);
|
||||
rv = send_eof_upstream(csdata, csdata->res.rows_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -779,7 +790,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata)
|
||||
if (packetlen < MYSQL_EOF_PACKET_LEN)
|
||||
{
|
||||
MXS_ERROR("EOF packet has size of %lu instead of %d", packetlen, MYSQL_EOF_PACKET_LEN);
|
||||
rv = send_ok_upstream(csdata);
|
||||
rv = send_eof_upstream(csdata, csdata->res.rows_offset);
|
||||
csdata->state = MAXROWS_EXPECTING_NOTHING;
|
||||
break;
|
||||
}
|
||||
@ -800,7 +811,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata)
|
||||
// Discard data or send data
|
||||
if (csdata->discard_resultset)
|
||||
{
|
||||
rv = send_ok_upstream(csdata);
|
||||
rv = send_eof_upstream(csdata, csdata->res.rows_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -897,23 +908,65 @@ static int send_upstream(MAXROWS_SESSION_DATA *csdata)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send OK packet data upstream.
|
||||
* Send upstream the Response Buffer up to columns def in response
|
||||
* including its EOF of the first result set
|
||||
* An EOF packet for empty result set with no MULTI flags is added
|
||||
* at the end.
|
||||
*
|
||||
* @param csdata Session data
|
||||
* @param csdata Session data
|
||||
* @param offset The offset to server reply pointing to
|
||||
* next byte after column definitions EOF
|
||||
* of the first result set.
|
||||
*
|
||||
* @return Whatever the upstream returns.
|
||||
*/
|
||||
static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata)
|
||||
static int send_eof_upstream(MAXROWS_SESSION_DATA *csdata, size_t offset)
|
||||
{
|
||||
/* Note: sequence id is always 01 (4th byte) */
|
||||
uint8_t ok[MYSQL_OK_PACKET_MIN_LEN] = {07, 00, 00, 01, 00, 00, 00, 02, 00, 00, 00};
|
||||
GWBUF *packet = gwbuf_alloc(MYSQL_OK_PACKET_MIN_LEN);
|
||||
uint8_t *ptr = GWBUF_DATA(packet);
|
||||
memcpy(ptr, &ok, MYSQL_OK_PACKET_MIN_LEN);
|
||||
int rv = -1;
|
||||
/* Sequence byte is #3 */
|
||||
uint8_t eof[MYSQL_EOF_PACKET_LEN] = {05, 00, 00, 01, 0xfe, 00, 00, 02, 00};
|
||||
GWBUF *new_pkt = NULL;
|
||||
|
||||
ss_dassert(csdata->res.data != NULL);
|
||||
|
||||
int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, packet);
|
||||
/* Data to send + added EOF */
|
||||
uint8_t *new_result = MXS_MALLOC(offset + MYSQL_EOF_PACKET_LEN);
|
||||
|
||||
if (new_result)
|
||||
{
|
||||
/* Get contiguous data from beginning to specified offset */
|
||||
gwbuf_copy_data(csdata->res.data, 0, offset, new_result);
|
||||
|
||||
/* Increment sequence number for the EOF being added for empty resultset:
|
||||
* last one if found in EOF terminating column def
|
||||
*/
|
||||
eof[3] = new_result[offset - (MYSQL_EOF_PACKET_LEN - 3)] + 1;
|
||||
|
||||
/* Copy EOF data */
|
||||
memcpy(new_result + offset, &eof, MYSQL_EOF_PACKET_LEN);
|
||||
|
||||
/* Create new packet */
|
||||
new_pkt = gwbuf_alloc_and_load(offset + MYSQL_EOF_PACKET_LEN, new_result);
|
||||
|
||||
/* Free intermediate data */
|
||||
MXS_FREE(new_result);
|
||||
|
||||
if (new_pkt)
|
||||
{
|
||||
/* new_pkt will be freed by write routine */
|
||||
rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, new_pkt);
|
||||
}
|
||||
}
|
||||
|
||||
/* Abort client connection */
|
||||
if (!(new_result && new_pkt))
|
||||
{
|
||||
/* Abort client connection */
|
||||
poll_fake_hangup_event(csdata->session->client_dcb);
|
||||
rv = 0;
|
||||
}
|
||||
|
||||
/* Free full input buffer */
|
||||
gwbuf_free(csdata->res.data);
|
||||
csdata->res.data = NULL;
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
add_library(testfilter SHARED testfilter.c)
|
||||
target_link_libraries(testfilter maxscale-common)
|
||||
set_target_properties(testfilter PROPERTIES VERSION "1.0.0")
|
||||
install_module(testfilter core)
|
@ -127,7 +127,7 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
||||
{"multimaster", MXS_MODULE_PARAM_BOOL, "false"},
|
||||
{"failover", MXS_MODULE_PARAM_BOOL, "false"},
|
||||
{"failcount", MXS_MODULE_PARAM_COUNT, "5"},
|
||||
{"failover_recovery", MXS_MODULE_PARAM_BOOL, "false"},
|
||||
{"failover_recovery", MXS_MODULE_PARAM_BOOL, "true"},
|
||||
{
|
||||
"script",
|
||||
MXS_MODULE_PARAM_PATH,
|
||||
@ -973,7 +973,7 @@ bool failover_required(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db)
|
||||
candidates++;
|
||||
MYSQL_SERVER_INFO *server_info = hashtable_fetch(handle->server_info, db->server->unique_name);
|
||||
|
||||
if (server_info->read_only || candidates > 1)
|
||||
if (server_info->read_only || server_info->slave_configured || candidates > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -8,6 +8,3 @@ add_subdirectory(maxscaled)
|
||||
add_subdirectory(MySQL)
|
||||
add_subdirectory(telnetd)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(testprotocol)
|
||||
endif()
|
||||
|
@ -189,12 +189,10 @@ static int gw_create_backend_connection(DCB *backend_dcb,
|
||||
/** Copy client flags to backend protocol */
|
||||
if (backend_dcb->session->client_dcb->protocol)
|
||||
{
|
||||
/** Copy client flags to backend protocol */
|
||||
protocol->client_capabilities =
|
||||
((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->client_capabilities;
|
||||
/** Copy client charset to backend protocol */
|
||||
protocol->charset =
|
||||
((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->charset;
|
||||
MySQLProtocol *client = (MySQLProtocol*)backend_dcb->session->client_dcb->protocol;
|
||||
protocol->client_capabilities = client->client_capabilities;
|
||||
protocol->charset = client->charset;
|
||||
protocol->extra_capabilities = client->extra_capabilities;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -224,16 +224,25 @@ int MySQLSendHandshake(DCB* dcb)
|
||||
uint8_t mysql_server_language = 8;
|
||||
uint8_t mysql_server_status[2];
|
||||
uint8_t mysql_scramble_len = 21;
|
||||
uint8_t mysql_filler_ten[10];
|
||||
uint8_t mysql_filler_ten[10] = {};
|
||||
/* uint8_t mysql_last_byte = 0x00; not needed */
|
||||
char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1] = "";
|
||||
char *version_string;
|
||||
int len_version_string = 0;
|
||||
int id_num;
|
||||
|
||||
bool is_maria = false;
|
||||
|
||||
if (dcb->service->dbref)
|
||||
{
|
||||
mysql_server_language = dcb->service->dbref->server->charset;
|
||||
|
||||
if (dcb->service->dbref->server->server_string &&
|
||||
strstr(dcb->service->dbref->server->server_string, "10.2."))
|
||||
{
|
||||
/** The backend servers support the extended capabilities */
|
||||
is_maria = true;
|
||||
}
|
||||
}
|
||||
|
||||
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
@ -256,9 +265,15 @@ int MySQLSendHandshake(DCB* dcb)
|
||||
// copy back to the caller
|
||||
memcpy(protocol->scramble, server_scramble, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
|
||||
// fill the handshake packet
|
||||
|
||||
memset(mysql_filler_ten, 0x00, sizeof(mysql_filler_ten));
|
||||
if (is_maria)
|
||||
{
|
||||
/**
|
||||
* The new 10.2 capability flags are stored in the last 4 bytes of the
|
||||
* 10 byte filler block.
|
||||
*/
|
||||
uint32_t new_flags = MXS_MARIA_CAP_STMT_BULK_OPERATIONS;
|
||||
memcpy(mysql_filler_ten + 6, &new_flags, sizeof(new_flags));
|
||||
}
|
||||
|
||||
// thread id, now put thePID
|
||||
id_num = getpid() + dcb->fd;
|
||||
@ -324,11 +339,19 @@ int MySQLSendHandshake(DCB* dcb)
|
||||
mysql_handshake_payload++;
|
||||
|
||||
// write server capabilities part one
|
||||
mysql_server_capabilities_one[0] = GW_MYSQL_SERVER_CAPABILITIES_BYTE1;
|
||||
mysql_server_capabilities_one[1] = GW_MYSQL_SERVER_CAPABILITIES_BYTE2;
|
||||
mysql_server_capabilities_one[0] = (uint8_t)GW_MYSQL_CAPABILITIES_SERVER;
|
||||
mysql_server_capabilities_one[1] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 8);
|
||||
|
||||
// Check that we match the old values
|
||||
ss_dassert(mysql_server_capabilities_one[0] = 0xff);
|
||||
ss_dassert(mysql_server_capabilities_one[1] = 0xf7);
|
||||
|
||||
mysql_server_capabilities_one[0] &= ~(int)GW_MYSQL_CAPABILITIES_COMPRESS;
|
||||
if (is_maria)
|
||||
{
|
||||
/** A MariaDB 10.2 server doesn't send the CLIENT_MYSQL capability
|
||||
* to signal that it supports extended capabilities */
|
||||
mysql_server_capabilities_one[0] &= ~(uint8_t)GW_MYSQL_CAPABILITIES_CLIENT_MYSQL;
|
||||
}
|
||||
|
||||
if (ssl_required_by_dcb(dcb))
|
||||
{
|
||||
@ -349,8 +372,13 @@ int MySQLSendHandshake(DCB* dcb)
|
||||
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_status);
|
||||
|
||||
//write server capabilities part two
|
||||
mysql_server_capabilities_two[0] = 15;
|
||||
mysql_server_capabilities_two[1] = 128;
|
||||
mysql_server_capabilities_two[0] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 16);
|
||||
mysql_server_capabilities_two[1] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 24);
|
||||
|
||||
// Check that we match the old values
|
||||
ss_dassert(mysql_server_capabilities_two[0] == 15);
|
||||
/** NOTE: pre-2.1 versions sent the fourth byte of the capabilities as
|
||||
the value 128 even though there's no such capability. */
|
||||
|
||||
memcpy(mysql_handshake_payload, mysql_server_capabilities_two, sizeof(mysql_server_capabilities_two));
|
||||
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_two);
|
||||
@ -531,6 +559,13 @@ static void store_client_information(DCB *dcb, GWBUF *buffer)
|
||||
proto->client_capabilities = gw_mysql_get_byte4(data + MYSQL_CLIENT_CAP_OFFSET);
|
||||
proto->charset = data[MYSQL_CHARSET_OFFSET];
|
||||
|
||||
/** MariaDB 10.2 compatible clients don't set the first bit to signal that
|
||||
* there are extra capabilities stored in the last 4 bytes of the 23 byte filler. */
|
||||
if ((proto->client_capabilities & GW_MYSQL_CAPABILITIES_CLIENT_MYSQL) == 0)
|
||||
{
|
||||
proto->extra_capabilities = gw_mysql_get_byte4(data + MARIADB_CAP_OFFSET);
|
||||
}
|
||||
|
||||
if (len > MYSQL_AUTH_PACKET_BASE_SIZE)
|
||||
{
|
||||
strcpy(ses->user, (char*)data + MYSQL_AUTH_PACKET_BASE_SIZE);
|
||||
@ -825,7 +860,12 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe
|
||||
}
|
||||
|
||||
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||
proto->current_command = cmd;
|
||||
if (dcb->protocol_packet_length - MYSQL_HEADER_LEN != GW_MYSQL_MAX_PACKET_LEN)
|
||||
{
|
||||
/** We're processing the first packet of a command */
|
||||
proto->current_command = cmd;
|
||||
}
|
||||
|
||||
dcb->protocol_packet_length = pktlen + MYSQL_HEADER_LEN;
|
||||
dcb->protocol_bytes_processed = 0;
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd)
|
||||
p->protocol_command.scom_nresponse_packets = 0;
|
||||
p->protocol_command.scom_nbytes_to_read = 0;
|
||||
p->stored_query = NULL;
|
||||
p->extra_capabilities = 0;
|
||||
#if defined(SS_DEBUG)
|
||||
p->protocol_chk_top = CHK_NUM_PROTOCOL;
|
||||
p->protocol_chk_tail = CHK_NUM_PROTOCOL;
|
||||
@ -1364,8 +1365,12 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb)
|
||||
|
||||
payload++;
|
||||
|
||||
// 23 bytes of 0
|
||||
payload += 23;
|
||||
// 19 filler bytes of 0
|
||||
payload += 19;
|
||||
|
||||
// Either MariaDB 10.2 extra capabilities or 4 bytes filler
|
||||
memcpy(payload, &conn->extra_capabilities, sizeof(conn->extra_capabilities));
|
||||
payload += 4;
|
||||
|
||||
if (dcb->server->server_ssl && dcb->ssl_state != SSL_ESTABLISHED)
|
||||
{
|
||||
|
@ -1,3 +0,0 @@
|
||||
add_library(testprotocol SHARED testprotocol.c)
|
||||
set_target_properties(testprotocol PROPERTIES VERSION "1.0.0")
|
||||
install_module(testprotocol core)
|
@ -12,6 +12,4 @@ add_subdirectory(readconnroute)
|
||||
add_subdirectory(readwritesplit)
|
||||
add_subdirectory(schemarouter)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(testroute)
|
||||
endif()
|
||||
|
||||
|
@ -72,14 +72,14 @@ static const char* alter_table_regex =
|
||||
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue,
|
||||
static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message,
|
||||
static void errorReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *message,
|
||||
DCB *backend_dcb, mxs_error_action_t action, bool *succp);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
extern int MaxScaleUptime();
|
||||
@ -630,7 +630,7 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
AVRO_INSTANCE *inst = (AVRO_INSTANCE *) instance;
|
||||
@ -703,7 +703,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @param router_cli_ses The particular session to free
|
||||
*
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses)
|
||||
static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_ses)
|
||||
{
|
||||
AVRO_INSTANCE *router = (AVRO_INSTANCE *) router_instance;
|
||||
AVRO_CLIENT *client = (AVRO_CLIENT *) router_client_ses;
|
||||
@ -752,7 +752,7 @@ static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses)
|
||||
* @param instance The router instance data
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
AVRO_INSTANCE *router = (AVRO_INSTANCE *) instance;
|
||||
AVRO_CLIENT *client = (AVRO_CLIENT *) router_session;
|
||||
@ -784,7 +784,7 @@ static void closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @return 1 on success, 0 on error
|
||||
*/
|
||||
static int
|
||||
routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
AVRO_INSTANCE *router = (AVRO_INSTANCE *) instance;
|
||||
AVRO_CLIENT *client = (AVRO_CLIENT *) router_session;
|
||||
@ -960,7 +960,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb)
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void
|
||||
clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
{
|
||||
/** We should never end up here */
|
||||
ss_dassert(false);
|
||||
@ -1002,7 +1002,7 @@ extract_message(GWBUF *errpkt)
|
||||
*
|
||||
*/
|
||||
static void
|
||||
errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb,
|
||||
errorReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *message, DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp)
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ static bool warn_large_enumset = false; /**< Remove when support for ENUM/SET va
|
||||
|
||||
uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create,
|
||||
avro_value_t *record, uint8_t *ptr,
|
||||
uint8_t *columns_present);
|
||||
uint8_t *columns_present, uint8_t *end);
|
||||
void notify_all_clients(AVRO_INSTANCE *router);
|
||||
void add_used_table(AVRO_INSTANCE* router, const char* table);
|
||||
|
||||
@ -309,9 +309,10 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr)
|
||||
while (ptr - start < hdr->event_size - BINLOG_EVENT_HDR_LEN)
|
||||
{
|
||||
/** Add the current GTID and timestamp */
|
||||
uint8_t *end = ptr + hdr->event_size;
|
||||
int event_type = get_event_type(hdr->event_type);
|
||||
prepare_record(router, hdr, event_type, &record);
|
||||
ptr = process_row_event_data(map, create, &record, ptr, col_present);
|
||||
ptr = process_row_event_data(map, create, &record, ptr, col_present, end);
|
||||
avro_file_writer_append_value(table->avro_file, &record);
|
||||
|
||||
/** Update rows events have the before and after images of the
|
||||
@ -320,7 +321,7 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr)
|
||||
if (event_type == UPDATE_EVENT)
|
||||
{
|
||||
prepare_record(router, hdr, UPDATE_EVENT_AFTER, &record);
|
||||
ptr = process_row_event_data(map, create, &record, ptr, col_present);
|
||||
ptr = process_row_event_data(map, create, &record, ptr, col_present, end);
|
||||
avro_file_writer_append_value(table->avro_file, &record);
|
||||
}
|
||||
|
||||
@ -497,7 +498,7 @@ int get_metadata_len(uint8_t type)
|
||||
* @return Pointer to the first byte after the current row event
|
||||
*/
|
||||
uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value_t *record,
|
||||
uint8_t *ptr, uint8_t *columns_present)
|
||||
uint8_t *ptr, uint8_t *columns_present, uint8_t *end)
|
||||
{
|
||||
int npresent = 0;
|
||||
avro_value_t field;
|
||||
@ -507,10 +508,12 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
|
||||
/** BIT type values use the extra bits in the row event header */
|
||||
int extra_bits = (((ncolumns + 7) / 8) * 8) - ncolumns;
|
||||
ss_dassert(ptr < end);
|
||||
|
||||
/** Store the null value bitmap */
|
||||
uint8_t *null_bitmap = ptr;
|
||||
ptr += (ncolumns + 7) / 8;
|
||||
ss_dassert(ptr < end);
|
||||
|
||||
for (long i = 0; i < map->columns && npresent < ncolumns; i++)
|
||||
{
|
||||
@ -544,6 +547,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
}
|
||||
avro_value_set_string(&field, strval);
|
||||
ptr += bytes;
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -553,6 +557,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
str[bytes] = '\0';
|
||||
avro_value_set_string(&field, str);
|
||||
ptr += bytes + 1;
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
}
|
||||
else if (column_is_bit(map->column_types[i]))
|
||||
@ -572,21 +577,36 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
}
|
||||
avro_value_set_int(&field, value);
|
||||
ptr += bytes;
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
else if (column_is_decimal(map->column_types[i]))
|
||||
{
|
||||
double f_value = 0.0;
|
||||
ptr += unpack_decimal_field(ptr, metadata + metadata_offset, &f_value);
|
||||
avro_value_set_double(&field, f_value);
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
else if (column_is_variable_string(map->column_types[i]))
|
||||
{
|
||||
size_t sz;
|
||||
char *str = mxs_lestr_consume(&ptr, &sz);
|
||||
int bytes = metadata[metadata_offset] | metadata[metadata_offset + 1] << 8;
|
||||
if (bytes > 255)
|
||||
{
|
||||
sz = gw_mysql_get_byte2(ptr);
|
||||
ptr += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
sz = *ptr;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
char buf[sz + 1];
|
||||
memcpy(buf, str, sz);
|
||||
memcpy(buf, ptr, sz);
|
||||
buf[sz] = '\0';
|
||||
ptr += sz;
|
||||
avro_value_set_string(&field, buf);
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
else if (column_is_blob(map->column_types[i]))
|
||||
{
|
||||
@ -596,6 +616,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
ptr += bytes;
|
||||
avro_value_set_bytes(&field, ptr, len);
|
||||
ptr += len;
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
else if (column_is_temporal(map->column_types[i]))
|
||||
{
|
||||
@ -604,6 +625,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
ptr += unpack_temporal_value(map->column_types[i], ptr, &metadata[metadata_offset], &tm);
|
||||
format_temporal_value(buf, sizeof(buf), map->column_types[i], &tm);
|
||||
avro_value_set_string(&field, buf);
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
/** All numeric types (INT, LONG, FLOAT etc.) */
|
||||
else
|
||||
@ -613,6 +635,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
ptr += unpack_numeric_field(ptr, map->column_types[i],
|
||||
&metadata[metadata_offset], lval);
|
||||
set_numeric_field_value(&field, map->column_types[i], &metadata[metadata_offset], lval);
|
||||
ss_dassert(ptr < end);
|
||||
}
|
||||
ss_dassert(metadata_offset <= map->column_metadata_size);
|
||||
metadata_offset += get_metadata_len(map->column_types[i]);
|
||||
|
@ -449,6 +449,10 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size)
|
||||
while (*ptr && (isspace(*ptr) || (bt = *ptr == '`')))
|
||||
{
|
||||
ptr++;
|
||||
if (bt)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 ||
|
||||
@ -481,11 +485,6 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size)
|
||||
/** Valid identifier */
|
||||
size_t bytes = ptr - start;
|
||||
|
||||
if (bt)
|
||||
{
|
||||
bytes--;
|
||||
}
|
||||
|
||||
memcpy(dest, start, bytes);
|
||||
dest[bytes] = '\0';
|
||||
|
||||
|
@ -90,21 +90,21 @@
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void free_instance(ROUTER_INSTANCE *instance);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void errorReply(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *message,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void errorReply(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *message,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static int blr_handler_config(void *userdata, const char *section, const char *name, const char *value);
|
||||
@ -952,7 +952,7 @@ free_instance(ROUTER_INSTANCE *instance)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance;
|
||||
@ -1022,7 +1022,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
*
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER* router_instance,
|
||||
void* router_client_ses)
|
||||
MXS_ROUTER_SESSION* router_client_ses)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_client_ses;
|
||||
@ -1090,7 +1090,7 @@ static void freeSession(MXS_ROUTER* router_instance,
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
|
||||
@ -1176,7 +1176,7 @@ closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @return The number of bytes sent
|
||||
*/
|
||||
static int
|
||||
routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
|
||||
@ -1680,7 +1680,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb)
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void
|
||||
clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
|
||||
@ -1726,7 +1726,7 @@ extract_message(GWBUF *errpkt)
|
||||
*/
|
||||
static void
|
||||
errorReply(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *message,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
|
@ -2463,6 +2463,7 @@ blr_file_write_master_config(ROUTER_INSTANCE *router, char *error)
|
||||
|
||||
if (chmod(tmp_file, S_IRUSR | S_IWUSR) < 0)
|
||||
{
|
||||
fclose(config_file);
|
||||
snprintf(error, BINLOG_ERROR_MSG_LEN, "%s, errno %u",
|
||||
strerror_r(errno, err_msg, sizeof(err_msg)), errno);
|
||||
return 2;
|
||||
|
@ -44,11 +44,11 @@
|
||||
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
|
||||
extern int execute_cmd(CLI_SESSION *cli);
|
||||
@ -156,7 +156,7 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
CLI_INSTANCE *inst = (CLI_INSTANCE *)instance;
|
||||
@ -188,7 +188,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
CLI_INSTANCE *inst = (CLI_INSTANCE *)instance;
|
||||
CLI_SESSION *session = (CLI_SESSION *)router_session;
|
||||
@ -224,9 +224,8 @@ closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @param router_instance The router session
|
||||
* @param router_client_session The router session as returned from newSession
|
||||
*/
|
||||
static void freeSession(
|
||||
MXS_ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
static void freeSession(MXS_ROUTER* router_instance,
|
||||
MXS_ROUTER_SESSION* router_client_session)
|
||||
{
|
||||
MXS_FREE(router_client_session);
|
||||
return;
|
||||
@ -243,7 +242,7 @@ static void freeSession(
|
||||
* @return The number of bytes sent
|
||||
*/
|
||||
static int
|
||||
execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
CLI_SESSION *session = (CLI_SESSION *)router_session;
|
||||
|
||||
|
@ -43,11 +43,11 @@
|
||||
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
|
||||
extern int execute_cmd(CLI_SESSION *cli);
|
||||
@ -147,7 +147,7 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
CLI_INSTANCE *inst = (CLI_INSTANCE *)instance;
|
||||
@ -182,7 +182,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
CLI_INSTANCE *inst = (CLI_INSTANCE *)instance;
|
||||
CLI_SESSION *session = (CLI_SESSION *)router_session;
|
||||
@ -218,9 +218,8 @@ closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @param router_instance The router session
|
||||
* @param router_client_session The router session as returned from newSession
|
||||
*/
|
||||
static void freeSession(
|
||||
MXS_ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
static void freeSession(MXS_ROUTER* router_instance,
|
||||
MXS_ROUTER_SESSION* router_client_session)
|
||||
{
|
||||
MXS_FREE(router_client_session);
|
||||
return;
|
||||
@ -237,7 +236,7 @@ static void freeSession(
|
||||
* @return The number of bytes sent
|
||||
*/
|
||||
static int
|
||||
execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
CLI_SESSION *session = (CLI_SESSION *)router_session;
|
||||
|
||||
|
@ -65,18 +65,18 @@ static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *router_session, GWB
|
||||
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp);
|
||||
|
||||
static SPINLOCK instlock;
|
||||
static INFO_INSTANCE *instances;
|
||||
@ -180,7 +180,7 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
|
||||
@ -212,7 +212,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
|
||||
INFO_SESSION *session = (INFO_SESSION *)router_session;
|
||||
@ -249,7 +249,7 @@ closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @param router_client_session The router session as returned from newSession
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
MXS_ROUTER_SESSION* router_client_session)
|
||||
{
|
||||
MXS_FREE(router_client_session);
|
||||
return;
|
||||
@ -268,12 +268,12 @@ static void freeSession(MXS_ROUTER* router_instance,
|
||||
* @param succp Result of action: true iff router can continue
|
||||
*
|
||||
*/
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp)
|
||||
bool *succp)
|
||||
|
||||
{
|
||||
DCB *client_dcb;
|
||||
@ -316,7 +316,7 @@ static void handleError(MXS_ROUTER *instance,
|
||||
* @return The number of bytes sent
|
||||
*/
|
||||
static int
|
||||
execute(MXS_ROUTER *rinstance, void *router_session, GWBUF *queue)
|
||||
execute(MXS_ROUTER *rinstance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
|
||||
INFO_SESSION *session = (INFO_SESSION *)router_session;
|
||||
|
@ -89,14 +89,14 @@
|
||||
|
||||
/* The router entry points */
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue,
|
||||
static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void handleError(MXS_ROUTER *instance, void *router_session, GWBUF *errbuf,
|
||||
static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errbuf,
|
||||
DCB *problem_dcb, mxs_error_action_t action, bool *succp);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses);
|
||||
@ -257,7 +257,7 @@ createInstance(SERVICE *service, char **options)
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
static MXS_ROUTER_SESSION *
|
||||
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance;
|
||||
@ -450,7 +450,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session)
|
||||
* @details (write detailed description here)
|
||||
*
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses)
|
||||
static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_ses)
|
||||
{
|
||||
ROUTER_INSTANCE* router = (ROUTER_INSTANCE *) router_instance;
|
||||
ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *) router_client_ses;
|
||||
@ -469,7 +469,7 @@ static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses)
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *) router_session;
|
||||
DCB* backend_dcb;
|
||||
@ -537,7 +537,7 @@ static void log_closed_session(mysql_server_cmd_t mysql_command, bool is_closed,
|
||||
* @return if succeed 1, otherwise 0
|
||||
*/
|
||||
static int
|
||||
routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
|
||||
{
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance;
|
||||
ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *) router_session;
|
||||
@ -656,7 +656,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb)
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void
|
||||
clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
{
|
||||
ss_dassert(backend_dcb->session->client_dcb != NULL);
|
||||
MXS_SESSION_ROUTE_REPLY(backend_dcb->session, queue);
|
||||
@ -675,7 +675,7 @@ clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backe
|
||||
* @param succp Result of action: true if router can continue
|
||||
*
|
||||
*/
|
||||
static void handleError(MXS_ROUTER *instance, void *router_session, GWBUF *errbuf,
|
||||
static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errbuf,
|
||||
DCB *problem_dcb, mxs_error_action_t action, bool *succp)
|
||||
|
||||
{
|
||||
|
@ -69,14 +69,14 @@
|
||||
*/
|
||||
|
||||
static MXS_ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue);
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue);
|
||||
static void diagnostics(MXS_ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue,
|
||||
static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void handleError(MXS_ROUTER *instance, void *router_session,
|
||||
static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errmsgbuf, DCB *backend_dcb,
|
||||
mxs_error_action_t action, bool *succp);
|
||||
static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
@ -250,7 +250,6 @@ static MXS_ROUTER *createInstance(SERVICE *service, char **options)
|
||||
return NULL;
|
||||
}
|
||||
router->service = service;
|
||||
spinlock_init(&router->lock);
|
||||
|
||||
/*
|
||||
* Until we know otherwise assume we have some available slaves.
|
||||
@ -313,7 +312,7 @@ static MXS_ROUTER *createInstance(SERVICE *service, char **options)
|
||||
* @param session The MaxScale session (generic connection data)
|
||||
* @return Session specific data for this session, i.e. a router session
|
||||
*/
|
||||
static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session)
|
||||
static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_inst;
|
||||
ROUTER_CLIENT_SES *client_rses = (ROUTER_CLIENT_SES *)MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES));
|
||||
@ -331,7 +330,6 @@ static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session)
|
||||
client_rses->client_dcb = session->client_dcb;
|
||||
client_rses->have_tmp_tables = false;
|
||||
client_rses->forced_node = NULL;
|
||||
spinlock_init(&client_rses->rses_lock);
|
||||
memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(client_rses->rses_config));
|
||||
|
||||
int router_nservers = router->service->n_dbref;
|
||||
@ -404,12 +402,12 @@ static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session)
|
||||
* @param instance The router instance data
|
||||
* @param session The router session being closed
|
||||
*/
|
||||
static void closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
{
|
||||
ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session;
|
||||
CHK_CLIENT_RSES(router_cli_ses);
|
||||
|
||||
if (!router_cli_ses->rses_closed && rses_begin_locked_router_action(router_cli_ses))
|
||||
if (!router_cli_ses->rses_closed)
|
||||
{
|
||||
/**
|
||||
* Mark router session as closed. @c rses_closed is checked at the start
|
||||
@ -465,8 +463,6 @@ static void closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +476,7 @@ static void closeSession(MXS_ROUTER *instance, void *router_session)
|
||||
* @param router_client_session Client session
|
||||
*
|
||||
*/
|
||||
static void freeSession(MXS_ROUTER *router_instance, void *router_client_session)
|
||||
static void freeSession(MXS_ROUTER *router_instance, MXS_ROUTER_SESSION *router_client_session)
|
||||
{
|
||||
ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session;
|
||||
|
||||
@ -553,7 +549,7 @@ void close_failed_bref(backend_ref_t *bref, bool fatal)
|
||||
* @param querybuf Buffer containing the query
|
||||
* @return 1 on success, 0 on error
|
||||
*/
|
||||
static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *querybuf)
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *querybuf)
|
||||
{
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance;
|
||||
ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *) router_session;
|
||||
@ -665,34 +661,27 @@ static void diagnostics(MXS_ROUTER *instance, DCB *dcb)
|
||||
* @param backend_dcb The backend DCB
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *writebuf,
|
||||
static void clientReply(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *writebuf,
|
||||
DCB *backend_dcb)
|
||||
{
|
||||
DCB *client_dcb;
|
||||
ROUTER_INSTANCE *router_inst;
|
||||
ROUTER_CLIENT_SES *router_cli_ses;
|
||||
sescmd_cursor_t *scur = NULL;
|
||||
backend_ref_t *bref;
|
||||
ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session;
|
||||
ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)instance;
|
||||
DCB *client_dcb = backend_dcb->session->client_dcb;
|
||||
|
||||
router_cli_ses = (ROUTER_CLIENT_SES *)router_session;
|
||||
router_inst = (ROUTER_INSTANCE *)instance;
|
||||
CHK_CLIENT_RSES(router_cli_ses);
|
||||
|
||||
/**
|
||||
* Lock router client session for secure read of router session members.
|
||||
* Note that this could be done without lock by using version #
|
||||
*/
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
if (router_cli_ses->rses_closed)
|
||||
{
|
||||
gwbuf_free(writebuf);
|
||||
goto lock_failed;
|
||||
return;
|
||||
}
|
||||
/** Holding lock ensures that router session remains open */
|
||||
ss_dassert(backend_dcb->session != NULL);
|
||||
client_dcb = backend_dcb->session->client_dcb;
|
||||
|
||||
/** Unlock */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
/**
|
||||
* 1. Check if backend received reply to sescmd.
|
||||
* 2. Check sescmd's state whether OK_PACKET has been
|
||||
@ -702,32 +691,10 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write
|
||||
* 3. If reply for this sescmd is sent, lock property cursor
|
||||
* and
|
||||
*/
|
||||
if (client_dcb == NULL)
|
||||
{
|
||||
gwbuf_free(writebuf);
|
||||
/** Log that client was closed before reply */
|
||||
goto lock_failed;
|
||||
}
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
/** Log to debug that router was closed */
|
||||
goto lock_failed;
|
||||
}
|
||||
bref = get_bref_from_dcb(router_cli_ses, backend_dcb);
|
||||
|
||||
#if !defined(FOR_BUG548_FIX_ONLY)
|
||||
/** This makes the issue becoming visible in poll.c */
|
||||
if (bref == NULL)
|
||||
{
|
||||
/** Unlock router session */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
goto lock_failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
backend_ref_t *bref = get_bref_from_dcb(router_cli_ses, backend_dcb);
|
||||
CHK_BACKEND_REF(bref);
|
||||
scur = &bref->bref_sescmd_cur;
|
||||
sescmd_cursor_t *scur = &bref->bref_sescmd_cur;
|
||||
|
||||
/** Statement was successfully executed, free the stored statement */
|
||||
session_clear_stmt(backend_dcb->session);
|
||||
@ -788,15 +755,7 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write
|
||||
/** Write reply to client DCB */
|
||||
MXS_SESSION_ROUTE_REPLY(backend_dcb->session, writebuf);
|
||||
}
|
||||
/** Unlock router session */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
/** Log to debug that router was closed */
|
||||
goto lock_failed;
|
||||
}
|
||||
/** There is one pending session command to be executed. */
|
||||
if (sescmd_cursor_is_active(scur))
|
||||
{
|
||||
@ -847,11 +806,6 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write
|
||||
gwbuf_free(bref->bref_pending_cmd);
|
||||
bref->bref_pending_cmd = NULL;
|
||||
}
|
||||
/** Unlock router session */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
|
||||
lock_failed:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -876,64 +830,6 @@ static uint64_t getCapabilities(MXS_ROUTER* instance)
|
||||
* functions are not intended for use outside the read write split router.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Acquires lock to router client session if it is not closed.
|
||||
*
|
||||
* Parameters:
|
||||
* @param rses - in, use
|
||||
*
|
||||
*
|
||||
* @return true if router session was not closed. If return value is true
|
||||
* it means that router is locked, and must be unlocked later. False, if
|
||||
* router was closed before lock was acquired.
|
||||
*
|
||||
*/
|
||||
bool rses_begin_locked_router_action(ROUTER_CLIENT_SES *rses)
|
||||
{
|
||||
bool succp = false;
|
||||
|
||||
if (rses == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CHK_CLIENT_RSES(rses);
|
||||
|
||||
if (rses->rses_closed)
|
||||
{
|
||||
|
||||
goto return_succp;
|
||||
}
|
||||
spinlock_acquire(&rses->rses_lock);
|
||||
if (rses->rses_closed)
|
||||
{
|
||||
spinlock_release(&rses->rses_lock);
|
||||
goto return_succp;
|
||||
}
|
||||
succp = true;
|
||||
|
||||
return_succp:
|
||||
return succp;
|
||||
}
|
||||
|
||||
/** to be inline'd */
|
||||
|
||||
/**
|
||||
* @brief Releases router client session lock.
|
||||
*
|
||||
* Parameters:
|
||||
* @param rses - <usage>
|
||||
* <description>
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
void rses_end_locked_router_action(ROUTER_CLIENT_SES *rses)
|
||||
{
|
||||
CHK_CLIENT_RSES(rses);
|
||||
spinlock_release(&rses->rses_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Clear one or more bits in the backend reference state
|
||||
*
|
||||
@ -1135,28 +1031,21 @@ int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses)
|
||||
*/
|
||||
backend_ref_t *get_bref_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb)
|
||||
{
|
||||
backend_ref_t *bref;
|
||||
int i = 0;
|
||||
ss_dassert(dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER);
|
||||
CHK_DCB(dcb);
|
||||
CHK_CLIENT_RSES(rses);
|
||||
|
||||
bref = rses->rses_backend_ref;
|
||||
|
||||
while (i < rses->rses_nbackends)
|
||||
for (int i = 0; i < rses->rses_nbackends; i++)
|
||||
{
|
||||
if (bref->bref_dcb == dcb)
|
||||
if (rses->rses_backend_ref[i].bref_dcb == dcb)
|
||||
{
|
||||
break;
|
||||
return &rses->rses_backend_ref[i];
|
||||
}
|
||||
bref++;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i == rses->rses_nbackends)
|
||||
{
|
||||
bref = NULL;
|
||||
}
|
||||
return bref;
|
||||
/** We should always have a valid backend reference */
|
||||
ss_dassert(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1284,17 +1173,19 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router,
|
||||
* 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(MXS_ROUTER *instance, void *router_session,
|
||||
GWBUF *errmsgbuf, DCB *problem_dcb,
|
||||
mxs_error_action_t action, bool *succp)
|
||||
static void handleError(MXS_ROUTER *instance,
|
||||
MXS_ROUTER_SESSION *router_session,
|
||||
GWBUF *errmsgbuf,
|
||||
DCB *problem_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool *succp)
|
||||
{
|
||||
MXS_SESSION *session;
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session;
|
||||
|
||||
CHK_CLIENT_RSES(rses);
|
||||
CHK_DCB(problem_dcb);
|
||||
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
if (rses->rses_closed)
|
||||
{
|
||||
/** Session is already closed */
|
||||
problem_dcb->dcb_errhandle_called = true;
|
||||
@ -1311,32 +1202,24 @@ static void handleError(MXS_ROUTER *instance, void *router_session,
|
||||
* be safe with the code as it stands on 9 Sept 2015 - MNB
|
||||
*/
|
||||
*succp = true;
|
||||
rses_end_locked_router_action(rses);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
problem_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
session = problem_dcb->session;
|
||||
|
||||
backend_ref_t *bref = get_bref_from_dcb(rses, problem_dcb);
|
||||
MXS_SESSION *session = problem_dcb->session;
|
||||
ss_dassert(session);
|
||||
|
||||
if (session == NULL)
|
||||
{
|
||||
MXS_ERROR("Session of DCB %p is NULL, won't close the DCB.", problem_dcb);
|
||||
ss_dassert(false);
|
||||
*succp = false;
|
||||
}
|
||||
else if (DCB_ROLE_CLIENT_HANDLER == problem_dcb->dcb_role)
|
||||
if (problem_dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER)
|
||||
{
|
||||
dcb_close(problem_dcb);
|
||||
*succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
CHK_SESSION(session);
|
||||
CHK_CLIENT_RSES(rses);
|
||||
backend_ref_t *bref = get_bref_from_dcb(rses, problem_dcb);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
@ -1449,8 +1332,6 @@ static void handleError(MXS_ROUTER *instance, void *router_session,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rses_end_locked_router_action(rses);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1570,7 +1451,6 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst,
|
||||
bool succp;
|
||||
|
||||
myrses = *rses;
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&myrses->rses_lock));
|
||||
|
||||
ses = backend_dcb->session;
|
||||
CHK_SESSION(ses);
|
||||
|
@ -307,7 +307,6 @@ struct router_client_session
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t rses_chk_top;
|
||||
#endif
|
||||
SPINLOCK rses_lock; /*< protects rses_deleted */
|
||||
bool rses_closed; /*< true when closeSession is called */
|
||||
rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; /*< Properties listed by their type */
|
||||
backend_ref_t* rses_master_ref;
|
||||
@ -349,7 +348,6 @@ typedef struct
|
||||
typedef struct router_instance
|
||||
{
|
||||
SERVICE* service; /*< Pointer to service */
|
||||
SPINLOCK lock; /*< Lock for the instance data */
|
||||
rwsplit_config_t rwsplit_config; /*< expanded config info from SERVICE */
|
||||
int rwsplit_version; /*< version number for router's config */
|
||||
ROUTER_STATS stats; /*< Statistics for this router */
|
||||
|
@ -56,7 +56,6 @@ bool handle_target_is_all(route_target_t route_target,
|
||||
GWBUF *querybuf, int packet_type, qc_query_type_t qtype);
|
||||
int determine_packet_type(GWBUF *querybuf, bool *non_empty_packet);
|
||||
void log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, qc_query_type_t qtype);
|
||||
void session_lock_failure_handling(GWBUF *querybuf, int packet_type, qc_query_type_t qtype);
|
||||
bool is_packet_a_one_way_message(int packet_type);
|
||||
sescmd_cursor_t *backend_ref_get_sescmd_cursor(backend_ref_t *bref);
|
||||
bool is_packet_a_query(int packet_type);
|
||||
@ -65,8 +64,6 @@ bool send_readonly_error(DCB *dcb);
|
||||
/*
|
||||
* The following are implemented in readwritesplit.c
|
||||
*/
|
||||
bool rses_begin_locked_router_action(ROUTER_CLIENT_SES *rses);
|
||||
void rses_end_locked_router_action(ROUTER_CLIENT_SES *rses);
|
||||
void bref_clear_state(backend_ref_t *bref, bref_state_t state);
|
||||
void bref_set_state(backend_ref_t *bref, bref_state_t state);
|
||||
int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data);
|
||||
|
@ -287,32 +287,6 @@ handle_target_is_all(route_target_t route_target,
|
||||
return result;
|
||||
}
|
||||
|
||||
/* This is MySQL specific */
|
||||
/**
|
||||
* @brief Write an error message to the log indicating failure
|
||||
*
|
||||
* Used when an attempt to lock the router session fails.
|
||||
*
|
||||
* @param querybuf Query buffer containing packet
|
||||
* @param packet_type Integer (enum) indicating type of packet
|
||||
* @param qtype Query type
|
||||
*/
|
||||
void
|
||||
session_lock_failure_handling(GWBUF *querybuf, int packet_type, qc_query_type_t qtype)
|
||||
{
|
||||
if (packet_type != MYSQL_COM_QUIT)
|
||||
{
|
||||
/* NOTE: modutil_get_query is MySQL specific */
|
||||
char *query_str = modutil_get_query(querybuf);
|
||||
|
||||
MXS_ERROR("Can't route %s:%s:\"%s\" to "
|
||||
"backend server. Router is closed.",
|
||||
STRPACKETTYPE(packet_type), STRQTYPE(qtype),
|
||||
(query_str == NULL ? "(empty)" : query_str));
|
||||
MXS_FREE(query_str);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Probably MySQL specific because of modutil function
|
||||
*/
|
||||
|
@ -70,16 +70,10 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses,
|
||||
/* packet_type is a problem as it is MySQL specific */
|
||||
packet_type = determine_packet_type(querybuf, &non_empty_packet);
|
||||
qtype = determine_query_type(querybuf, packet_type, non_empty_packet);
|
||||
|
||||
if (non_empty_packet)
|
||||
{
|
||||
/** This might not be absolutely necessary as some parts of the code
|
||||
* can only be executed by one thread at a time. */
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
handle_multi_temp_and_load(rses, querybuf, packet_type, (int *)&qtype);
|
||||
rses_end_locked_router_action(rses);
|
||||
|
||||
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
||||
{
|
||||
@ -116,7 +110,7 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses,
|
||||
{
|
||||
succp = handle_target_is_all(route_target, inst, rses, querybuf, packet_type, qtype);
|
||||
}
|
||||
else if (rses_begin_locked_router_action(rses))
|
||||
else
|
||||
{
|
||||
/* Now we have a lock on the router session */
|
||||
bool store_stmt = false;
|
||||
@ -152,12 +146,6 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses,
|
||||
ss_dassert(!store_stmt || TARGET_IS_SLAVE(route_target));
|
||||
handle_got_target(inst, rses, querybuf, target_dcb, store_stmt);
|
||||
}
|
||||
rses_end_locked_router_action(rses);
|
||||
}
|
||||
else
|
||||
{
|
||||
session_lock_failure_handling(querybuf, packet_type, qtype);
|
||||
succp = false;
|
||||
}
|
||||
|
||||
return succp;
|
||||
@ -214,12 +202,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses,
|
||||
{
|
||||
int rc;
|
||||
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
for (i = 0; i < router_cli_ses->rses_nbackends; i++)
|
||||
{
|
||||
DCB *dcb = backend_ref[i].bref_dcb;
|
||||
@ -244,15 +226,9 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses,
|
||||
}
|
||||
}
|
||||
}
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
gwbuf_free(querybuf);
|
||||
goto return_succp;
|
||||
}
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
if (router_cli_ses->rses_nbackends <= 0)
|
||||
{
|
||||
@ -318,7 +294,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses,
|
||||
if ((prop = rses_property_init(RSES_PROP_TYPE_SESCMD)) == NULL)
|
||||
{
|
||||
MXS_ERROR("Router session property initialization failed");
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -328,7 +303,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses,
|
||||
if (rses_property_add(router_cli_ses, prop) != 0)
|
||||
{
|
||||
MXS_ERROR("Session property addition failed.");
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -387,9 +361,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses,
|
||||
|
||||
atomic_add(&router_cli_ses->rses_nsescmd, 1);
|
||||
|
||||
/** Unlock router session */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
|
||||
return_succp:
|
||||
/**
|
||||
* Routing must succeed to all backends that are used.
|
||||
@ -1364,7 +1335,6 @@ int rses_property_add(ROUTER_CLIENT_SES *rses, rses_property_t *prop)
|
||||
|
||||
CHK_CLIENT_RSES(rses);
|
||||
CHK_RSES_PROP(prop);
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock));
|
||||
|
||||
prop->rses_prop_rsession = rses;
|
||||
p = rses->rses_properties[prop->rses_prop_type];
|
||||
|
@ -60,8 +60,6 @@ mysql_sescmd_t *rses_property_get_sescmd(rses_property_t *prop)
|
||||
}
|
||||
|
||||
CHK_RSES_PROP(prop);
|
||||
ss_dassert(prop->rses_prop_rsession == NULL ||
|
||||
SPINLOCK_IS_LOCKED(&prop->rses_prop_rsession->rses_lock));
|
||||
|
||||
sescmd = &prop->rses_prop_data.sescmd;
|
||||
|
||||
@ -134,14 +132,9 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf,
|
||||
backend_ref_t *bref,
|
||||
bool *reconnect)
|
||||
{
|
||||
mysql_sescmd_t *scmd;
|
||||
sescmd_cursor_t *scur;
|
||||
ROUTER_CLIENT_SES *ses;
|
||||
|
||||
scur = &bref->bref_sescmd_cur;
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock)));
|
||||
scmd = sescmd_cursor_get_command(scur);
|
||||
ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession;
|
||||
sescmd_cursor_t *scur = &bref->bref_sescmd_cur;
|
||||
mysql_sescmd_t *scmd = sescmd_cursor_get_command(scur);
|
||||
ROUTER_CLIENT_SES *ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession;
|
||||
CHK_GWBUF(replybuf);
|
||||
|
||||
/**
|
||||
@ -273,7 +266,6 @@ mysql_sescmd_t *sescmd_cursor_get_command(sescmd_cursor_t *scur)
|
||||
{
|
||||
mysql_sescmd_t *scmd;
|
||||
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock)));
|
||||
scur->scmd_cur_cmd = rses_property_get_sescmd(*scur->scmd_cur_ptr_property);
|
||||
|
||||
CHK_MYSQL_SESCMD(scur->scmd_cur_cmd);
|
||||
@ -293,7 +285,6 @@ bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor)
|
||||
MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&sescmd_cursor->scmd_cur_rses->rses_lock));
|
||||
|
||||
succp = sescmd_cursor->scmd_cur_active;
|
||||
return succp;
|
||||
@ -303,7 +294,6 @@ bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor)
|
||||
void sescmd_cursor_set_active(sescmd_cursor_t *sescmd_cursor,
|
||||
bool value)
|
||||
{
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(&sescmd_cursor->scmd_cur_rses->rses_lock));
|
||||
/** avoid calling unnecessarily */
|
||||
ss_dassert(sescmd_cursor->scmd_cur_active != value);
|
||||
sescmd_cursor->scmd_cur_active = value;
|
||||
@ -420,8 +410,6 @@ static bool sescmd_cursor_next(sescmd_cursor_t *scur)
|
||||
|
||||
ss_dassert(scur != NULL);
|
||||
ss_dassert(*(scur->scmd_cur_ptr_property) != NULL);
|
||||
ss_dassert(SPINLOCK_IS_LOCKED(
|
||||
&(*(scur->scmd_cur_ptr_property))->rses_prop_rsession->rses_lock));
|
||||
|
||||
/** Illegal situation */
|
||||
if (scur == NULL || *scur->scmd_cur_ptr_property == NULL ||
|
||||
|
@ -58,23 +58,23 @@
|
||||
*/
|
||||
|
||||
static MXS_ROUTER* createInstance(SERVICE *service, char **options);
|
||||
static void* newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, void *session);
|
||||
static void freeSession(MXS_ROUTER *instance, void *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue);
|
||||
static void diagnostic(MXS_ROUTER *instance, DCB *dcb);
|
||||
static MXS_ROUTER_SESSION* newSession(MXS_ROUTER *instance, MXS_SESSION *session);
|
||||
static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session);
|
||||
static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue);
|
||||
static void diagnostic(MXS_ROUTER *instance, DCB *dcb);
|
||||
|
||||
static void clientReply(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
GWBUF* queue,
|
||||
DCB* backend_dcb);
|
||||
MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* queue,
|
||||
DCB* backend_dcb);
|
||||
|
||||
static void handleError(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
GWBUF* errmsgbuf,
|
||||
DCB* backend_dcb,
|
||||
static void handleError(MXS_ROUTER* instance,
|
||||
MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* errmsgbuf,
|
||||
DCB* backend_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool* succp);
|
||||
bool* succp);
|
||||
static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb);
|
||||
|
||||
static route_target_t get_shard_route_target(qc_query_type_t qtype,
|
||||
@ -847,7 +847,7 @@ enum shard_map_state shard_map_update_state(shard_map_t *self, ROUTER_INSTANCE*
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session)
|
||||
static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session)
|
||||
{
|
||||
backend_ref_t* backend_ref; /*< array of backend references (DCB, BACKEND, cursor) */
|
||||
ROUTER_CLIENT_SES* client_rses = NULL;
|
||||
@ -1040,7 +1040,7 @@ static void* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session)
|
||||
* @param instance The router instance data
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void closeSession(MXS_ROUTER* instance, void* router_session)
|
||||
static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session)
|
||||
{
|
||||
ROUTER_CLIENT_SES* router_cli_ses;
|
||||
ROUTER_INSTANCE* inst;
|
||||
@ -1135,7 +1135,7 @@ static void closeSession(MXS_ROUTER* instance, void* router_session)
|
||||
}
|
||||
}
|
||||
|
||||
static void freeSession(MXS_ROUTER* router_instance, void* router_client_session)
|
||||
static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_session)
|
||||
{
|
||||
ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session;
|
||||
|
||||
@ -1612,7 +1612,7 @@ bool send_database_list(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client)
|
||||
*
|
||||
*/
|
||||
static int routeQuery(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* qbuf)
|
||||
{
|
||||
qc_query_type_t qtype = QUERY_TYPE_UNKNOWN;
|
||||
@ -2238,7 +2238,7 @@ static void diagnostic(MXS_ROUTER *instance, DCB *dcb)
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void clientReply(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* buffer,
|
||||
DCB* backend_dcb)
|
||||
{
|
||||
@ -3550,12 +3550,12 @@ return_succp:
|
||||
* 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(MXS_ROUTER* instance,
|
||||
void* router_session,
|
||||
GWBUF* errmsgbuf,
|
||||
DCB* problem_dcb,
|
||||
static void handleError(MXS_ROUTER* instance,
|
||||
MXS_ROUTER_SESSION* router_session,
|
||||
GWBUF* errmsgbuf,
|
||||
DCB* problem_dcb,
|
||||
mxs_error_action_t action,
|
||||
bool* succp)
|
||||
bool* succp)
|
||||
{
|
||||
MXS_SESSION* session;
|
||||
ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance;
|
||||
|
@ -1,4 +0,0 @@
|
||||
add_library(testroute SHARED testroute.c)
|
||||
target_link_libraries(testroute maxscale-common)
|
||||
set_target_properties(testroute PROPERTIES VERSION "1.0.0")
|
||||
install_module(testroute core)
|
Loading…
x
Reference in New Issue
Block a user