948 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			948 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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: 2024-11-16
 | |
|  *
 | |
|  * 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/ccdefs.hh>
 | |
| 
 | |
| #include <vector>
 | |
| #include <iostream>
 | |
| #include <string>
 | |
| #include <iterator>
 | |
| 
 | |
| #include <maxbase/alloc.h>
 | |
| #include <maxscale/buffer.hh>
 | |
| #include <maxscale/dcb.hh>
 | |
| #include <maxscale/modinfo.h>
 | |
| #include <maxscale/modulecmd.hh>
 | |
| #include <maxscale/modutil.hh>
 | |
| #include <maxscale/protocol/mysql.hh>
 | |
| #include <maxscale/query_classifier.hh>
 | |
| #include <maxscale/router.hh>
 | |
| 
 | |
| // #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, json_t** output);
 | |
| 
 | |
| 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. */
 | |
|     const MXS_CONFIG_PARAMETER& params = service->svc_config_param;
 | |
|     m_max_backends = params.get_integer(MAX_BACKENDS);
 | |
|     m_write_server = params.get_server(WRITE_BACKEND);
 | |
|     m_print_on_routing = params.get_bool(PRINT_ON_ROUTING);
 | |
|     m_example_enum = params.get_enum(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().c_str());
 | |
|                 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().c_str());
 | |
|             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->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)
 | |
| {
 | |
|     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->role == DCB::Role::CLIENT)
 | |
|     {
 | |
|         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_STARTED)
 | |
|                 {
 | |
|                     /* 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->role == DCB::Role::BACKEND)
 | |
|                 {
 | |
|                     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:
 | |
|             mxb_assert(!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. */
 | |
|     mxb_assert(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;
 | |
|         for (unsigned int i = 0; i < m_backend_dcbs.size(); i++)
 | |
|         {
 | |
|             DCB* dcb = m_backend_dcbs[i];
 | |
|             SERVER_REF* sref = dcb->service->dbref;
 | |
|             while (sref && (sref->server != dcb->server))
 | |
|             {
 | |
|                 sref = sref->next;
 | |
|             }
 | |
|             if (sref)
 | |
|             {
 | |
|                 atomic_add(&(sref->connections), -1);
 | |
|             }
 | |
|             dcb_close(dcb);
 | |
|         }
 | |
|         int closed_conns = m_backend_dcbs.size();
 | |
|         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 */
 | |
|     mxs_mysql_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 MXS_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 MXS_COM_INIT_DB:
 | |
|         query_types = q_route_to_all;
 | |
|         RR_DEBUG("MYSQL_COM_INIT_DB");
 | |
|         break;
 | |
| 
 | |
|     case MXS_COM_QUIT:
 | |
|         query_types = q_route_to_all;
 | |
|         RR_DEBUG("MYSQL_COM_QUIT");
 | |
|         break;
 | |
| 
 | |
|     case MXS_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, MXS_CONFIG_PARAMETER* params);
 | |
| 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 json_t*             diagnostics_json(const MXS_ROUTER* instance);
 | |
| 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,
 | |
|         diagnostics_json,
 | |
|         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 */
 | |
|         RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_RESULTSET_OUTPUT,
 | |
|         &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, MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     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",
 | |
|                                     MODULECMD_TYPE_ACTIVE,
 | |
|                                     custom_cmd_example,
 | |
|                                     2,
 | |
|                                     custom_cmd_args,
 | |
|                                     "This is the command description"))
 | |
|     {
 | |
|         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 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 json_t* diagnostics_json(const MXS_ROUTER* instance)
 | |
| {
 | |
|     const RRRouter* router = static_cast<const RRRouter*>(instance);
 | |
|     json_t* rval = json_object();
 | |
| 
 | |
|     json_object_set_new(rval, "queries_ok", json_integer(router->m_routing_s));
 | |
|     json_object_set_new(rval, "queries_failed", json_integer(router->m_routing_f));
 | |
|     json_object_set_new(rval, "replies", json_integer(router->m_routing_c));
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @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_NONE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @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, json_t** output)
 | |
| {
 | |
|     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;
 | |
| }
 | 
