567 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			567 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# 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
 | 
						|
 | 
						|
* [Introduction](#introduction)
 | 
						|
  * [Module categories](#module-categories)
 | 
						|
  * [Common definitions and headers](#common-definitions-and-headers)
 | 
						|
* [Module information container](#module-information-container)
 | 
						|
  * [Module API](#module-api)
 | 
						|
* [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)
 | 
						|
* [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*, MXS_SESSION*)
 | 
						|
int32_t close(struct dcb *)
 | 
						|
int32_t listen(struct dcb *, char *)
 | 
						|
int32_t auth(struct dcb*, struct server*, MXS_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=MariaDBClient
 | 
						|
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: MariaDBBackend    Name: LocalMaster1
 | 
						|
		127.0.0.1:3002    Protocol: MariaDBBackend    Name: LocalSlave1
 | 
						|
		127.0.0.1:3003    Protocol: MariaDBBackend    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).
 |