From 06b59ffe2e9a56a379baca03d18398d19fcb8aa4 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 22 Feb 2015 04:10:57 +0200 Subject: [PATCH 01/32] Testing of plain routing with MongoDB. --- server/modules/protocol/CMakeLists.txt | 9 + server/modules/protocol/mongo_backend.c | 1204 +++++++++++++++++++++++ server/modules/protocol/mongo_client.c | 1004 +++++++++++++++++++ server/modules/routing/CMakeLists.txt | 4 + server/modules/routing/plainroute.c | 923 +++++++++++++++++ 5 files changed, 3144 insertions(+) create mode 100644 server/modules/protocol/mongo_backend.c create mode 100644 server/modules/protocol/mongo_client.c create mode 100644 server/modules/routing/plainroute.c diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index fa1c2ab34..0bea95832 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -6,6 +6,15 @@ add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c) target_link_libraries(MySQLBackend log_manager utils) install(TARGETS MySQLBackend DESTINATION modules) +add_library(mongoclient SHARED mongo_client.c mysql_common.c) +target_link_libraries(mongoclient log_manager utils) +install(TARGETS mongoclient DESTINATION modules) + +add_library(mongobackend SHARED mongo_backend.c mysql_common.c) +target_link_libraries(mongobackend log_manager utils) +install(TARGETS mongobackend DESTINATION modules) + + add_library(telnetd SHARED telnetd.c) target_link_libraries(telnetd log_manager utils) install(TARGETS telnetd DESTINATION modules) diff --git a/server/modules/protocol/mongo_backend.c b/server/modules/protocol/mongo_backend.c new file mode 100644 index 000000000..4fe1ff0b8 --- /dev/null +++ b/server/modules/protocol/mongo_backend.c @@ -0,0 +1,1204 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +#include "mysql_client_server_protocol.h" +#include +#include +#include +#include + +/* + * MySQL Protocol module for handling the protocol between the gateway + * and the backend MySQL database. + * + * Revision History + * Date Who Description + * 14/06/2013 Mark Riddoch Initial version + * 17/06/2013 Massimiliano Pinto Added MaxScale To Backends routines + * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. + * 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection + * 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend + * 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails + * 12/07/2013 Massimiliano Pinto Added Mysql Change User via dcb->func.auth() + * 15/07/2013 Massimiliano Pinto Added Mysql session change via dcb->func.session() + * 17/07/2013 Massimiliano Pinto Added dcb->command update from gwbuf->command for proper routing + server replies to client via router->clientReply + * 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL + * 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake + * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0 + * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support + * 10/11/2014 Massimiliano Pinto Client charset is passed to backend + * + */ +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_GA, + GWPROTOCOL_VERSION, + "The MySQL to backend server protocol" +}; + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +static char *version_str = "V2.0.0"; +static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session); +static int gw_read_backend_event(DCB* dcb); +static int gw_write_backend_event(DCB *dcb); +static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); +static int gw_error_backend_event(DCB *dcb); +static int gw_backend_close(DCB *dcb); +static int gw_backend_hangup(DCB *dcb); +static int backend_write_delayqueue(DCB *dcb); +static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); +static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); +static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); +extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1); +extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); +static bool sescmd_response_complete(DCB* dcb); + + +#if defined(NOT_USED) + static int gw_session(DCB *backend_dcb, void *data); +#endif +static MYSQL_session* gw_get_shared_session_auth_info(DCB* dcb); + +static GWPROTOCOL MyObject = { + gw_read_backend_event, /* Read - EPOLLIN handler */ + gw_MySQLWrite_backend, /* Write - data from gateway */ + gw_write_backend_event, /* WriteReady - EPOLLOUT handler */ + gw_error_backend_event, /* Error - EPOLLERR handler */ + gw_backend_hangup, /* HangUp - EPOLLHUP handler */ + NULL, /* Accept */ + gw_create_backend_connection, /* Connect */ + gw_backend_close, /* Close */ + NULL, /* Listen */ + gw_change_user, /* Authentication */ + NULL /* Session */ +}; + +/* + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/* + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/* + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +GWPROTOCOL * +GetModuleObject() +{ + return &MyObject; +} + + +static MYSQL_session* gw_get_shared_session_auth_info( + DCB* dcb) +{ + MYSQL_session* auth_info = NULL; + CHK_DCB(dcb); + CHK_SESSION(dcb->session); + + spinlock_acquire(&dcb->session->ses_lock); + + if (dcb->session->state != SESSION_STATE_ALLOC) { + auth_info = dcb->session->data; + } else { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [gw_get_shared_session_auth_info] Couldn't get " + "session authentication info. Session in a wrong state %d.", + pthread_self(), + dcb->session->state))); + } + spinlock_release(&dcb->session->ses_lock); + + return auth_info; +} + +/** + * Backend Read Event for EPOLLIN on the MySQL backend protocol module + * @param dcb The backend Descriptor Control Block + * @return 1 on operation, 0 for no action + */ +static int gw_read_backend_event(DCB *dcb) { + MySQLProtocol *client_protocol = NULL; + MySQLProtocol *backend_protocol = NULL; + MYSQL_session *current_session = NULL; + int rc = 0; + + + backend_protocol = (MySQLProtocol *) dcb->protocol; + CHK_PROTOCOL(backend_protocol); + + + /* reading MySQL command output from backend and writing to the client */ + { + GWBUF *read_buffer = NULL; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + SESSION *session = dcb->session; + int nbytes_read = 0; + + CHK_SESSION(session); + router = session->service->router; + router_instance = session->service->router_instance; + + /* read available backend data */ + rc = dcb_read(dcb, &read_buffer); + + if (rc < 0) + { + GWBUF* errbuf; + bool succp; + + errbuf = mysql_create_custom_error( + 1, + 0, + "Read from backend failed"); + + router->handleError( + router_instance, + session->router_session, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + gwbuf_free(errbuf); + + if (!succp) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + ss_dassert(dcb->dcb_errhandle_called); + dcb_close(dcb); + rc = 0; + goto return_rc; + } + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read == 0 && dcb->dcb_readqueue == NULL) + { + goto return_rc; + } + else + { + ss_dassert(read_buffer != NULL || dcb->dcb_readqueue != NULL); + } + + /** Packet prefix was read earlier */ + if (dcb->dcb_readqueue) + { + if (read_buffer != NULL) + { + read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer); + } + else + { + read_buffer = dcb->dcb_readqueue; + } + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read < 5) /*< read at least command type */ + { + rc = 0; + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%p [gw_read_backend_event] Read %d bytes " + "from DCB %p, fd %d, session %s. " + "Returning to poll wait.\n", + pthread_self(), + nbytes_read, + dcb, + dcb->fd, + dcb->session))); + goto return_rc; + } + /** There is at least length and command type. */ + else + { + dcb->dcb_readqueue = NULL; + } + } + /** This may be either short prefix of a packet, or the tail of it. */ + else + { + if (nbytes_read < 5) + { + dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer); + rc = 0; + goto return_rc; + } + } + + + if (dcb->session->state == SESSION_STATE_ROUTER_READY && + dcb->session->client != NULL && + dcb->session->client->state == DCB_STATE_POLLING) + { + client_protocol = SESSION_PROTOCOL(dcb->session, + MySQLProtocol); + + { + gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); + router->clientReply(router_instance, session->router_session, read_buffer, dcb); + rc = 1; + } + } + else /*< session is closing; replying to client isn't possible */ + { + gwbuf_free(read_buffer); + } + } + +return_rc: + return rc; + +return_with_lock: + + goto return_rc; +} + +/* + * EPOLLOUT handler for the MySQL Backend protocol module. + * + * @param dcb The descriptor control block + * @return 1 in success, 0 in case of failure, + */ +static int gw_write_backend_event(DCB *dcb) { + int rc = 0; + MySQLProtocol *backend_protocol = dcb->protocol; + + /*< + * Don't write to backend if backend_dcb is not in poll set anymore. + */ + if (dcb->state != DCB_STATE_POLLING) { + uint8_t* data; + + if (dcb->writeq != NULL) + { + data = (uint8_t *)GWBUF_DATA(dcb->writeq); + + + } + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_write_backend_event] Dcb %p in state %s " + "but there's nothing to write either.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + rc = 1; + } + goto return_rc; + } + + dcb_drain_writeq(dcb); + rc = 1; +return_rc: + + + return rc; +} + +/* + * Write function for backend DCB. Store command to protocol. + * + * @param dcb The DCB of the backend + * @param queue Queue of buffers to write + * @return 0 on failure, 1 on success + */ +static int +gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) +{ + MySQLProtocol *backend_protocol = dcb->protocol; + int rc = 0; + + rc = dcb_write(dcb, queue); + + +return_rc: + return rc; +} + +/** + * Error event handler. + * Create error message, pass it to router's error handler and if error + * handler fails in providing enough backend servers, mark session being + * closed and call DCB close function which triggers closing router session + * and related backends (if any exists. + */ +static int gw_error_backend_event(DCB *dcb) +{ + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + GWBUF* errbuf; + bool succp; + session_state_t ses_state; + + CHK_DCB(dcb); + session = dcb->session; + CHK_SESSION(session); + rsession = session->router_session; + router = session->service->router; + router_instance = session->service->router_instance; + + /** + * Avoid running redundant error handling procedure. + * dcb_close is already called for the DCB. Thus, either connection is + * closed by router and COM_QUIT sent or there was an error which + * have already been handled. + */ + if (dcb->state != DCB_STATE_POLLING) + { + int error, len; + char buf[100]; + + len = sizeof(error); + + if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) + { + if (error != 0) + { + strerror_r(error, buf, 100); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "DCB in state %s got error '%s'.", + STRDCBSTATE(dcb->state), + buf))); + } + } + return 1; + } + errbuf = mysql_create_custom_error( + 1, + 0, + "Lost connection to backend server."); + + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + + /** + * Session might be initialized when DCB already is in the poll set. + * Thus hangup can occur in the middle of session initialization. + * Only complete and successfully initialized sessions allow for + * calling error handler. + */ + while (ses_state == SESSION_STATE_READY) + { + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + } + + if (ses_state != SESSION_STATE_ROUTER_READY) + { + int error, len; + char buf[100]; + + len = sizeof(error); + if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) + { + if (error != 0) + { + strerror_r(error, buf, 100); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error '%s' in session that is not ready for routing.", + buf))); + } + } + gwbuf_free(errbuf); + goto retblock; + } + +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend error event handling."))); +#endif + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + gwbuf_free(errbuf); + + /** + * If error handler fails it means that routing session can't continue + * and it must be closed. In success, only this DCB is closed. + */ + if (!succp) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + ss_dassert(dcb->dcb_errhandle_called); + dcb_close(dcb); + +retblock: + return 1; +} + +/* + * Create a new backend connection. + * + * This routine will connect to a backend server and it is called by dbc_connect + * in router->newSession + * + * @param backend_dcb, in, out, use - backend DCB allocated from dcb_connect + * @param server, in, use - server to connect to + * @param session, in use - current session from client DCB + * @return 0/1 on Success and -1 on Failure. + * If succesful, returns positive fd to socket which is connected to + * backend server. Positive fd is copied to protocol and to dcb. + * If fails, fd == -1 and socket is closed. + */ +static int gw_create_backend_connection( + DCB *backend_dcb, + SERVER *server, + SESSION *session) +{ + MySQLProtocol *protocol = NULL; + int rv = -1; + int fd = -1; + + protocol = mysql_protocol_init(backend_dcb, -1); + ss_dassert(protocol != NULL); + + if (protocol == NULL) { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_create_backend_connection] Failed to create " + "protocol object for backend connection.", + pthread_self()))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to create " + "protocol object for backend connection."))); + goto return_fd; + } + + /*< if succeed, fd > 0, -1 otherwise */ + rv = gw_do_connect_to_backend(server->name, server->port, &fd); + /*< Assign protocol with backend_dcb */ + backend_dcb->protocol = protocol; + + /*< Set protocol state */ + switch (rv) { + case 0: + ss_dassert(fd > 0); + protocol->fd = fd; + protocol->protocol_auth_state = MYSQL_CONNECTED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_create_backend_connection] Established " + "connection to %s:%i, protocol fd %d client " + "fd %d.", + pthread_self(), + server->name, + server->port, + protocol->fd, + session->client->fd))); + break; + + case 1: + ss_dassert(fd > 0); + protocol->protocol_auth_state = MYSQL_PENDING_CONNECT; + protocol->fd = fd; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_create_backend_connection] Connection " + "pending to %s:%i, protocol fd %d client fd %d.", + pthread_self(), + server->name, + server->port, + protocol->fd, + session->client->fd))); + break; + + default: + ss_dassert(fd == -1); + ss_dassert(protocol->protocol_auth_state == MYSQL_ALLOC); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_create_backend_connection] Connection " + "failed to %s:%i, protocol fd %d client fd %d.", + pthread_self(), + server->name, + server->port, + protocol->fd, + session->client->fd))); + break; + } /*< switch */ + +return_fd: + return fd; +} + + +/** + * Error event handler. + * Create error message, pass it to router's error handler and if error + * handler fails in providing enough backend servers, mark session being + * closed and call DCB close function which triggers closing router session + * and related backends (if any exists. + * + * @param dcb The current Backend DCB + * @return 1 always + */ +static int +gw_backend_hangup(DCB *dcb) +{ + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + bool succp; + GWBUF* errbuf; + session_state_t ses_state; + + CHK_DCB(dcb); + session = dcb->session; + CHK_SESSION(session); + + rsession = session->router_session; + router = session->service->router; + router_instance = session->service->router_instance; + + errbuf = mysql_create_custom_error( + 1, + 0, + "Lost connection to backend server."); + + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + + /** + * Session might be initialized when DCB already is in the poll set. + * Thus hangup can occur in the middle of session initialization. + * Only complete and successfully initialized sessions allow for + * calling error handler. + */ + while (ses_state == SESSION_STATE_READY) + { + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + } + + if (ses_state != SESSION_STATE_ROUTER_READY) + { + int error, len; + char buf[100]; + + len = sizeof(error); + if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) + { + if (error != 0) + { + strerror_r(error, buf, 100); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Hangup in session that is not ready for routing, " + "Error reported is '%s'.", + buf))); + } + } + gwbuf_free(errbuf); + goto retblock; + } +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend hangup error handling."))); +#endif + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + + gwbuf_free(errbuf); + /** There are no required backends available, close session. */ + if (!succp) + { +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend hangup -> closing session."))); +#endif + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + ss_dassert(dcb->dcb_errhandle_called); + dcb_close(dcb); + +retblock: + return 1; +} + +/** + * Send COM_QUIT to backend so that it can be closed. + * @param dcb The current Backend DCB + * @return 1 always + */ +static int +gw_backend_close(DCB *dcb) +{ + DCB* client_dcb; + SESSION* session; + GWBUF* quitbuf; + + CHK_DCB(dcb); + session = dcb->session; + CHK_SESSION(session); + + LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, + "%lu [gw_backend_close]", + pthread_self()))); + + quitbuf = mysql_create_com_quit(NULL, 0); + gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL); + + /** Send COM_QUIT to the backend being closed */ + mysql_send_com_quit(dcb, 0, quitbuf); + + mysql_protocol_done(dcb); + /** + * The lock is needed only to protect the read of session->state and + * session->client values. Client's state may change by other thread + * but client's close and adding client's DCB to zombies list is executed + * only if client's DCB's state does _not_ change in parallel. + */ + spinlock_acquire(&session->ses_lock); + /** + * If session->state is STOPPING, start closing client session. + * Otherwise only this backend connection is closed. + */ + if (session != NULL && + session->state == SESSION_STATE_STOPPING && + session->client != NULL) + { + if (session->client->state == DCB_STATE_POLLING) + { + spinlock_release(&session->ses_lock); + + /** Close client DCB */ + dcb_close(session->client); + } + else + { + spinlock_release(&session->ses_lock); + } + } + else + { + spinlock_release(&session->ses_lock); + } + return 1; +} + +/** + * This routine put into the delay queue the input queue + * The input is what backend DCB is receiving + * The routine is called from func.write() when mysql backend connection + * is not yet complete buu there are inout data from client + * + * @param dcb The current backend DCB + * @param queue Input data in the GWBUF struct + */ +static void backend_set_delayqueue(DCB *dcb, GWBUF *queue) { + spinlock_acquire(&dcb->delayqlock); + + if (dcb->delayq) { + /* Append data */ + dcb->delayq = gwbuf_append(dcb->delayq, queue); + } else { + if (queue != NULL) { + /* create the delay queue */ + dcb->delayq = queue; + } + } + spinlock_release(&dcb->delayqlock); +} + +/** + * This routine writes the delayq via dcb_write + * The dcb->delayq contains data received from the client before + * mysql backend authentication succeded + * + * @param dcb The current backend DCB + * @return The dcb_write status + */ +static int backend_write_delayqueue(DCB *dcb) +{ + GWBUF *localq = NULL; + int rc; + + spinlock_acquire(&dcb->delayqlock); + + if (dcb->delayq == NULL) + { + spinlock_release(&dcb->delayqlock); + rc = 1; + } + else + { + localq = dcb->delayq; + dcb->delayq = NULL; + spinlock_release(&dcb->delayqlock); + + if (MYSQL_IS_CHANGE_USER(((uint8_t *)GWBUF_DATA(localq)))) + { + MYSQL_session* mses; + GWBUF* new_packet; + + mses = (MYSQL_session *)dcb->session->client->data; + new_packet = gw_create_change_user_packet( + mses, + (MySQLProtocol *)dcb->protocol); + /** + * Remove previous packet which lacks scramble + * and append the new. + */ + localq = gwbuf_consume(localq, GWBUF_LENGTH(localq)); + localq = gwbuf_append(localq, new_packet); + } + rc = dcb_write(dcb, localq); + } + + if (rc == 0) + { + GWBUF* errbuf; + bool succp; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + SESSION *session = dcb->session; + + CHK_SESSION(session); + + if (session != NULL) + { + router = session->service->router; + router_instance = session->service->router_instance; + rsession = session->router_session; +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend write delayqueue error handling."))); +#endif + errbuf = mysql_create_custom_error( + 1, + 0, + "Failed to write buffered data to back-end server. " + "Buffer was empty or back-end was disconnected during " + "operation. Attempting to find a new backend."); + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + gwbuf_free(errbuf); + + if (!succp) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + ss_dassert(dcb->dcb_errhandle_called); + dcb_close(dcb); + } + } + } + return rc; +} + +/** + * This routine handles the COM_CHANGE_USER command + * + * @param dcb The current backend DCB + * @param server The backend server pointer + * @param in_session The current session data (MYSQL_session) + * @param queue The GWBUF containing the COM_CHANGE_USER receveid + * @return 1 on success and 0 on failure + */ +static int gw_change_user( + DCB *backend, + SERVER *server, + SESSION *in_session, + GWBUF *queue) +{ + MYSQL_session *current_session = NULL; + MySQLProtocol *backend_protocol = NULL; + MySQLProtocol *client_protocol = NULL; + char username[MYSQL_USER_MAXLEN+1]=""; + char database[MYSQL_DATABASE_MAXLEN+1]=""; + char current_database[MYSQL_DATABASE_MAXLEN+1]=""; + uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]=""; + uint8_t *client_auth_packet = GWBUF_DATA(queue); + unsigned int auth_token_len = 0; + uint8_t *auth_token = NULL; + int rv = -1; + int auth_ret = 1; + + current_session = (MYSQL_session *)in_session->client->data; + backend_protocol = backend->protocol; + client_protocol = in_session->client->protocol; + + /* now get the user, after 4 bytes header and 1 byte command */ + client_auth_packet += 5; + strncpy(username, (char *)client_auth_packet,MYSQL_USER_MAXLEN); + client_auth_packet += strlen(username) + 1; + + /* get the auth token len */ + memcpy(&auth_token_len, client_auth_packet, 1); + + client_auth_packet++; + + /* allocate memory for token only if auth_token_len > 0 */ + if (auth_token_len > 0) { + auth_token = (uint8_t *)malloc(auth_token_len); + ss_dassert(auth_token != NULL); + + if (auth_token == NULL) + return rv; + memcpy(auth_token, client_auth_packet, auth_token_len); + client_auth_packet += auth_token_len; + } + + /* get new database name */ + strncpy(database, (char *)client_auth_packet,MYSQL_DATABASE_MAXLEN); + + /* get character set */ + if (strlen(database)) { + client_auth_packet += strlen(database) + 1; + } else { + client_auth_packet++; + } + + if (client_auth_packet && *client_auth_packet) + memcpy(&backend_protocol->charset, client_auth_packet, sizeof(int)); + + /* save current_database name */ + strncpy(current_database, current_session->db,MYSQL_DATABASE_MAXLEN); + + /* + * Now clear database name in dcb as we don't do local authentication on db name for change user. + * Local authentication only for user@host and if successful the database name change is sent to backend. + */ + strcpy(current_session->db, ""); + + /* + * decode the token and check the password. + * Note: if auth_token_len == 0 && auth_token == NULL, user is without password + */ + auth_ret = gw_check_mysql_scramble_data(backend->session->client, + auth_token, + auth_token_len, + client_protocol->scramble, + sizeof(client_protocol->scramble), + username, + client_sha1); + + if (auth_ret != 0) { + if (!service_refresh_users(backend->session->client->service)) { + /* Try authentication again with new repository data */ + /* Note: if no auth client authentication will fail */ + auth_ret = gw_check_mysql_scramble_data( + backend->session->client, + auth_token, auth_token_len, + client_protocol->scramble, + sizeof(client_protocol->scramble), + username, + client_sha1); + } + } + + /* copy back current datbase to client session */ + strcpy(current_session->db, current_database); + + /* let's free the auth_token now */ + if (auth_token) + free(auth_token); + + if (auth_ret != 0) { + char *password_set = NULL; + char *message = NULL; + GWBUF* buf; + + if (auth_token_len > 0) + password_set = (char *)client_sha1; + else + password_set = ""; + + /** + * Create an error message and make it look like legit reply + * from backend server. Then make it look like an incoming event + * so that thread gets new task of it, calls clientReply + * which filters out duplicate errors from same cause and forward + * reply to the client. + */ + message = create_auth_fail_str(username, + backend->session->client->remote, + password_set, + ""); + if (message == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Creating error message failed."))); + rv = 0; + goto retblock; + } + /** + * Add command to backend's protocol, create artificial reply + * packet and add it to client's read buffer. + */ + protocol_add_srv_command((MySQLProtocol*)backend->protocol, + MYSQL_COM_CHANGE_USER); + modutil_reply_auth_error(backend, message, 0); + rv = 1; + } else { + rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); + /* + * Now copy new data into user session + */ + strcpy(current_session->user, username); + strcpy(current_session->db, database); + memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1)); + } + +retblock: + gwbuf_free(queue); + + return rv; +} + + +/** + * Move packets or parts of packets from readbuf to outbuf as the packet headers + * and lengths have been noticed and counted. + * Session commands need to be marked so that they can be handled properly in + * the router's clientReply. + * + * @param dcb Backend's DCB where data was read from + * @param readbuf GWBUF where data was read to + * @param nbytes_to_process Number of bytes that has been read and need to be processed + * + * @return GWBUF which includes complete MySQL packet + */ +static GWBUF* process_response_data ( + DCB* dcb, + GWBUF* readbuf, + int nbytes_to_process) +{ + int npackets_left = 0; /*< response's packet count */ + ssize_t nbytes_left = 0; /*< nbytes to be read for the packet */ + MySQLProtocol* p; + GWBUF* outbuf = NULL; + + /** Get command which was stored in gw_MySQLWrite_backend */ + p = DCB_PROTOCOL(dcb, MySQLProtocol); + if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p); + + /** All buffers processed here are sescmd responses */ + gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE); + + /** + * Now it is known how many packets there should be and how much + * is read earlier. + */ + while (nbytes_to_process != 0) + { + mysql_server_cmd_t srvcmd; + bool succp; + + srvcmd = protocol_get_srv_command(p, false); + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [process_response_data] Read command %s for DCB %p fd %d.", + pthread_self(), + STRPACKETTYPE(srvcmd), + dcb, + dcb->fd))); + /** + * Read values from protocol structure, fails if values are + * uninitialized. + */ + if (npackets_left == 0) + { + succp = protocol_get_response_status(p, &npackets_left, &nbytes_left); + + if (!succp || npackets_left == 0) + { + /** + * Examine command type and the readbuf. Conclude response + * packet count from the command type or from the first + * packet content. Fails if read buffer doesn't include + * enough data to read the packet length. + */ + init_response_status(readbuf, srvcmd, &npackets_left, &nbytes_left); + } + } + /** Only session commands with responses should be processed */ + ss_dassert(npackets_left > 0); + + /** Read incomplete packet. */ + if (nbytes_left > nbytes_to_process) + { + /** Includes length info so it can be processed */ + if (nbytes_to_process >= 5) + { + /** discard source buffer */ + readbuf = gwbuf_consume(readbuf, GWBUF_LENGTH(readbuf)); + nbytes_left -= nbytes_to_process; + } + nbytes_to_process = 0; + } + /** Packet was read. All bytes belonged to the last packet. */ + else if (nbytes_left == nbytes_to_process) + { + nbytes_left = 0; + nbytes_to_process = 0; + ss_dassert(npackets_left > 0); + npackets_left -= 1; + outbuf = gwbuf_append(outbuf, readbuf); + readbuf = NULL; + } + /** + * Packet was read. There should be more since bytes were + * left over. + * Move the next packet to its own buffer and add that next + * to the prev packet's buffer. + */ + else /*< nbytes_left < nbytes_to_process */ + { + ss_dassert(nbytes_left >= 0); + nbytes_to_process -= nbytes_left; + + /** Move the prefix of the buffer to outbuf from redbuf */ + outbuf = gwbuf_append(outbuf, + gwbuf_clone_portion(readbuf, 0, (size_t)nbytes_left)); + readbuf = gwbuf_consume(readbuf, (size_t)nbytes_left); + ss_dassert(npackets_left > 0); + npackets_left -= 1; + nbytes_left = 0; + } + + /** Store new status to protocol structure */ + protocol_set_response_status(p, npackets_left, nbytes_left); + + /** A complete packet was read */ + if (nbytes_left == 0) + { + /** No more packets in this response */ + if (npackets_left == 0 && outbuf != NULL) + { + GWBUF* b = outbuf; + + while (b->next != NULL) + { + b = b->next; + } + /** Mark last as end of response */ + gwbuf_set_type(b, GWBUF_TYPE_RESPONSE_END); + + /** Archive the command */ + protocol_archive_srv_command(p); + } + /** Read next packet */ + else + { + uint8_t* data; + + /** Read next packet length */ + data = GWBUF_DATA(readbuf); + nbytes_left = MYSQL_GET_PACKET_LEN(data)+MYSQL_HEADER_LEN; + /** Store new status to protocol structure */ + protocol_set_response_status(p, npackets_left, nbytes_left); + } + } + } + return outbuf; +} + + +static bool sescmd_response_complete( + DCB* dcb) +{ + int npackets_left; + ssize_t nbytes_left; + MySQLProtocol* p; + bool succp; + + p = DCB_PROTOCOL(dcb, MySQLProtocol); + if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p); + + protocol_get_response_status(p, &npackets_left, &nbytes_left); + + if (npackets_left == 0) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} diff --git a/server/modules/protocol/mongo_client.c b/server/modules/protocol/mongo_client.c new file mode 100644 index 000000000..1de7d237d --- /dev/null +++ b/server/modules/protocol/mongo_client.c @@ -0,0 +1,1004 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2015 + */ + +/** + * @file mongo_client.c + * + * Revision History + * Date Who Description + + * + */ +#include +#include +#include +#include +#include +#include + +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_GA, + GWPROTOCOL_VERSION, + "The client to plain protocol implementation" +}; + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +static char *version_str = "V1.0.0"; + +static int plain_accept(DCB *listener); +static int plain_listen(DCB *listener, char *config_bind); +static int plain_read(DCB* dcb); +static int plain_write_ready(DCB *dcb); +static int plain_write(DCB *dcb, GWBUF *queue); +static int plain_client_error(DCB *dcb); +static int plain_client_close(DCB *dcb); +static int plain_client_hangup_event(DCB *dcb); + +/* + * The "module object" for the mysqld client protocol module. + */ +static GWPROTOCOL MyObject = { + plain_read, /* Read - EPOLLIN handler */ + plain_write, /* Write - data from gateway */ + plain_write_ready, /* WriteReady - EPOLLOUT handler */ + plain_client_error, /* Error - EPOLLERR handler */ + plain_client_hangup_event, /* HangUp - EPOLLHUP handler */ + plain_accept, /* Accept */ + NULL, /* Connect */ + plain_client_close, /* Close */ + plain_listen, /* Listen */ + NULL, /* Authentication */ + NULL /* Session */ +}; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +GWPROTOCOL * +GetModuleObject() +{ + return &MyObject; +} + +/** + * mysql_send_ok + * + * Send a MySQL protocol OK message to the dcb (client) + * + * @param dcb Descriptor Control Block for the connection to which the OK is sent + * @param packet_number + * @param in_affected_rows + * @param mysql_message + * @return packet length + * + */ +int +mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { + return 1; +} + +/** + * MySQLSendHandshake + * + * @param dcb The descriptor control block to use for sending the handshake request + * @return The packet length sent + */ +int +MySQLSendHandshake(DCB* dcb) +{ + return 1; +} + +/** + * gw_mysql_do_authentication + * + * Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue + * + * (MYSQL_session*)client_data including: user, db, client_sha1 are copied into + * the dcb->data and later to dcb->session->data. + * + * client_capabilitiesa are copied into the dcb->protocol + * + * @param dcb Descriptor Control Block of the client + * @param queue The GWBUF with data from client + * @return 0 If succeed, otherwise non-zero value + * + * @note in case of failure, dcb->data is freed before returning. If succeed, + * dcb->data is freed in session.c:session_free. + */ +static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { + MySQLProtocol *protocol = NULL; + /* int compress = -1; */ + int connect_with_db = -1; + uint8_t *client_auth_packet = GWBUF_DATA(queue); + int client_auth_packet_size = 0; + char *username = NULL; + char *database = NULL; + unsigned int auth_token_len = 0; + uint8_t *auth_token = NULL; + uint8_t *stage1_hash = NULL; + int auth_ret = -1; + MYSQL_session *client_data = NULL; + + CHK_DCB(dcb); + + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); + client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session)); +#if defined(SS_DEBUG) + client_data->myses_chk_top = CHK_NUM_MYSQLSES; + client_data->myses_chk_tail = CHK_NUM_MYSQLSES; +#endif + /** + * Assign authentication structure with client DCB. + */ + dcb->data = client_data; + + stage1_hash = client_data->client_sha1; + username = client_data->user; + + client_auth_packet_size = gwbuf_length(queue); + + /* For clients supporting CLIENT_PROTOCOL_41 + * the Handshake Response Packet is: + * + * 4 bytes mysql protocol heade + * 4 bytes capability flags + * 4 max-packet size + * 1 byte character set + * string[23] reserved (all [0]) + * ... + * ... + */ + + /* Detect now if there are enough bytes to continue */ + if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) + { + return 1; + } + + memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4); + + connect_with_db = + GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB & gw_mysql_get_byte4( + (uint32_t *)&protocol->client_capabilities); + /* + compress = + GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4( + &protocol->client_capabilities); + */ + + username = get_username_from_auth(username, client_auth_packet); + + if (username == NULL) + { + return 1; + } + + /* get charset */ + memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int)); + + /* get the auth token len */ + memcpy(&auth_token_len, + client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1, + 1); + + /* + * Note: some clients may pass empty database, connect_with_db !=0 but database ="" + */ + if (connect_with_db) { + database = client_data->db; + strncpy(database, + (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + + 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); + } + + /* allocate memory for token only if auth_token_len > 0 */ + if (auth_token_len) { + auth_token = (uint8_t *)malloc(auth_token_len); + memcpy(auth_token, + client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1, + auth_token_len); + } + + /* + * Decode the token and check the password + * Note: if auth_token_len == 0 && auth_token == NULL, user is without password + */ + + auth_ret = gw_check_mysql_scramble_data(dcb, + auth_token, + auth_token_len, + protocol->scramble, + sizeof(protocol->scramble), + username, + stage1_hash); + + /* check for database name match in resource hashtable */ + auth_ret = check_db_name_after_auth(dcb, database, auth_ret); + + /* On failed auth try to load users' table from backend database */ + if (auth_ret != 0) { + if (!service_refresh_users(dcb->service)) { + /* Try authentication again with new repository data */ + /* Note: if no auth client authentication will fail */ + auth_ret = gw_check_mysql_scramble_data( + dcb, + auth_token, + auth_token_len, + protocol->scramble, + sizeof(protocol->scramble), + username, + stage1_hash); + } + else + { + LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, + "%s: login attempt for user %s, user not " + "found.", + dcb->service->name, username))); + } + } + + /* Do again the database check */ + auth_ret = check_db_name_after_auth(dcb, database, auth_ret); + + /* on succesful auth set user into dcb field */ + if (auth_ret == 0) { + dcb->user = strdup(client_data->user); + } + + /* let's free the auth_token now */ + if (auth_token) { + free(auth_token); + } + + return auth_ret; +} + +/** + * Write function for client DCB: writes data from MaxScale to Client + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + */ +int +plain_write(DCB *dcb, GWBUF *queue) +{ + return dcb_write(dcb, queue); +} + +/** + * Client read event triggered by EPOLLIN + * + * @param dcb Descriptor control block + * @return 0 if succeed, 1 otherwise + */ +int plain_read( + DCB* dcb) +{ + SESSION *session = NULL; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + MySQLProtocol *protocol = NULL; + GWBUF *read_buffer = NULL; + int rc = 0; + int nbytes_read = 0; + uint8_t cap = 0; + bool stmt_input = false; /*< router input type */ + + CHK_DCB(dcb); + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); + rc = dcb_read(dcb, &read_buffer); + + if (rc < 0) + { + dcb_close(dcb); + } + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read == 0) + { + goto return_rc; + } + + if(dcb->session == NULL) + { + dcb->session = session_alloc(dcb->service,dcb); + } + + rc = SESSION_ROUTE_QUERY(dcb->session, read_buffer); + + +return_rc: + + return rc; +} + +/////////////////////////////////////////////// +// client write event to Client triggered by EPOLLOUT +////////////////////////////////////////////// +/** + * @node Client's fd became writable, and EPOLLOUT event + * arrived. As a consequence, client input buffer (writeq) is flushed. + * + * Parameters: + * @param dcb - in, use + * client dcb + * + * @return constantly 1 + * + * + * @details (write detailed description here) + * + */ +int plain_write_ready(DCB *dcb) +{ + MySQLProtocol *protocol = NULL; + + CHK_DCB(dcb); + + ss_dassert(dcb->state != DCB_STATE_DISCONNECTED); + + if (dcb == NULL) { + goto return_1; + } + + if (dcb->state == DCB_STATE_DISCONNECTED) { + goto return_1; + } + + if (dcb->protocol == NULL) { + goto return_1; + } + protocol = (MySQLProtocol *)dcb->protocol; + + + dcb_drain_writeq(dcb); + goto return_1; + +return_1: + + return 1; +} + +/** + * set listener for mysql protocol, retur 1 on success and 0 in failure + */ +int plain_listen( + DCB *listen_dcb, + char *config_bind) +{ + int l_so; + int syseno = 0; + struct sockaddr_in serv_addr; + struct sockaddr_un local_addr; + struct sockaddr *current_addr; + int one = 1; + int rc; + + if (strchr(config_bind, '/')) { + char *tmp = strrchr(config_bind, ':'); + if (tmp) + *tmp = '\0'; + + // UNIX socket create + if ((l_so = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, + "\n* Error: can't create UNIX socket due " + "error %i, %s.\n\n\t", + errno, + strerror(errno)); + return 0; + } + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sun_family = AF_UNIX; + strncpy(local_addr.sun_path, config_bind, sizeof(local_addr.sun_path) - 1); + + current_addr = (struct sockaddr *) &local_addr; + + } else { + /* MaxScale, as default, will bind on port 4406 */ + if (!parse_bindconfig(config_bind, 4406, &serv_addr)) { + fprintf(stderr, "Error in parse_bindconfig for [%s]\n", config_bind); + return 0; + } + // TCP socket create + if ((l_so = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, + "\n* Error: can't create socket due " + "error %i, %s.\n\n\t", + errno, + strerror(errno)); + return 0; + } + + current_addr = (struct sockaddr *) &serv_addr; + } + + listen_dcb->fd = -1; + + // socket options + if((syseno = setsockopt(l_so, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one))) != 0){ + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); + } + + + // set NONBLOCKING mode + setnonblocking(l_so); + + /* get the right socket family for bind */ + switch (current_addr->sa_family) { + case AF_UNIX: + rc = unlink(config_bind); + if ( (rc == -1) && (errno!=ENOENT) ) { + fprintf(stderr, "Error unlink Unix Socket %s\n", config_bind); + } + + if (bind(l_so, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) { + fprintf(stderr, + "\n* Bind failed due error %i, %s.\n", + errno, + strerror(errno)); + fprintf(stderr, "* Can't bind to %s\n\n", config_bind); + close(l_so); + return 0; + } + + /* set permission for all users */ + if (chmod(config_bind, 0777) < 0) { + fprintf(stderr, + "\n* chmod failed for %s due error %i, %s.\n\n", + config_bind, + errno, + strerror(errno)); + } + + break; + + case AF_INET: + if (bind(l_so, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + fprintf(stderr, + "\n* Bind failed due error %i, %s.\n", + errno, + strerror(errno)); + fprintf(stderr, "* Can't bind to %s\n\n", config_bind); + close(l_so); + return 0; + } + break; + + default: + fprintf(stderr, "* Socket Family %i not supported\n", current_addr->sa_family); + close(l_so); + return 0; + } + + rc = listen(l_so, 10 * SOMAXCONN); + + if (rc == 0) { + LOGIF(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening MySQL connections at %s", config_bind))); + } else { + int eno = errno; + errno = 0; + fprintf(stderr, + "\n* Failed to start listening MySQL due error %d, %s\n\n", + eno, + strerror(eno)); + close(l_so); + return 0; + } + // assign l_so to dcb + listen_dcb->fd = l_so; + + // add listening socket to poll structure + if (poll_add_dcb(listen_dcb) == -1) { + fprintf(stderr, + "\n* Failed to start polling the socket due error " + "%i, %s.\n\n", + errno, + strerror(errno)); + return 0; + } +#if defined(FAKE_CODE) + conn_open[l_so] = true; +#endif /* FAKE_CODE */ + listen_dcb->func.accept = plain_accept; + + return 1; +} + + +/** + * @node (write brief function description here) + * + * Parameters: + * @param listener - + * + * + * @return 0 in success, 1 in failure + * + * + * @details (write detailed description here) + * + */ +int plain_accept(DCB *listener) +{ + int rc = 0; + DCB *client_dcb; + MySQLProtocol *protocol; + int c_sock; + struct sockaddr client_conn; + socklen_t client_len = sizeof(struct sockaddr_storage); + int sendbuf = GW_BACKEND_SO_SNDBUF; + socklen_t optlen = sizeof(sendbuf); + int eno = 0; + int syseno = 0; + int i = 0; + + CHK_DCB(listener); + + while (1) { + + retry_accept: + +#if defined(FAKE_CODE) + if (fail_next_accept > 0) + { + c_sock = -1; + eno = fail_accept_errno; + fail_next_accept -= 1; + } else { + fail_accept_errno = 0; +#endif /* FAKE_CODE */ + // new connection from client + c_sock = accept(listener->fd, + (struct sockaddr *) &client_conn, + &client_len); + eno = errno; + errno = 0; +#if defined(FAKE_CODE) + } +#endif /* FAKE_CODE */ + + if (c_sock == -1) { + + if (eno == EAGAIN || eno == EWOULDBLOCK) + { + /** + * We have processed all incoming connections. + */ + rc = 1; + goto return_rc; + } + else if (eno == ENFILE || eno == EMFILE) + { + struct timespec ts1; + ts1.tv_sec = 0; + /** + * Exceeded system's (ENFILE) or processes + * (EMFILE) max. number of files limit. + */ + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [plain_accept] Error %d, %s. ", + pthread_self(), + eno, + strerror(eno)))); + + if (i == 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error %d, %s. " + "Failed to accept new client " + "connection.", + eno, + strerror(eno)))); + } + i++; + ts1.tv_nsec = 100*i*i*1000000; + nanosleep(&ts1, NULL); + + if (i<10) { + goto retry_accept; + } + rc = 1; + goto return_rc; + } + else + { + /** + * Other error. + */ + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [plain_accept] Error %d, %s.", + pthread_self(), + eno, + strerror(eno)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to accept new client " + "connection due to %d, %s.", + eno, + strerror(eno)))); + rc = 1; + goto return_rc; + } /* if (eno == ..) */ + } /* if (c_sock == -1) */ + /* reset counter */ + i = 0; + + listener->stats.n_accepts++; +#if defined(SS_DEBUG) + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [plain_accept] Accepted fd %d.", + pthread_self(), + c_sock))); +#endif /* SS_DEBUG */ +#if defined(FAKE_CODE) + conn_open[c_sock] = true; +#endif /* FAKE_CODE */ + /* set nonblocking */ + sendbuf = GW_CLIENT_SO_SNDBUF; + + if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, optlen)) != 0){ + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); + } + + sendbuf = GW_CLIENT_SO_RCVBUF; + + if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_RCVBUF, &sendbuf, optlen)) != 0){ + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); + } + setnonblocking(c_sock); + + client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); + + if (client_dcb == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to create " + "DCB object for client connection."))); + close(c_sock); + rc = 1; + goto return_rc; + } + + client_dcb->service = listener->session->service; + client_dcb->fd = c_sock; + + // get client address + if ( client_conn.sa_family == AF_UNIX) + { + // client address + client_dcb->remote = strdup("localhost_from_socket"); + // set localhost IP for user authentication + (client_dcb->ipv4).sin_addr.s_addr = 0x0100007F; + } + else + { + /* client IPv4 in raw data*/ + memcpy(&client_dcb->ipv4, + (struct sockaddr_in *)&client_conn, + sizeof(struct sockaddr_in)); + /* client IPv4 in string representation */ + client_dcb->remote = (char *)calloc(INET_ADDRSTRLEN+1, sizeof(char)); + + if (client_dcb->remote != NULL) + { + inet_ntop(AF_INET, + &(client_dcb->ipv4).sin_addr, + client_dcb->remote, + INET_ADDRSTRLEN); + } + } + protocol = mysql_protocol_init(client_dcb, c_sock); + ss_dassert(protocol != NULL); + + if (protocol == NULL) { + /** delete client_dcb */ + dcb_close(client_dcb); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [plain_accept] Failed to create " + "protocol object for client connection.", + pthread_self()))); + rc = 1; + goto return_rc; + } + client_dcb->protocol = protocol; + // assign function poiters to "func" field + memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); + + // client protocol state change + protocol->protocol_auth_state = MYSQL_IDLE; + + /** + * Set new descriptor to event set. At the same time, + * change state to DCB_STATE_POLLING so that + * thread which wakes up sees correct state. + */ + if (poll_add_dcb(client_dcb) == -1) + { + /* Send a custom error as MySQL command reply */ + mysql_send_custom_error( + client_dcb, + 1, + 0, + "MaxScale internal error."); + + /** close client_dcb */ + dcb_close(client_dcb); + + /** Previous state is recovered in poll_add_dcb. */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [plain_accept] Failed to add dcb %p for " + "fd %d to epoll set.", + pthread_self(), + client_dcb, + client_dcb->fd))); + rc = 1; + goto return_rc; + } + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [plain_accept] Added dcb %p for fd " + "%d to epoll set.", + pthread_self(), + client_dcb, + client_dcb->fd))); + } + } /**< while 1 */ +#if defined(SS_DEBUG) + if (rc == 0) { + CHK_DCB(client_dcb); + CHK_PROTOCOL(((MySQLProtocol *)client_dcb->protocol)); + } +#endif +return_rc: + + return rc; +} + +static int plain_client_error( + DCB* dcb) +{ + SESSION* session; + + CHK_DCB(dcb); + + session = dcb->session; + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [plain_client_error] Error event handling for DCB %p " + "in state %s, session %p.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + (session != NULL ? session : NULL)))); + + if (session != NULL && session->state == SESSION_STATE_STOPPING) + { + goto retblock; + } + + dcb_close(dcb); + +retblock: + return 1; +} + +static int +plain_client_close(DCB *dcb) +{ + SESSION* session; + ROUTER_OBJECT* router; + void* router_instance; +#if defined(SS_DEBUG) + MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol; + if (dcb->state == DCB_STATE_POLLING || + dcb->state == DCB_STATE_NOPOLLING || + dcb->state == DCB_STATE_ZOMBIE) + { + if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(protocol); + } +#endif + LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, + "%lu [plain_client_close]", + pthread_self()))); + mysql_protocol_done(dcb); + session = dcb->session; + /** + * session may be NULL if session_alloc failed. + * In that case, router session wasn't created. + */ + if (session != NULL) + { + CHK_SESSION(session); + spinlock_acquire(&session->ses_lock); + + if (session->state != SESSION_STATE_STOPPING) + { + session->state = SESSION_STATE_STOPPING; + } + router_instance = session->service->router_instance; + router = session->service->router; + /** + * If router session is being created concurrently router + * session might be NULL and it shouldn't be closed. + */ + if (session->router_session != NULL) + { + spinlock_release(&session->ses_lock); + /** Close router session and all its connections */ + router->closeSession(router_instance, session->router_session); + } + else + { + spinlock_release(&session->ses_lock); + } + } + return 1; +} + +/** + * Handle a hangup event on the client side descriptor. + * + * We simply close the DCB, this will propogate the closure to any + * backend descriptors and perform the session cleanup. + * + * @param dcb The DCB of the connection + */ +static int +plain_client_hangup_event(DCB *dcb) +{ + SESSION* session; + + CHK_DCB(dcb); + session = dcb->session; + + if (session != NULL && session->state == SESSION_STATE_ROUTER_READY) + { + CHK_SESSION(session); + } + + if (session != NULL && session->state == SESSION_STATE_STOPPING) + { + goto retblock; + } + + dcb_close(dcb); + +retblock: + return 1; +} + + +/** + * Detect if buffer includes partial mysql packet or multiple packets. + * Store partial packet to dcb_readqueue. Send complete packets one by one + * to router. + * + * It is assumed readbuf includes at least one complete packet. + * Return 1 in success. If the last packet is incomplete return success but + * leave incomplete packet to readbuf. + * + * @param session Session pointer + * @param p_readbuf Pointer to the address of GWBUF including the query + * + * @return 1 if succeed, + */ +static int route_by_statement( + SESSION* session, + GWBUF** p_readbuf) +{ + int rc; + GWBUF* packetbuf; +#if defined(SS_DEBUG) + GWBUF* tmpbuf; + + tmpbuf = *p_readbuf; + while (tmpbuf != NULL) + { + ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf)); + tmpbuf=tmpbuf->next; + } +#endif + do + { + ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf))); + + /** + * Collect incoming bytes to a buffer until complete packet has + * arrived and then return the buffer. + */ + packetbuf = gw_MySQL_get_next_packet(p_readbuf); + + if (packetbuf != NULL) + { + CHK_GWBUF(packetbuf); + ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf)); + /** + * This means that buffer includes exactly one MySQL + * statement. + * backend func.write uses the information. MySQL backend + * protocol, for example, stores the command identifier + * to protocol structure. When some other thread reads + * the corresponding response the command tells how to + * handle response. + * + * Set it here instead of plain_read to make + * sure it is set to each (MySQL) packet. + */ + gwbuf_set_type(packetbuf, GWBUF_TYPE_SINGLE_STMT); + /** Route query */ + rc = SESSION_ROUTE_QUERY(session, packetbuf); + } + else + { + rc = 1; + goto return_rc; + } + } + while (rc == 1 && *p_readbuf != NULL); + +return_rc: + return rc; +} + diff --git a/server/modules/routing/CMakeLists.txt b/server/modules/routing/CMakeLists.txt index 4cf341d83..a14ca00ad 100644 --- a/server/modules/routing/CMakeLists.txt +++ b/server/modules/routing/CMakeLists.txt @@ -9,6 +9,10 @@ add_library(readconnroute SHARED readconnroute.c) target_link_libraries(readconnroute log_manager utils) install(TARGETS readconnroute DESTINATION modules) +add_library(plainroute SHARED plainroute.c) +target_link_libraries(plainroute log_manager utils) +install(TARGETS plainroute DESTINATION modules) + add_library(debugcli SHARED debugcli.c debugcmd.c) target_link_libraries(debugcli log_manager utils) install(TARGETS debugcli DESTINATION modules) diff --git a/server/modules/routing/plainroute.c b/server/modules/routing/plainroute.c new file mode 100644 index 000000000..245a3427a --- /dev/null +++ b/server/modules/routing/plainroute.c @@ -0,0 +1,923 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file readconnroute.c - Read Connection Load Balancing Query Router + * + * This is the implementation of a simple query router that balances + * read connections. It assumes the service is configured with a set + * of slaves and that the application clients already split read and write + * queries. It offers a service to balance the client read connections + * over this set of slave servers. It does this once only, at the time + * the connection is made. It chooses the server that currently has the least + * number of connections by keeping a count for each server of how + * many connections the query router has made to the server. + * + * When two servers have the same number of current connections the one with + * the least number of connections since startup will be used. + * + * The router may also have options associated to it that will limit the + * choice of backend server. Currently two options are supported, the "master" + * option will cause the router to only connect to servers marked as masters + * and the "slave" option will limit connections to routers that are marked + * as slaves. If neither option is specified the router will connect to either + * masters or slaves. + * + * @verbatim + * Revision History + * + * Date Who Description + * 14/06/2013 Mark Riddoch Initial implementation + * 25/06/2013 Mark Riddoch Addition of checks for current server state + * 26/06/2013 Mark Riddoch Use server with least connections since + * startup if the number of current + * connections is the same for two servers + * Addition of master and slave options + * 27/06/2013 Vilho Raatikka Added skygw_log_write command as an example + * and necessary headers. + * 17/07/2013 Massimiliano Pinto Added clientReply routine: + * called by backend server to send data to client + * Included mysql_client_server_protocol.h + * with macros and MySQL commands with MYSQL_ prefix + * avoiding any conflict with the standard ones + * in mysql.h + * 22/07/2013 Mark Riddoch Addition of joined router option for Galera + * clusters + * 31/07/2013 Massimiliano Pinto Added a check for candidate server, if NULL return + * 12/08/2013 Mark Riddoch Log unsupported router options + * 04/09/2013 Massimiliano Pinto Added client NULL check in clientReply + * 22/10/2013 Massimiliano Pinto errorReply called from backend, for client error reply + * or take different actions such as open a new backend connection + * 20/02/2014 Massimiliano Pinto If router_options=slave, route traffic to master if no slaves available + * 06/03/2014 Massimiliano Pinto Server connection counter is now updated in closeSession + * 24/06/2014 Massimiliano Pinto New rules for selecting the Master server + * 27/06/2014 Mark Riddoch Addition of server weighting + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_GA, + ROUTER_VERSION, + "A connection based router to load balance based on connections" +}; + +static char *version_str = "V1.1.0"; + +/* The router entry points */ +static ROUTER *createInstance(SERVICE *service, char **options); +static void *newSession(ROUTER *instance, SESSION *session); +static void closeSession(ROUTER *instance, void *router_session); +static void freeSession(ROUTER *instance, void *router_session); +static int routeQuery(ROUTER *instance, void *router_session, GWBUF *queue); +static void diagnostics(ROUTER *instance, DCB *dcb); +static void clientReply( + ROUTER *instance, + void *router_session, + GWBUF *queue, + DCB *backend_dcb); +static void handleError( + ROUTER *instance, + void *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + error_action_t action, + bool *succp); +static uint8_t getCapabilities (ROUTER* inst, void* router_session); + + +/** The module object definition */ +static ROUTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + routeQuery, + diagnostics, + clientReply, + handleError, + getCapabilities +}; + +static bool rses_begin_locked_router_action( + ROUTER_CLIENT_SES* rses); + +static void rses_end_locked_router_action( + ROUTER_CLIENT_SES* rses); + +static BACKEND *get_root_master( + BACKEND **servers); +static int handle_state_switch( + DCB* dcb,DCB_REASON reason, void * routersession); +static SPINLOCK instlock; +static ROUTER_INSTANCE *instances; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Initialise readconnroute router module %s.\n", version_str))); + spinlock_init(&instlock); + instances = NULL; +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +ROUTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the router for a particular service + * within the gateway. + * + * @param service The service this router is being create for + * @param options An array of options for this query router + * + * @return The instance data for this new instance + */ +static ROUTER * +createInstance(SERVICE *service, char **options) +{ +ROUTER_INSTANCE *inst; +SERVER *server; +SERVER_REF *sref; +int i, n; +BACKEND *backend; +char *weightby; + + if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { + return NULL; + } + + inst->service = service; + spinlock_init(&inst->lock); + + /* + * We need an array of the backend servers in the instance structure so + * that we can maintain a count of the number of connections to each + * backend server. + */ + for (sref = service->dbref, n = 0; sref; sref = sref->next) + n++; + + inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *)); + if (!inst->servers) + { + free(inst); + return NULL; + } + + for (sref = service->dbref, n = 0; sref; sref = sref->next) + { + if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL) + { + for (i = 0; i < n; i++) + free(inst->servers[i]); + free(inst->servers); + free(inst); + return NULL; + } + inst->servers[n]->server = sref->server; + inst->servers[n]->current_connection_count = 0; + inst->servers[n]->weight = 1000; + n++; + } + inst->servers[n] = NULL; + + if ((weightby = serviceGetWeightingParameter(service)) != NULL) + { + int total = 0; + for (n = 0; inst->servers[n]; n++) + { + backend = inst->servers[n]; + total += atoi(serverGetParameter(backend->server, + weightby)); + } + if (total == 0) + { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "WARNING: Weighting Parameter for service '%s' " + "will be ignored as no servers have values " + "for the parameter '%s'.\n", + service->name, weightby))); + } + else + { + for (n = 0; inst->servers[n]; n++) + { + int perc, wght; + backend = inst->servers[n]; + perc = ((wght = atoi(serverGetParameter(backend->server, + weightby))) * 1000) / total; + if (perc == 0 && wght != 0) + perc = 1; + backend->weight = perc; + if (perc == 0) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Server '%s' has no value " + "for weighting parameter '%s', " + "no queries will be routed to " + "this server.\n", + inst->servers[n]->server->unique_name, + weightby))); + } + + } + } + } + + /* + * Process the options + */ + inst->bitmask = 0; + inst->bitvalue = 0; + if (options) + { + for (i = 0; options[i]; i++) + { + if (!strcasecmp(options[i], "master")) + { + inst->bitmask |= (SERVER_MASTER|SERVER_SLAVE); + inst->bitvalue |= SERVER_MASTER; + } + else if (!strcasecmp(options[i], "slave")) + { + inst->bitmask |= (SERVER_MASTER|SERVER_SLAVE); + inst->bitvalue |= SERVER_SLAVE; + } + else if (!strcasecmp(options[i], "synced")) + { + inst->bitmask |= (SERVER_JOINED); + inst->bitvalue |= SERVER_JOINED; + } + else if (!strcasecmp(options[i], "ndb")) + { + inst->bitmask |= (SERVER_NDB); + inst->bitvalue |= SERVER_NDB; + } + else + { + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : Unsupported router " + "option \'%s\' for readconnroute. " + "Expected router options are " + "[slave|master|synced|ndb]", + options[i]))); + } + } + } + + /* + * We have completed the creation of the instance data, so now + * insert this router instance into the linked list of routers + * that have been created with this module. + */ + spinlock_acquire(&instlock); + inst->next = instances; + instances = inst; + spinlock_release(&instlock); + + return (ROUTER *)inst; +} + +/** + * Associate a new session with this instance of the router. + * + * @param instance The router instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(ROUTER *instance, SESSION *session) +{ +ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; +ROUTER_CLIENT_SES *client_rses; +BACKEND *candidate = NULL; +int i; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [newSession] new router session with session " + "%p, and inst %p.", + pthread_self(), + session, + inst))); + + + client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); + + if (client_rses == NULL) { + return NULL; + } + +#if defined(SS_DEBUG) + client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; + client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; +#endif + + + /** + * Find a backend server to connect to. This is the extent of the + * load balancing algorithm we need to implement for this simple + * connection router. + */ + + /* + * Loop over all the servers and find any that have fewer connections + * than the candidate server. + * + * If a server has less connections than the current candidate we mark this + * as the new candidate to connect to. + * + * If a server has the same number of connections currently as the candidate + * and has had less connections over time than the candidate it will also + * become the new candidate. This has the effect of spreading the + * connections over different servers during periods of very low load. + */ + for (i = 0; inst->servers[i]; i++) { + + + if (SERVER_IN_MAINT(inst->servers[i]->server)) + continue; + + if (inst->servers[i]->weight == 0) + continue; + + + + /* If no candidate set, set first running server as + our initial candidate server */ + if(candidate == NULL) + { + candidate = inst->servers[i]; + } + else if((inst->servers[i]->current_connection_count + * 1000) / inst->servers[i]->weight < + (candidate->current_connection_count * + 1000) / candidate->weight) + { + /* This running server has fewer + connections, set it as a new candidate */ + candidate = inst->servers[i]; + } + else if((inst->servers[i]->current_connection_count + * 1000) / inst->servers[i]->weight == + (candidate->current_connection_count * + 1000) / candidate->weight && + inst->servers[i]->server->stats.n_connections < + candidate->server->stats.n_connections) + { + /* This running server has the same number + of connections currently as the candidate + but has had fewer connections over time + than candidate, set this server to candidate*/ + candidate = inst->servers[i]; + } + } + + + + client_rses->rses_capabilities = RCAP_TYPE_PACKET_INPUT; + + /* + * We now have the server with the least connections. + * Bump the connection count for this server + */ + atomic_add(&candidate->current_connection_count, 1); + client_rses->backend = candidate; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [newSession] Selected server in port %d. " + "Connections : %d\n", + pthread_self(), + candidate->server->port, + candidate->current_connection_count))); + /* + * Open a backend connection, putting the DCB for this + * connection in the client_rses->backend_dcb + */ + client_rses->backend_dcb = dcb_connect(candidate->server, + session, + candidate->server->protocol); + if (client_rses->backend_dcb == NULL) + { + atomic_add(&candidate->current_connection_count, -1); + free(client_rses); + return NULL; + } + dcb_add_callback( + client_rses->backend_dcb, + DCB_REASON_NOT_RESPONDING, + &handle_state_switch, + client_rses); + inst->stats.n_sessions++; + + /** + * Add this session to the list of active sessions. + */ + spinlock_acquire(&inst->lock); + client_rses->next = inst->connections; + inst->connections = client_rses; + spinlock_release(&inst->lock); + + + return (void *)client_rses; +} + +/** + * @node Unlink from backend server, unlink from router's connection list, + * and free memory of a router client session. + * + * Parameters: + * @param router - + * + * + * @param router_cli_ses - + * + * + * @return void + * + * + * @details (write detailed description here) + * + */ +static void freeSession( + ROUTER* router_instance, + void* router_client_ses) +{ + ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_instance; + ROUTER_CLIENT_SES* router_cli_ses = + (ROUTER_CLIENT_SES *)router_client_ses; + int prev_val; + + prev_val = atomic_add(&router_cli_ses->backend->current_connection_count, -1); + ss_dassert(prev_val > 0); + + spinlock_acquire(&router->lock); + + if (router->connections == router_cli_ses) { + router->connections = router_cli_ses->next; + } else { + ROUTER_CLIENT_SES *ptr = router->connections; + + while (ptr != NULL && ptr->next != router_cli_ses) { + ptr = ptr->next; + } + + if (ptr != NULL) { + ptr->next = router_cli_ses->next; + } + } + spinlock_release(&router->lock); + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [freeSession] Unlinked router_client_session %p from " + "router %p and from server on port %d. Connections : %d. ", + pthread_self(), + router_cli_ses, + router, + router_cli_ses->backend->server->port, + prev_val-1))); + + free(router_cli_ses); +} + + +/** + * Close a session with the router, this is the mechanism + * by which a router may cleanup data structure etc. + * + * @param instance The router instance data + * @param router_session The session being closed + */ +static void +closeSession(ROUTER *instance, void *router_session) +{ +ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; +DCB* backend_dcb; + + CHK_CLIENT_RSES(router_cli_ses); + /** + * Lock router client session for secure read and update. + */ + if (rses_begin_locked_router_action(router_cli_ses)) + { + /* decrease server current connection counter */ + atomic_add(&router_cli_ses->backend->server->stats.n_current, -1); + + backend_dcb = router_cli_ses->backend_dcb; + router_cli_ses->backend_dcb = NULL; + router_cli_ses->rses_closed = true; + /** Unlock */ + rses_end_locked_router_action(router_cli_ses); + + /** + * Close the backend server connection + */ + if (backend_dcb != NULL) { + CHK_DCB(backend_dcb); + dcb_close(backend_dcb); + } + } +} + +/** + * We have data from the client, we must route it to the backend. + * This is simply a case of sending it to the connection that was + * chosen when we started the client session. + * + * @param instance The router instance + * @param router_session The router session returned from the newSession call + * @param queue The queue of data buffers to route + * @return if succeed 1, otherwise 0 + */ +static int +routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) +{ + ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + uint8_t *payload = GWBUF_DATA(queue); + int mysql_command; + int rc; + DCB* backend_dcb; + bool rses_is_closed; + + inst->stats.n_queries++; + + /** Dirty read for quick check if router is closed. */ + if (router_cli_ses->rses_closed) + { + rses_is_closed = true; + } + else + { + /** + * Lock router client session for secure read of DCBs + */ + rses_is_closed = !(rses_begin_locked_router_action(router_cli_ses)); + } + + if (!rses_is_closed) + { + backend_dcb = router_cli_ses->backend_dcb; + /** unlock */ + rses_end_locked_router_action(router_cli_ses); + } + + if (rses_is_closed || backend_dcb == NULL) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Failed to route MySQL command %d to backend " + "server.", + mysql_command))); + rc = 0; + goto return_rc; + } + + + rc = backend_dcb->func.write(backend_dcb, queue); + +return_rc: + return rc; +} + +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static void +diagnostics(ROUTER *router, DCB *dcb) +{ +ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router; +ROUTER_CLIENT_SES *session; +int i = 0; +BACKEND *backend; +char *weightby; + + spinlock_acquire(&router_inst->lock); + session = router_inst->connections; + while (session) + { + i++; + session = session->next; + } + spinlock_release(&router_inst->lock); + + dcb_printf(dcb, "\tNumber of router sessions: %d\n", + router_inst->stats.n_sessions); + dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", i); + dcb_printf(dcb, "\tNumber of queries forwarded: %d\n", + router_inst->stats.n_queries); + if ((weightby = serviceGetWeightingParameter(router_inst->service)) + != NULL) + { + dcb_printf(dcb, "\tConnection distribution based on %s " + "server parameter.\n", + weightby); + dcb_printf(dcb, + "\t\tServer Target %% Connections\n"); + for (i = 0; router_inst->servers[i]; i++) + { + backend = router_inst->servers[i]; + dcb_printf(dcb, "\t\t%-20s %3.1f%% %d\n", + backend->server->unique_name, + (float)backend->weight / 10, + backend->current_connection_count); + } + + } +} + +/** + * Client Reply routine + * + * The routine will reply to client data from backend server + * + * @param instance The router instance + * @param router_session The router session + * @param backend_dcb The backend DCB + * @param queue The GWBUF with reply data + */ +static void +clientReply( + ROUTER *instance, + void *router_session, + GWBUF *queue, + DCB *backend_dcb) +{ + DCB *client ; + + client = backend_dcb->session->client; + + ss_dassert(client != NULL); + + SESSION_ROUTE_REPLY(backend_dcb->session, queue); +} + +/** + * Error Handler routine + * + * The routine will handle errors that occurred in backend writes. + * + * @param instance The router instance + * @param router_session The router session + * @param message The error message to reply + * @param backend_dcb The backend DCB + * @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION + * + */ +static void handleError( + ROUTER *instance, + void *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + error_action_t action, + bool *succp) + +{ + DCB *client_dcb; + SESSION *session = backend_dcb->session; + session_state_t sesstate; + + /** Reset error handle flag from a given DCB */ + if (action == ERRACT_RESET) + { + backend_dcb->dcb_errhandle_called = false; + return; + } + + /** Don't handle same error twice on same DCB */ + if (backend_dcb->dcb_errhandle_called) + { + /** we optimistically assume that previous call succeed */ + *succp = true; + return; + } + else + { + backend_dcb->dcb_errhandle_called = true; + } + spinlock_acquire(&session->ses_lock); + sesstate = session->state; + client_dcb = session->client; + + if (sesstate == SESSION_STATE_ROUTER_READY) + { + CHK_DCB(client_dcb); + spinlock_release(&session->ses_lock); + client_dcb->func.write(client_dcb, gwbuf_clone(errbuf)); + } + else + { + spinlock_release(&session->ses_lock); + } + + /** false because connection is not available anymore */ + *succp = false; +} + +/** to be inline'd */ +/** + * @node 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. + * + * + * @details (write detailed description here) + * + */ +static bool rses_begin_locked_router_action( + ROUTER_CLIENT_SES* rses) +{ + bool succp = 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 */ +/** + * @node Releases router client session lock. + * + * Parameters: + * @param rses - + * + * + * @return void + * + * + * @details (write detailed description here) + * + */ +static void rses_end_locked_router_action( + ROUTER_CLIENT_SES* rses) +{ + CHK_CLIENT_RSES(rses); + spinlock_release(&rses->rses_lock); +} + + +static uint8_t getCapabilities( + ROUTER* inst, + void* router_session) +{ + return 0; +} + +/******************************** + * This routine returns the root master server from MySQL replication tree + * Get the root Master rule: + * + * find server with the lowest replication depth level + * and the SERVER_MASTER bitval + * Servers are checked even if they are in 'maintenance' + * + * @param servers The list of servers + * @return The Master found + * + */ + +static BACKEND *get_root_master(BACKEND **servers) { + int i = 0; + BACKEND *master_host = NULL; + + for (i = 0; servers[i]; i++) { + if (servers[i] && (servers[i]->server->status & (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) { + if (master_host && servers[i]->server->depth < master_host->server->depth) { + master_host = servers[i]; + } else { + if (master_host == NULL) { + master_host = servers[i]; + } + } + } + } + return master_host; +} + +static int handle_state_switch(DCB* dcb,DCB_REASON reason, void * routersession) +{ + ss_dassert(dcb != NULL); + SESSION* session = dcb->session; + ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*)routersession; + SERVICE* service = session->service; + ROUTER* router = (ROUTER *)service->router; + + switch(reason) + { + case DCB_REASON_CLOSE: + dcb->func.close(dcb); + break; + case DCB_REASON_DRAINED: + /** Do we need to do anything? */ + break; + case DCB_REASON_HIGH_WATER: + /** Do we need to do anything? */ + break; + case DCB_REASON_LOW_WATER: + /** Do we need to do anything? */ + break; + case DCB_REASON_ERROR: + dcb->func.error(dcb); + break; + case DCB_REASON_HUP: + dcb->func.hangup(dcb); + break; + case DCB_REASON_NOT_RESPONDING: + dcb->func.hangup(dcb); + break; + default: + break; + } + + return 0; +} From b11d66148a23f588254e5da40ed8a566831c18bd Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 24 Mar 2015 19:23:37 +0200 Subject: [PATCH 02/32] Updated plain protocol. --- server/modules/include/plainprotocol.h | 260 +++++++++ server/modules/protocol/mongo_backend.c | 736 +++++++----------------- server/modules/protocol/mongo_client.c | 314 +--------- 3 files changed, 485 insertions(+), 825 deletions(-) create mode 100644 server/modules/include/plainprotocol.h diff --git a/server/modules/include/plainprotocol.h b/server/modules/include/plainprotocol.h new file mode 100644 index 000000000..35ecd1cd0 --- /dev/null +++ b/server/modules/include/plainprotocol.h @@ -0,0 +1,260 @@ +#ifndef _MYSQL_PROTOCOL_H +#define _MYSQL_PROTOCOL_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2015 + */ + +/* + * Revision History + * + * Date Who Description + * 24-03-2015 Markus Makela Initial implementation + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define GW_MYSQL_VERSION "MaxScale " MAXSCALE_VERSION +#define GW_MYSQL_LOOP_TIMEOUT 300000000 +#define GW_MYSQL_READ 0 +#define GW_MYSQL_WRITE 1 +#define MYSQL_HEADER_LEN 4L + +#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_SCRAMBLE_SIZE 20 +#define GW_SCRAMBLE_LENGTH_323 8 + +#ifndef MYSQL_SCRAMBLE_LEN +# define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE +#endif + +#define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR) +#define SMALL_CHUNK 1024 +#define MAX_CHUNK SMALL_CHUNK * 8 * 4 +#define ToHex(Y) (Y>='0'&&Y<='9'?Y-'0':Y-'A'+10) +struct dcb; + +/** Protocol packing macros. */ +#define gw_mysql_set_byte2(__buffer, __int) do { \ + (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ + (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); } while (0) +#define gw_mysql_set_byte3(__buffer, __int) do { \ + (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ + (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \ + (__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); } while (0) +#define gw_mysql_set_byte4(__buffer, __int) do { \ + (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ + (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \ + (__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); \ + (__buffer)[3]= (uint8_t)(((__int) >> 24) & 0xFF); } while (0) + +/** Protocol unpacking macros. */ +#define gw_mysql_get_byte2(__buffer) \ + (uint16_t)((__buffer)[0] | \ + ((__buffer)[1] << 8)) +#define gw_mysql_get_byte3(__buffer) \ + (uint32_t)((__buffer)[0] | \ + ((__buffer)[1] << 8) | \ + ((__buffer)[2] << 16)) +#define gw_mysql_get_byte4(__buffer) \ + (uint32_t)((__buffer)[0] | \ + ((__buffer)[1] << 8) | \ + ((__buffer)[2] << 16) | \ + ((__buffer)[3] << 24)) +#define gw_mysql_get_byte8(__buffer) \ + ((uint64_t)(__buffer)[0] | \ + ((uint64_t)(__buffer)[1] << 8) | \ + ((uint64_t)(__buffer)[2] << 16) | \ + ((uint64_t)(__buffer)[3] << 24) | \ + ((uint64_t)(__buffer)[4] << 32) | \ + ((uint64_t)(__buffer)[5] << 40) | \ + ((uint64_t)(__buffer)[6] << 48) | \ + ((uint64_t)(__buffer)[7] << 56)) + +/** MySQL protocol constants */ +typedef enum +{ + GW_MYSQL_CAPABILITIES_NONE= 0, + GW_MYSQL_CAPABILITIES_LONG_PASSWORD= (1 << 0), + GW_MYSQL_CAPABILITIES_FOUND_ROWS= (1 << 1), + GW_MYSQL_CAPABILITIES_LONG_FLAG= (1 << 2), + GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB= (1 << 3), + GW_MYSQL_CAPABILITIES_NO_SCHEMA= (1 << 4), + GW_MYSQL_CAPABILITIES_COMPRESS= (1 << 5), + GW_MYSQL_CAPABILITIES_ODBC= (1 << 6), + GW_MYSQL_CAPABILITIES_LOCAL_FILES= (1 << 7), + GW_MYSQL_CAPABILITIES_IGNORE_SPACE= (1 << 8), + GW_MYSQL_CAPABILITIES_PROTOCOL_41= (1 << 9), + GW_MYSQL_CAPABILITIES_INTERACTIVE= (1 << 10), + GW_MYSQL_CAPABILITIES_SSL= (1 << 11), + GW_MYSQL_CAPABILITIES_IGNORE_SIGPIPE= (1 << 12), + GW_MYSQL_CAPABILITIES_TRANSACTIONS= (1 << 13), + GW_MYSQL_CAPABILITIES_RESERVED= (1 << 14), + GW_MYSQL_CAPABILITIES_SECURE_CONNECTION= (1 << 15), + GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS= (1 << 16), + 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_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_t; + +/** Copy from enum in mariadb-5.5 mysql_com.h */ +typedef enum mysql_server_cmd { + MYSQL_COM_UNDEFINED = -1, + MYSQL_COM_SLEEP = 0, + MYSQL_COM_QUIT, + MYSQL_COM_INIT_DB, + MYSQL_COM_QUERY, + MYSQL_COM_FIELD_LIST, + MYSQL_COM_CREATE_DB, + MYSQL_COM_DROP_DB, + MYSQL_COM_REFRESH, + MYSQL_COM_SHUTDOWN, + MYSQL_COM_STATISTICS, + MYSQL_COM_PROCESS_INFO, + MYSQL_COM_CONNECT, + MYSQL_COM_PROCESS_KILL, + MYSQL_COM_DEBUG, + MYSQL_COM_PING, + MYSQL_COM_TIME, + MYSQL_COM_DELAYED_INSERT, + MYSQL_COM_CHANGE_USER, + MYSQL_COM_BINLOG_DUMP, + MYSQL_COM_TABLE_DUMP, + MYSQL_COM_CONNECT_OUT, + MYSQL_COM_REGISTER_SLAVE, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_CLOSE, + MYSQL_COM_STMT_RESET, + MYSQL_COM_SET_OPTION, + MYSQL_COM_STMT_FETCH, + MYSQL_COM_DAEMON, + MYSQL_COM_END /*< Must be the last */ +} mysql_server_cmd_t; + +/** + * MySQL Protocol specific state data. + * + * Protocol carries information from client side to backend side, such as + * MySQL session command information and history of earlier session commands. + */ +typedef struct { +#if defined(SS_DEBUG) + skygw_chk_t protocol_chk_top; +#endif + int fd; /*< The socket descriptor */ + struct dcb *owner_dcb; /*< The DCB of the socket + * we are running on */ + SPINLOCK protocol_lock; + 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 */ + unsigned long tid; /*< MySQL Thread ID, in + * handshake */ + unsigned int charset; /*< MySQL character set at connect time */ +#if defined(SS_DEBUG) + skygw_chk_t protocol_chk_tail; +#endif +} PlainProtocol; + + + +#define MYSQL_GET_COMMAND(payload) (payload[4]) +#define MYSQL_GET_PACKET_NO(payload) (payload[3]) +#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) +#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) +#define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9])) +#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) +#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) +#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) +#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02) +#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11) +#define MYSQL_GET_NATTR(payload) ((int)payload[4]) + +#endif /** _MYSQL_PROTOCOL_H */ + +PlainProtocol* mysql_protocol_init(DCB* dcb, int fd); +void mysql_protocol_done (DCB* dcb); +PlainProtocol *gw_mysql_init(PlainProtocol *data); +int gw_receive_backend_auth(PlainProtocol *protocol); +int gw_decode_mysql_server_handshake(PlainProtocol *protocol, uint8_t *payload); +int gw_read_backend_handshake(PlainProtocol *protocol); +int gw_send_authentication_to_backend( + char *dbname, + char *user, + uint8_t *passwd, + PlainProtocol *protocol); + +int plain_do_connect_to_backend(char *host, int port, int* fd); diff --git a/server/modules/protocol/mongo_backend.c b/server/modules/protocol/mongo_backend.c index 4fe1ff0b8..b52663b6a 100644 --- a/server/modules/protocol/mongo_backend.c +++ b/server/modules/protocol/mongo_backend.c @@ -16,12 +16,14 @@ * Copyright MariaDB Corporation Ab 2013-2014 */ -#include "mysql_client_server_protocol.h" +#include #include #include #include #include +#define PLAIN_BACKEND_SO_SNDBUF (128 * 1024) +#define PLAIN_BACKEND_SO_RCVBUF (128 * 1024) /* * MySQL Protocol module for handling the protocol between the gateway * and the backend MySQL database. @@ -51,7 +53,7 @@ MODULE_INFO info = { MODULE_API_PROTOCOL, MODULE_GA, GWPROTOCOL_VERSION, - "The MySQL to backend server protocol" + "The plain protocol" }; /** Defined in log_manager.cc */ @@ -60,38 +62,32 @@ extern size_t log_ses_count[]; extern __thread log_info_t tls_log_info; static char *version_str = "V2.0.0"; -static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session); -static int gw_read_backend_event(DCB* dcb); -static int gw_write_backend_event(DCB *dcb); -static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); -static int gw_error_backend_event(DCB *dcb); -static int gw_backend_close(DCB *dcb); -static int gw_backend_hangup(DCB *dcb); +static int plain_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session); +static int plain_read_backend_event(DCB* dcb); +static int plain_write_ready_backend_event(DCB *dcb); +static int plain_write_backend(DCB *dcb, GWBUF *queue); +static int plain_error_backend_event(DCB *dcb); +static int plain_backend_close(DCB *dcb); +static int plain_backend_hangup(DCB *dcb); static int backend_write_delayqueue(DCB *dcb); static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); -static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); +static int plain_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); static bool sescmd_response_complete(DCB* dcb); - -#if defined(NOT_USED) - static int gw_session(DCB *backend_dcb, void *data); -#endif -static MYSQL_session* gw_get_shared_session_auth_info(DCB* dcb); - static GWPROTOCOL MyObject = { - gw_read_backend_event, /* Read - EPOLLIN handler */ - gw_MySQLWrite_backend, /* Write - data from gateway */ - gw_write_backend_event, /* WriteReady - EPOLLOUT handler */ - gw_error_backend_event, /* Error - EPOLLERR handler */ - gw_backend_hangup, /* HangUp - EPOLLHUP handler */ + plain_read_backend_event, /* Read - EPOLLIN handler */ + plain_write_backend, /* Write - data from gateway */ + plain_write_ready_backend_event, /* WriteReady - EPOLLOUT handler */ + plain_error_backend_event, /* Error - EPOLLERR handler */ + plain_backend_hangup, /* HangUp - EPOLLHUP handler */ NULL, /* Accept */ - gw_create_backend_connection, /* Connect */ - gw_backend_close, /* Close */ + plain_create_backend_connection, /* Connect */ + plain_backend_close, /* Close */ NULL, /* Listen */ - gw_change_user, /* Authentication */ + NULL, /* Authentication */ NULL /* Session */ }; @@ -130,43 +126,63 @@ GetModuleObject() } -static MYSQL_session* gw_get_shared_session_auth_info( - DCB* dcb) +/** + * Creates MySQL protocol structure + * + * @param dcb * Must be non-NULL. + * @param fd + * + * @return + * + * + * @details Protocol structure does not have fd because dcb is not + * connected yet. + * + */ +PlainProtocol* plain_protocol_init( + DCB* dcb, + int fd) { - MYSQL_session* auth_info = NULL; - CHK_DCB(dcb); - CHK_SESSION(dcb->session); + PlainProtocol* p; - spinlock_acquire(&dcb->session->ses_lock); + p = (PlainProtocol *) calloc(1, sizeof(PlainProtocol)); + ss_dassert(p != NULL); - if (dcb->session->state != SESSION_STATE_ALLOC) { - auth_info = dcb->session->data; - } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [gw_get_shared_session_auth_info] Couldn't get " - "session authentication info. Session in a wrong state %d.", - pthread_self(), - dcb->session->state))); + if (p == NULL) { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [mysql_init_protocol] MySQL protocol init failed : " + "memory allocation due error %d, %s.", + pthread_self(), + eno, + strerror(eno)))); + goto return_p; } - spinlock_release(&dcb->session->ses_lock); - return auth_info; + /*< Assign fd with protocol */ + p->fd = fd; + p->owner_dcb = dcb; + +return_p: + return p; } + /** * Backend Read Event for EPOLLIN on the MySQL backend protocol module * @param dcb The backend Descriptor Control Block * @return 1 on operation, 0 for no action */ -static int gw_read_backend_event(DCB *dcb) { - MySQLProtocol *client_protocol = NULL; - MySQLProtocol *backend_protocol = NULL; - MYSQL_session *current_session = NULL; +static int plain_read_backend_event(DCB *dcb) { + PlainProtocol *client_protocol = NULL; + PlainProtocol *backend_protocol = NULL; + int rc = 0; - backend_protocol = (MySQLProtocol *) dcb->protocol; + backend_protocol = (PlainProtocol *) dcb->protocol; CHK_PROTOCOL(backend_protocol); @@ -226,58 +242,12 @@ static int gw_read_backend_event(DCB *dcb) { ss_dassert(read_buffer != NULL || dcb->dcb_readqueue != NULL); } - /** Packet prefix was read earlier */ - if (dcb->dcb_readqueue) - { - if (read_buffer != NULL) - { - read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer); - } - else - { - read_buffer = dcb->dcb_readqueue; - } - nbytes_read = gwbuf_length(read_buffer); - - if (nbytes_read < 5) /*< read at least command type */ - { - rc = 0; - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%p [gw_read_backend_event] Read %d bytes " - "from DCB %p, fd %d, session %s. " - "Returning to poll wait.\n", - pthread_self(), - nbytes_read, - dcb, - dcb->fd, - dcb->session))); - goto return_rc; - } - /** There is at least length and command type. */ - else - { - dcb->dcb_readqueue = NULL; - } - } - /** This may be either short prefix of a packet, or the tail of it. */ - else - { - if (nbytes_read < 5) - { - dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer); - rc = 0; - goto return_rc; - } - } - - if (dcb->session->state == SESSION_STATE_ROUTER_READY && dcb->session->client != NULL && dcb->session->client->state == DCB_STATE_POLLING) { client_protocol = SESSION_PROTOCOL(dcb->session, - MySQLProtocol); + PlainProtocol); { gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); @@ -305,9 +275,9 @@ return_with_lock: * @param dcb The descriptor control block * @return 1 in success, 0 in case of failure, */ -static int gw_write_backend_event(DCB *dcb) { +static int plain_write_ready_backend_event(DCB *dcb) { int rc = 0; - MySQLProtocol *backend_protocol = dcb->protocol; + PlainProtocol *backend_protocol = dcb->protocol; /*< * Don't write to backend if backend_dcb is not in poll set anymore. @@ -343,6 +313,138 @@ return_rc: return rc; } + +/** + * plain_do_connect_to_backend + * + * This routine creates socket and connects to a backend server. + * Connect it non-blocking operation. If connect fails, socket is closed. + * + * @param host The host to connect to + * @param port The host TCP/IP port + * @param *fd where connected fd is copied + * @return 0/1 on success and -1 on failure + * If successful, fd has file descriptor to socket which is connected to + * backend server. In failure, fd == -1 and socket is closed. + * + */ +int plain_do_connect_to_backend( + char *host, + int port, + int *fd) +{ + struct sockaddr_in serv_addr; + int rv; + int so = 0; + int bufsize; + + memset(&serv_addr, 0, sizeof serv_addr); + serv_addr.sin_family = AF_INET; + so = socket(AF_INET,SOCK_STREAM,0); + + if (so < 0) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Establishing connection to backend server " + "%s:%d failed.\n\t\t Socket creation failed " + "due %d, %s.", + host, + port, + errno, + strerror(errno)))); + rv = -1; + goto return_rv; + } + /* prepare for connect */ + setipaddress(&serv_addr.sin_addr, host); + serv_addr.sin_port = htons(port); + bufsize = PLAIN_BACKEND_SO_SNDBUF; + + if(setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to set socket options " + "%s:%d failed.\n\t\t Socket configuration failed " + "due %d, %s.", + host, + port, + errno, + strerror(errno)))); + rv = -1; + /** Close socket */ + goto close_so; + } + bufsize = PLAIN_BACKEND_SO_RCVBUF; + + if(setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to set socket options " + "%s:%d failed.\n\t\t Socket configuration failed " + "due %d, %s.", + host, + port, + errno, + strerror(errno)))); + rv = -1; + /** Close socket */ + goto close_so; + } + + /* set socket to as non-blocking here */ + setnonblocking(so); + rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + + if (rv != 0) + { + if (errno == EINPROGRESS) + { + rv = 1; + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to connect backend server %s:%d, " + "due %d, %s.", + host, + port, + errno, + strerror(errno)))); + /** Close socket */ + goto close_so; + } + } + *fd = so; + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [plain_do_connect_to_backend] Connected to backend server " + "%s:%d, fd %d.", + pthread_self(), + host, + port, + so))); + +return_rv: + return rv; + +close_so: + /*< Close newly created socket. */ + if (close(so) != 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to " + "close socket %d due %d, %s.", + so, + errno, + strerror(errno)))); + } + goto return_rv; +} + /* * Write function for backend DCB. Store command to protocol. * @@ -351,15 +453,13 @@ return_rc: * @return 0 on failure, 1 on success */ static int -gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) +plain_write_backend(DCB *dcb, GWBUF *queue) { - MySQLProtocol *backend_protocol = dcb->protocol; + PlainProtocol *backend_protocol = dcb->protocol; int rc = 0; rc = dcb_write(dcb, queue); - -return_rc: return rc; } @@ -370,7 +470,7 @@ return_rc: * closed and call DCB close function which triggers closing router session * and related backends (if any exists. */ -static int gw_error_backend_event(DCB *dcb) +static int plain_error_backend_event(DCB *dcb) { SESSION* session; void* rsession; @@ -414,11 +514,7 @@ static int gw_error_backend_event(DCB *dcb) } return 1; } - errbuf = mysql_create_custom_error( - 1, - 0, - "Lost connection to backend server."); - + spinlock_acquire(&session->ses_lock); ses_state = session->state; spinlock_release(&session->ses_lock); @@ -501,12 +597,12 @@ retblock: * backend server. Positive fd is copied to protocol and to dcb. * If fails, fd == -1 and socket is closed. */ -static int gw_create_backend_connection( +static int plain_create_backend_connection( DCB *backend_dcb, SERVER *server, SESSION *session) { - MySQLProtocol *protocol = NULL; + PlainProtocol *protocol = NULL; int rv = -1; int fd = -1; @@ -527,7 +623,8 @@ static int gw_create_backend_connection( } /*< if succeed, fd > 0, -1 otherwise */ - rv = gw_do_connect_to_backend(server->name, server->port, &fd); + + rv = plain_do_connect_to_backend(server->name, server->port, &fd); /*< Assign protocol with backend_dcb */ backend_dcb->protocol = protocol; @@ -536,7 +633,7 @@ static int gw_create_backend_connection( case 0: ss_dassert(fd > 0); protocol->fd = fd; - protocol->protocol_auth_state = MYSQL_CONNECTED; + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_create_backend_connection] Established " @@ -551,7 +648,7 @@ static int gw_create_backend_connection( case 1: ss_dassert(fd > 0); - protocol->protocol_auth_state = MYSQL_PENDING_CONNECT; + protocol->fd = fd; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -566,7 +663,7 @@ static int gw_create_backend_connection( default: ss_dassert(fd == -1); - ss_dassert(protocol->protocol_auth_state == MYSQL_ALLOC); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_create_backend_connection] Connection " @@ -595,7 +692,7 @@ return_fd: * @return 1 always */ static int -gw_backend_hangup(DCB *dcb) +plain_backend_hangup(DCB *dcb) { SESSION* session; void* rsession; @@ -612,12 +709,7 @@ gw_backend_hangup(DCB *dcb) rsession = session->router_session; router = session->service->router; router_instance = session->service->router_instance; - - errbuf = mysql_create_custom_error( - 1, - 0, - "Lost connection to backend server."); - + spinlock_acquire(&session->ses_lock); ses_state = session->state; spinlock_release(&session->ses_lock); @@ -695,7 +787,7 @@ retblock: * @return 1 always */ static int -gw_backend_close(DCB *dcb) +plain_backend_close(DCB *dcb) { DCB* client_dcb; SESSION* session; @@ -705,17 +797,6 @@ gw_backend_close(DCB *dcb) session = dcb->session; CHK_SESSION(session); - LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, - "%lu [gw_backend_close]", - pthread_self()))); - - quitbuf = mysql_create_com_quit(NULL, 0); - gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL); - - /** Send COM_QUIT to the backend being closed */ - mysql_send_com_quit(dcb, 0, quitbuf); - - mysql_protocol_done(dcb); /** * The lock is needed only to protect the read of session->state and * session->client values. Client's state may change by other thread @@ -796,409 +877,18 @@ static int backend_write_delayqueue(DCB *dcb) } else { - localq = dcb->delayq; - dcb->delayq = NULL; - spinlock_release(&dcb->delayqlock); - - if (MYSQL_IS_CHANGE_USER(((uint8_t *)GWBUF_DATA(localq)))) - { - MYSQL_session* mses; - GWBUF* new_packet; - - mses = (MYSQL_session *)dcb->session->client->data; - new_packet = gw_create_change_user_packet( - mses, - (MySQLProtocol *)dcb->protocol); - /** - * Remove previous packet which lacks scramble - * and append the new. - */ - localq = gwbuf_consume(localq, GWBUF_LENGTH(localq)); - localq = gwbuf_append(localq, new_packet); - } + rc = dcb_write(dcb, localq); } if (rc == 0) { - GWBUF* errbuf; - bool succp; - ROUTER_OBJECT *router = NULL; - ROUTER *router_instance = NULL; - void *rsession = NULL; - SESSION *session = dcb->session; - - CHK_SESSION(session); - - if (session != NULL) - { - router = session->service->router; - router_instance = session->service->router_instance; - rsession = session->router_session; #if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Backend write delayqueue error handling."))); #endif - errbuf = mysql_create_custom_error( - 1, - 0, - "Failed to write buffered data to back-end server. " - "Buffer was empty or back-end was disconnected during " - "operation. Attempting to find a new backend."); - - router->handleError(router_instance, - rsession, - errbuf, - dcb, - ERRACT_NEW_CONNECTION, - &succp); - gwbuf_free(errbuf); - - if (!succp) - { - spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - ss_dassert(dcb->dcb_errhandle_called); - dcb_close(dcb); - } - } + } return rc; } - -/** - * This routine handles the COM_CHANGE_USER command - * - * @param dcb The current backend DCB - * @param server The backend server pointer - * @param in_session The current session data (MYSQL_session) - * @param queue The GWBUF containing the COM_CHANGE_USER receveid - * @return 1 on success and 0 on failure - */ -static int gw_change_user( - DCB *backend, - SERVER *server, - SESSION *in_session, - GWBUF *queue) -{ - MYSQL_session *current_session = NULL; - MySQLProtocol *backend_protocol = NULL; - MySQLProtocol *client_protocol = NULL; - char username[MYSQL_USER_MAXLEN+1]=""; - char database[MYSQL_DATABASE_MAXLEN+1]=""; - char current_database[MYSQL_DATABASE_MAXLEN+1]=""; - uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]=""; - uint8_t *client_auth_packet = GWBUF_DATA(queue); - unsigned int auth_token_len = 0; - uint8_t *auth_token = NULL; - int rv = -1; - int auth_ret = 1; - - current_session = (MYSQL_session *)in_session->client->data; - backend_protocol = backend->protocol; - client_protocol = in_session->client->protocol; - - /* now get the user, after 4 bytes header and 1 byte command */ - client_auth_packet += 5; - strncpy(username, (char *)client_auth_packet,MYSQL_USER_MAXLEN); - client_auth_packet += strlen(username) + 1; - - /* get the auth token len */ - memcpy(&auth_token_len, client_auth_packet, 1); - - client_auth_packet++; - - /* allocate memory for token only if auth_token_len > 0 */ - if (auth_token_len > 0) { - auth_token = (uint8_t *)malloc(auth_token_len); - ss_dassert(auth_token != NULL); - - if (auth_token == NULL) - return rv; - memcpy(auth_token, client_auth_packet, auth_token_len); - client_auth_packet += auth_token_len; - } - - /* get new database name */ - strncpy(database, (char *)client_auth_packet,MYSQL_DATABASE_MAXLEN); - - /* get character set */ - if (strlen(database)) { - client_auth_packet += strlen(database) + 1; - } else { - client_auth_packet++; - } - - if (client_auth_packet && *client_auth_packet) - memcpy(&backend_protocol->charset, client_auth_packet, sizeof(int)); - - /* save current_database name */ - strncpy(current_database, current_session->db,MYSQL_DATABASE_MAXLEN); - - /* - * Now clear database name in dcb as we don't do local authentication on db name for change user. - * Local authentication only for user@host and if successful the database name change is sent to backend. - */ - strcpy(current_session->db, ""); - - /* - * decode the token and check the password. - * Note: if auth_token_len == 0 && auth_token == NULL, user is without password - */ - auth_ret = gw_check_mysql_scramble_data(backend->session->client, - auth_token, - auth_token_len, - client_protocol->scramble, - sizeof(client_protocol->scramble), - username, - client_sha1); - - if (auth_ret != 0) { - if (!service_refresh_users(backend->session->client->service)) { - /* Try authentication again with new repository data */ - /* Note: if no auth client authentication will fail */ - auth_ret = gw_check_mysql_scramble_data( - backend->session->client, - auth_token, auth_token_len, - client_protocol->scramble, - sizeof(client_protocol->scramble), - username, - client_sha1); - } - } - - /* copy back current datbase to client session */ - strcpy(current_session->db, current_database); - - /* let's free the auth_token now */ - if (auth_token) - free(auth_token); - - if (auth_ret != 0) { - char *password_set = NULL; - char *message = NULL; - GWBUF* buf; - - if (auth_token_len > 0) - password_set = (char *)client_sha1; - else - password_set = ""; - - /** - * Create an error message and make it look like legit reply - * from backend server. Then make it look like an incoming event - * so that thread gets new task of it, calls clientReply - * which filters out duplicate errors from same cause and forward - * reply to the client. - */ - message = create_auth_fail_str(username, - backend->session->client->remote, - password_set, - ""); - if (message == NULL) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Creating error message failed."))); - rv = 0; - goto retblock; - } - /** - * Add command to backend's protocol, create artificial reply - * packet and add it to client's read buffer. - */ - protocol_add_srv_command((MySQLProtocol*)backend->protocol, - MYSQL_COM_CHANGE_USER); - modutil_reply_auth_error(backend, message, 0); - rv = 1; - } else { - rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); - /* - * Now copy new data into user session - */ - strcpy(current_session->user, username); - strcpy(current_session->db, database); - memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1)); - } - -retblock: - gwbuf_free(queue); - - return rv; -} - - -/** - * Move packets or parts of packets from readbuf to outbuf as the packet headers - * and lengths have been noticed and counted. - * Session commands need to be marked so that they can be handled properly in - * the router's clientReply. - * - * @param dcb Backend's DCB where data was read from - * @param readbuf GWBUF where data was read to - * @param nbytes_to_process Number of bytes that has been read and need to be processed - * - * @return GWBUF which includes complete MySQL packet - */ -static GWBUF* process_response_data ( - DCB* dcb, - GWBUF* readbuf, - int nbytes_to_process) -{ - int npackets_left = 0; /*< response's packet count */ - ssize_t nbytes_left = 0; /*< nbytes to be read for the packet */ - MySQLProtocol* p; - GWBUF* outbuf = NULL; - - /** Get command which was stored in gw_MySQLWrite_backend */ - p = DCB_PROTOCOL(dcb, MySQLProtocol); - if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p); - - /** All buffers processed here are sescmd responses */ - gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE); - - /** - * Now it is known how many packets there should be and how much - * is read earlier. - */ - while (nbytes_to_process != 0) - { - mysql_server_cmd_t srvcmd; - bool succp; - - srvcmd = protocol_get_srv_command(p, false); - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [process_response_data] Read command %s for DCB %p fd %d.", - pthread_self(), - STRPACKETTYPE(srvcmd), - dcb, - dcb->fd))); - /** - * Read values from protocol structure, fails if values are - * uninitialized. - */ - if (npackets_left == 0) - { - succp = protocol_get_response_status(p, &npackets_left, &nbytes_left); - - if (!succp || npackets_left == 0) - { - /** - * Examine command type and the readbuf. Conclude response - * packet count from the command type or from the first - * packet content. Fails if read buffer doesn't include - * enough data to read the packet length. - */ - init_response_status(readbuf, srvcmd, &npackets_left, &nbytes_left); - } - } - /** Only session commands with responses should be processed */ - ss_dassert(npackets_left > 0); - - /** Read incomplete packet. */ - if (nbytes_left > nbytes_to_process) - { - /** Includes length info so it can be processed */ - if (nbytes_to_process >= 5) - { - /** discard source buffer */ - readbuf = gwbuf_consume(readbuf, GWBUF_LENGTH(readbuf)); - nbytes_left -= nbytes_to_process; - } - nbytes_to_process = 0; - } - /** Packet was read. All bytes belonged to the last packet. */ - else if (nbytes_left == nbytes_to_process) - { - nbytes_left = 0; - nbytes_to_process = 0; - ss_dassert(npackets_left > 0); - npackets_left -= 1; - outbuf = gwbuf_append(outbuf, readbuf); - readbuf = NULL; - } - /** - * Packet was read. There should be more since bytes were - * left over. - * Move the next packet to its own buffer and add that next - * to the prev packet's buffer. - */ - else /*< nbytes_left < nbytes_to_process */ - { - ss_dassert(nbytes_left >= 0); - nbytes_to_process -= nbytes_left; - - /** Move the prefix of the buffer to outbuf from redbuf */ - outbuf = gwbuf_append(outbuf, - gwbuf_clone_portion(readbuf, 0, (size_t)nbytes_left)); - readbuf = gwbuf_consume(readbuf, (size_t)nbytes_left); - ss_dassert(npackets_left > 0); - npackets_left -= 1; - nbytes_left = 0; - } - - /** Store new status to protocol structure */ - protocol_set_response_status(p, npackets_left, nbytes_left); - - /** A complete packet was read */ - if (nbytes_left == 0) - { - /** No more packets in this response */ - if (npackets_left == 0 && outbuf != NULL) - { - GWBUF* b = outbuf; - - while (b->next != NULL) - { - b = b->next; - } - /** Mark last as end of response */ - gwbuf_set_type(b, GWBUF_TYPE_RESPONSE_END); - - /** Archive the command */ - protocol_archive_srv_command(p); - } - /** Read next packet */ - else - { - uint8_t* data; - - /** Read next packet length */ - data = GWBUF_DATA(readbuf); - nbytes_left = MYSQL_GET_PACKET_LEN(data)+MYSQL_HEADER_LEN; - /** Store new status to protocol structure */ - protocol_set_response_status(p, npackets_left, nbytes_left); - } - } - } - return outbuf; -} - - -static bool sescmd_response_complete( - DCB* dcb) -{ - int npackets_left; - ssize_t nbytes_left; - MySQLProtocol* p; - bool succp; - - p = DCB_PROTOCOL(dcb, MySQLProtocol); - if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p); - - protocol_get_response_status(p, &npackets_left, &nbytes_left); - - if (npackets_left == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} diff --git a/server/modules/protocol/mongo_client.c b/server/modules/protocol/mongo_client.c index 1de7d237d..450634e12 100644 --- a/server/modules/protocol/mongo_client.c +++ b/server/modules/protocol/mongo_client.c @@ -30,14 +30,13 @@ #include #include #include - -#include +#include MODULE_INFO info = { MODULE_API_PROTOCOL, MODULE_GA, GWPROTOCOL_VERSION, - "The client to plain protocol implementation" + "The plain client protocol" }; /** Defined in log_manager.cc */ @@ -107,201 +106,6 @@ GetModuleObject() return &MyObject; } -/** - * mysql_send_ok - * - * Send a MySQL protocol OK message to the dcb (client) - * - * @param dcb Descriptor Control Block for the connection to which the OK is sent - * @param packet_number - * @param in_affected_rows - * @param mysql_message - * @return packet length - * - */ -int -mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { - return 1; -} - -/** - * MySQLSendHandshake - * - * @param dcb The descriptor control block to use for sending the handshake request - * @return The packet length sent - */ -int -MySQLSendHandshake(DCB* dcb) -{ - return 1; -} - -/** - * gw_mysql_do_authentication - * - * Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue - * - * (MYSQL_session*)client_data including: user, db, client_sha1 are copied into - * the dcb->data and later to dcb->session->data. - * - * client_capabilitiesa are copied into the dcb->protocol - * - * @param dcb Descriptor Control Block of the client - * @param queue The GWBUF with data from client - * @return 0 If succeed, otherwise non-zero value - * - * @note in case of failure, dcb->data is freed before returning. If succeed, - * dcb->data is freed in session.c:session_free. - */ -static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { - MySQLProtocol *protocol = NULL; - /* int compress = -1; */ - int connect_with_db = -1; - uint8_t *client_auth_packet = GWBUF_DATA(queue); - int client_auth_packet_size = 0; - char *username = NULL; - char *database = NULL; - unsigned int auth_token_len = 0; - uint8_t *auth_token = NULL; - uint8_t *stage1_hash = NULL; - int auth_ret = -1; - MYSQL_session *client_data = NULL; - - CHK_DCB(dcb); - - protocol = DCB_PROTOCOL(dcb, MySQLProtocol); - CHK_PROTOCOL(protocol); - client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session)); -#if defined(SS_DEBUG) - client_data->myses_chk_top = CHK_NUM_MYSQLSES; - client_data->myses_chk_tail = CHK_NUM_MYSQLSES; -#endif - /** - * Assign authentication structure with client DCB. - */ - dcb->data = client_data; - - stage1_hash = client_data->client_sha1; - username = client_data->user; - - client_auth_packet_size = gwbuf_length(queue); - - /* For clients supporting CLIENT_PROTOCOL_41 - * the Handshake Response Packet is: - * - * 4 bytes mysql protocol heade - * 4 bytes capability flags - * 4 max-packet size - * 1 byte character set - * string[23] reserved (all [0]) - * ... - * ... - */ - - /* Detect now if there are enough bytes to continue */ - if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) - { - return 1; - } - - memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4); - - connect_with_db = - GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB & gw_mysql_get_byte4( - (uint32_t *)&protocol->client_capabilities); - /* - compress = - GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4( - &protocol->client_capabilities); - */ - - username = get_username_from_auth(username, client_auth_packet); - - if (username == NULL) - { - return 1; - } - - /* get charset */ - memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int)); - - /* get the auth token len */ - memcpy(&auth_token_len, - client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1, - 1); - - /* - * Note: some clients may pass empty database, connect_with_db !=0 but database ="" - */ - if (connect_with_db) { - database = client_data->db; - strncpy(database, - (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + - 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); - } - - /* allocate memory for token only if auth_token_len > 0 */ - if (auth_token_len) { - auth_token = (uint8_t *)malloc(auth_token_len); - memcpy(auth_token, - client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1, - auth_token_len); - } - - /* - * Decode the token and check the password - * Note: if auth_token_len == 0 && auth_token == NULL, user is without password - */ - - auth_ret = gw_check_mysql_scramble_data(dcb, - auth_token, - auth_token_len, - protocol->scramble, - sizeof(protocol->scramble), - username, - stage1_hash); - - /* check for database name match in resource hashtable */ - auth_ret = check_db_name_after_auth(dcb, database, auth_ret); - - /* On failed auth try to load users' table from backend database */ - if (auth_ret != 0) { - if (!service_refresh_users(dcb->service)) { - /* Try authentication again with new repository data */ - /* Note: if no auth client authentication will fail */ - auth_ret = gw_check_mysql_scramble_data( - dcb, - auth_token, - auth_token_len, - protocol->scramble, - sizeof(protocol->scramble), - username, - stage1_hash); - } - else - { - LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, - "%s: login attempt for user %s, user not " - "found.", - dcb->service->name, username))); - } - } - - /* Do again the database check */ - auth_ret = check_db_name_after_auth(dcb, database, auth_ret); - - /* on succesful auth set user into dcb field */ - if (auth_ret == 0) { - dcb->user = strdup(client_data->user); - } - - /* let's free the auth_token now */ - if (auth_token) { - free(auth_token); - } - - return auth_ret; -} /** * Write function for client DCB: writes data from MaxScale to Client @@ -328,7 +132,7 @@ int plain_read( ROUTER_OBJECT *router = NULL; ROUTER *router_instance = NULL; void *rsession = NULL; - MySQLProtocol *protocol = NULL; + PlainProtocol *protocol = NULL; GWBUF *read_buffer = NULL; int rc = 0; int nbytes_read = 0; @@ -336,7 +140,7 @@ int plain_read( bool stmt_input = false; /*< router input type */ CHK_DCB(dcb); - protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + protocol = DCB_PROTOCOL(dcb, PlainProtocol); CHK_PROTOCOL(protocol); rc = dcb_read(dcb, &read_buffer); @@ -383,7 +187,7 @@ return_rc: */ int plain_write_ready(DCB *dcb) { - MySQLProtocol *protocol = NULL; + PlainProtocol *protocol = NULL; CHK_DCB(dcb); @@ -400,7 +204,7 @@ int plain_write_ready(DCB *dcb) if (dcb->protocol == NULL) { goto return_1; } - protocol = (MySQLProtocol *)dcb->protocol; + protocol = (PlainProtocol *)dcb->protocol; dcb_drain_writeq(dcb); @@ -575,7 +379,7 @@ int plain_accept(DCB *listener) { int rc = 0; DCB *client_dcb; - MySQLProtocol *protocol; + PlainProtocol *protocol; int c_sock; struct sockaddr client_conn; socklen_t client_len = sizeof(struct sockaddr_storage); @@ -591,24 +395,12 @@ int plain_accept(DCB *listener) retry_accept: -#if defined(FAKE_CODE) - if (fail_next_accept > 0) - { - c_sock = -1; - eno = fail_accept_errno; - fail_next_accept -= 1; - } else { - fail_accept_errno = 0; -#endif /* FAKE_CODE */ // new connection from client c_sock = accept(listener->fd, (struct sockaddr *) &client_conn, &client_len); eno = errno; errno = 0; -#if defined(FAKE_CODE) - } -#endif /* FAKE_CODE */ if (c_sock == -1) { @@ -687,9 +479,7 @@ int plain_accept(DCB *listener) pthread_self(), c_sock))); #endif /* SS_DEBUG */ -#if defined(FAKE_CODE) - conn_open[c_sock] = true; -#endif /* FAKE_CODE */ + /* set nonblocking */ sendbuf = GW_CLIENT_SO_SNDBUF; @@ -762,9 +552,7 @@ int plain_accept(DCB *listener) // assign function poiters to "func" field memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); - // client protocol state change - protocol->protocol_auth_state = MYSQL_IDLE; - + /** * Set new descriptor to event set. At the same time, * change state to DCB_STATE_POLLING so that @@ -773,12 +561,7 @@ int plain_accept(DCB *listener) if (poll_add_dcb(client_dcb) == -1) { /* Send a custom error as MySQL command reply */ - mysql_send_custom_error( - client_dcb, - 1, - 0, - "MaxScale internal error."); - + /** close client_dcb */ dcb_close(client_dcb); @@ -807,7 +590,7 @@ int plain_accept(DCB *listener) #if defined(SS_DEBUG) if (rc == 0) { CHK_DCB(client_dcb); - CHK_PROTOCOL(((MySQLProtocol *)client_dcb->protocol)); + CHK_PROTOCOL(((PlainProtocol *)client_dcb->protocol)); } #endif return_rc: @@ -851,7 +634,7 @@ plain_client_close(DCB *dcb) ROUTER_OBJECT* router; void* router_instance; #if defined(SS_DEBUG) - MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol; + PlainProtocol* protocol = (PlainProtocol *)dcb->protocol; if (dcb->state == DCB_STATE_POLLING || dcb->state == DCB_STATE_NOPOLLING || dcb->state == DCB_STATE_ZOMBIE) @@ -929,76 +712,3 @@ retblock: return 1; } - -/** - * Detect if buffer includes partial mysql packet or multiple packets. - * Store partial packet to dcb_readqueue. Send complete packets one by one - * to router. - * - * It is assumed readbuf includes at least one complete packet. - * Return 1 in success. If the last packet is incomplete return success but - * leave incomplete packet to readbuf. - * - * @param session Session pointer - * @param p_readbuf Pointer to the address of GWBUF including the query - * - * @return 1 if succeed, - */ -static int route_by_statement( - SESSION* session, - GWBUF** p_readbuf) -{ - int rc; - GWBUF* packetbuf; -#if defined(SS_DEBUG) - GWBUF* tmpbuf; - - tmpbuf = *p_readbuf; - while (tmpbuf != NULL) - { - ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf)); - tmpbuf=tmpbuf->next; - } -#endif - do - { - ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf))); - - /** - * Collect incoming bytes to a buffer until complete packet has - * arrived and then return the buffer. - */ - packetbuf = gw_MySQL_get_next_packet(p_readbuf); - - if (packetbuf != NULL) - { - CHK_GWBUF(packetbuf); - ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf)); - /** - * This means that buffer includes exactly one MySQL - * statement. - * backend func.write uses the information. MySQL backend - * protocol, for example, stores the command identifier - * to protocol structure. When some other thread reads - * the corresponding response the command tells how to - * handle response. - * - * Set it here instead of plain_read to make - * sure it is set to each (MySQL) packet. - */ - gwbuf_set_type(packetbuf, GWBUF_TYPE_SINGLE_STMT); - /** Route query */ - rc = SESSION_ROUTE_QUERY(session, packetbuf); - } - else - { - rc = 1; - goto return_rc; - } - } - while (rc == 1 && *p_readbuf != NULL); - -return_rc: - return rc; -} - From c3cb8df8a03705477dd8f31d20a7db161fe4951c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 31 Mar 2015 15:12:50 +0300 Subject: [PATCH 03/32] Renamed mongo protocol to plain protocol. --- server/modules/protocol/{mongo_backend.c => plainbackend.c} | 0 server/modules/protocol/{mongo_client.c => plainclient.c} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename server/modules/protocol/{mongo_backend.c => plainbackend.c} (100%) rename server/modules/protocol/{mongo_client.c => plainclient.c} (100%) diff --git a/server/modules/protocol/mongo_backend.c b/server/modules/protocol/plainbackend.c similarity index 100% rename from server/modules/protocol/mongo_backend.c rename to server/modules/protocol/plainbackend.c diff --git a/server/modules/protocol/mongo_client.c b/server/modules/protocol/plainclient.c similarity index 100% rename from server/modules/protocol/mongo_client.c rename to server/modules/protocol/plainclient.c From 341c04aa483a6d2bd92558f161cd8a5857bfcc98 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 31 Mar 2015 17:27:25 +0300 Subject: [PATCH 04/32] First tests. --- server/modules/monitor/CMakeLists.txt | 2 +- server/modules/monitor/mon_exec.c | 38 ++++++++++++++++++++++++++ server/modules/monitor/mon_exec.h | 13 +++++++++ server/modules/monitor/mysql_mon.c | 14 +++++++++- server/modules/monitor/mysqlmon.h | 1 + server/modules/protocol/CMakeLists.txt | 12 ++++---- 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 server/modules/monitor/mon_exec.c create mode 100644 server/modules/monitor/mon_exec.h diff --git a/server/modules/monitor/CMakeLists.txt b/server/modules/monitor/CMakeLists.txt index a99a66142..3d3f83580 100644 --- a/server/modules/monitor/CMakeLists.txt +++ b/server/modules/monitor/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(mysqlmon SHARED mysql_mon.c) +add_library(mysqlmon SHARED mysql_mon.c mon_exec.c) target_link_libraries(mysqlmon log_manager utils) install(TARGETS mysqlmon DESTINATION modules) diff --git a/server/modules/monitor/mon_exec.c b/server/modules/monitor/mon_exec.c new file mode 100644 index 000000000..122390da9 --- /dev/null +++ b/server/modules/monitor/mon_exec.c @@ -0,0 +1,38 @@ +#include + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +/** + *Execute a command in a separate process. + *@param cmd Command to execute + *@return 0 on success, -1 on error. + */ +int monitor_exec_cmd(char* cmd) +{ + int rval = 0; + pid_t pid; + + pid = fork(); + + if(pid < 0) + { + skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s', fork failed: [%d] %s", + cmd,errno,strerror(errno)); + rval = -1; + } + else if(pid == 0) + { + /** Child process, execute command */ + execl(cmd,cmd,NULL); + } + else + { + LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd)); + } + + return rval; + +} diff --git a/server/modules/monitor/mon_exec.h b/server/modules/monitor/mon_exec.h new file mode 100644 index 000000000..ed277ab3e --- /dev/null +++ b/server/modules/monitor/mon_exec.h @@ -0,0 +1,13 @@ +#ifndef MON_EXEC_HG +#define MON_EXEC_HG + +#include +#include +#include +#include +#include +#include + +int monitor_exec_cmd(char* cmd); + +#endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 9945086fb..51720c225 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -64,6 +64,7 @@ #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -184,6 +185,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; handle->read_timeout=DEFAULT_READ_TIMEOUT; handle->write_timeout=DEFAULT_WRITE_TIMEOUT; + handle->script = NULL; spinlock_init(&handle->lock); } @@ -193,6 +195,8 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->detectStaleMaster = config_truth_value(params->value); else if(!strcmp(params->name,"detect_replication_lag")) handle->replicationHeartbeat = config_truth_value(params->value); + else if(!strcmp(params->name,"script")) + handle->script = strdup(params->value); params = params->next; } @@ -696,7 +700,15 @@ int log_no_master = 1; !(SERVER_IS_IN_CLUSTER(ptr->server))) { dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); - } + } + +/* + if(handle->script) + { + if(monitor_exec_cmd(handle->script)) + skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s' on server state change.",handle->script); + } +*/ } if (mon_status_changed(ptr)) diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index a7d7fb419..3b5658a5a 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -77,6 +77,7 @@ typedef struct { int write_timeout; /**< Timeout in seconds for each attempt to write to the server. * There are retries and the total effective timeout value is two times the option value. */ + char *script;0 } MYSQL_MONITOR; #define MONITOR_RUNNING 1 diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index 0bea95832..7d118c3ec 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -6,13 +6,13 @@ add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c) target_link_libraries(MySQLBackend log_manager utils) install(TARGETS MySQLBackend DESTINATION modules) -add_library(mongoclient SHARED mongo_client.c mysql_common.c) -target_link_libraries(mongoclient log_manager utils) -install(TARGETS mongoclient DESTINATION modules) +add_library(plainclient SHARED plainclient.c mysql_common.c) +target_link_libraries(plainclient log_manager utils) +install(TARGETS plainclient DESTINATION modules) -add_library(mongobackend SHARED mongo_backend.c mysql_common.c) -target_link_libraries(mongobackend log_manager utils) -install(TARGETS mongobackend DESTINATION modules) +add_library(plainbackend SHARED plainbackend.c mysql_common.c) +target_link_libraries(plainbackend log_manager utils) +install(TARGETS plainbackend DESTINATION modules) add_library(telnetd SHARED telnetd.c) From 15164224587231a27afadb0f26441fc32581f864 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 7 Apr 2015 04:35:32 +0300 Subject: [PATCH 05/32] A script can now be called in mysql_mon when a master server loses master status. --- server/modules/monitor/mysql_mon.c | 34 ++++++++++++++++++++---------- server/modules/monitor/mysqlmon.h | 3 ++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 51720c225..0e035d98e 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -22,7 +22,7 @@ * @verbatim * Revision History * - * Date Who Description + * Date Who Demaster_down_scription * 08/07/13 Mark Riddoch Initial implementation * 11/07/13 Mark Riddoch Addition of code to check replication * status @@ -185,7 +185,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; handle->read_timeout=DEFAULT_READ_TIMEOUT; handle->write_timeout=DEFAULT_WRITE_TIMEOUT; - handle->script = NULL; + handle->master_down_script = NULL; spinlock_init(&handle->lock); } @@ -195,8 +195,18 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->detectStaleMaster = config_truth_value(params->value); else if(!strcmp(params->name,"detect_replication_lag")) handle->replicationHeartbeat = config_truth_value(params->value); - else if(!strcmp(params->name,"script")) - handle->script = strdup(params->value); + else if(!strcmp(params->name,"master_down_script")) + { + if(access(params->value,F_OK) == 0) + { + handle->master_down_script = strdup(params->value); + handle->master_down_script_called = 0; + } + else + { + skygw_log_write(LOGFILE_ERROR,"Error: could not find master_down_script file: %s",params->value); + } + } params = params->next; } @@ -688,6 +698,11 @@ int log_no_master = 1; "Server %s:%d lost the master status.", ptr->server->name, ptr->server->port))); + if(handle->master_down_script) + { + if(monitor_exec_cmd(handle->master_down_script)) + skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s' on server state change.",handle->master_down_script); + } } /** * Here we say: If the server's state changed @@ -700,15 +715,12 @@ int log_no_master = 1; !(SERVER_IS_IN_CLUSTER(ptr->server))) { dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); + } -/* - if(handle->script) - { - if(monitor_exec_cmd(handle->script)) - skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s' on server state change.",handle->script); - } -*/ + + + } if (mon_status_changed(ptr)) diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 3b5658a5a..969334b3b 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -77,7 +77,8 @@ typedef struct { int write_timeout; /**< Timeout in seconds for each attempt to write to the server. * There are retries and the total effective timeout value is two times the option value. */ - char *script;0 + char *master_down_script; + int master_down_script_called; } MYSQL_MONITOR; #define MONITOR_RUNNING 1 From cad59abbd1163bb9b81775bfc32438d7e6698277 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 5 May 2015 22:36:47 +0300 Subject: [PATCH 06/32] Added parameters for commands and changed the execv call to execvp. --- server/modules/monitor/mon_exec.c | 122 +++++++++++++++++++++++++++-- server/modules/monitor/mon_exec.h | 10 ++- server/modules/monitor/mysql_mon.c | 19 ++--- server/modules/monitor/mysqlmon.h | 5 +- 4 files changed, 135 insertions(+), 21 deletions(-) diff --git a/server/modules/monitor/mon_exec.c b/server/modules/monitor/mon_exec.c index 122390da9..bae0d31fc 100644 --- a/server/modules/monitor/mon_exec.c +++ b/server/modules/monitor/mon_exec.c @@ -5,12 +5,123 @@ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; extern __thread log_info_t tls_log_info; +/** + * Tokenize a string into arguments suitable for a execvp call. + * @param args Argument string + * @param argv Array of char pointers to be filled with tokenized arguments + * @return 0 on success, -1 on error + */ +int tokenize_arguments(char* args, char** argv) +{ + int i = 0; + bool quoted = false; + bool read = false; + bool escaped = false; + char *ptr,*start; + char qc; + + start = args; + ptr = start; + + while(*ptr != '\0' && i < MAXSCALE_EXTCMD_ARG_MAX) + { + if(escaped) + { + escaped = false; + } + else + { + if(*ptr == '\\') + { + escaped = true; + } + else if(quoted && !escaped && *ptr == qc) /** End of quoted string */ + { + *ptr = '\0'; + argv[i++] = strdup(start); + read = false; + quoted = false; + } + else if (!quoted) + { + if(isspace(*ptr)) + { + *ptr = '\0'; + if(read) /** New token */ + { + argv[i++] = strdup(start); + read = false; + } + } + else if( *ptr == '\"' || *ptr == '\'') + { + /** New quoted token, strip quotes */ + quoted = true; + qc = *ptr; + start = ptr + 1; + } + else if(!read) + { + start = ptr; + read = true; + } + } + } + ptr++; + } + if(read) + argv[i++] = strdup(start); + argv[i] = NULL; + + return 0; +} + +/** + * Allocate a new external command. + * The name and parameters are copied into the external command structure so + * the original memory can be freed if needed. + * @param command Command to execute with the parameters + * @return Pointer to new external command struct or NULL if an error occurred + */ +EXTERNCMD* externcmd_allocate(char* argstr) +{ + EXTERNCMD* cmd; + + if(argstr == NULL) + return NULL; + + if((cmd = (EXTERNCMD*)malloc(sizeof(EXTERNCMD))) != NULL) + { + if(tokenize_arguments(argstr,cmd->parameters) == -1) + { + free(cmd); + return NULL; + } + } + return cmd; +} + +/** + * Free a previously allocated external command. + * @param cmd Command to free + */ +void externcmd_free(EXTERNCMD* cmd) +{ + int i; + + for(i = 0;cmd->parameters[i] != NULL;i++) + { + free(cmd->parameters[i]); + } + free(cmd); +} + /** *Execute a command in a separate process. *@param cmd Command to execute *@return 0 on success, -1 on error. */ -int monitor_exec_cmd(char* cmd) +int externcmd_execute(EXTERNCMD* cmd) { int rval = 0; pid_t pid; @@ -20,19 +131,20 @@ int monitor_exec_cmd(char* cmd) if(pid < 0) { skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s', fork failed: [%d] %s", - cmd,errno,strerror(errno)); + cmd->parameters[0],errno,strerror(errno)); rval = -1; } else if(pid == 0) { /** Child process, execute command */ - execl(cmd,cmd,NULL); + execvp(cmd->parameters[0],cmd->parameters); } else { - LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd)); + cmd->n_exec++; + LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd)); } return rval; - } + diff --git a/server/modules/monitor/mon_exec.h b/server/modules/monitor/mon_exec.h index ed277ab3e..e1ef7ad37 100644 --- a/server/modules/monitor/mon_exec.h +++ b/server/modules/monitor/mon_exec.h @@ -1,13 +1,19 @@ #ifndef MON_EXEC_HG #define MON_EXEC_HG -#include #include #include #include #include #include +#define MAXSCALE_EXTCMD_ARG_MAX 256 -int monitor_exec_cmd(char* cmd); +typedef struct extern_cmd_t{ + char* parameters[MAXSCALE_EXTCMD_ARG_MAX]; /*< Command arguments */ + int n_exec; /*< Number of times executed */ +}EXTERNCMD; +EXTERNCMD* externcmd_allocate(char* argstr); +void externcmd_free(EXTERNCMD* cmd); +int externcmd_execute(EXTERNCMD* cmd); #endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index d52a6902d..aced27a13 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -197,15 +197,9 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->replicationHeartbeat = config_truth_value(params->value); else if(!strcmp(params->name,"master_down_script")) { - if(access(params->value,F_OK) == 0) - { - handle->master_down_script = strdup(params->value); - handle->master_down_script_called = 0; - } - else - { - skygw_log_write(LOGFILE_ERROR,"Error: could not find master_down_script file: %s",params->value); - } + if(handle->master_down_script) + externcmd_free(handle->master_down_script); + handle->master_down_script = externcmd_allocate(params->value); } params = params->next; } @@ -700,8 +694,11 @@ int log_no_master = 1; ptr->server->port))); if(handle->master_down_script) { - if(monitor_exec_cmd(handle->master_down_script)) - skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s' on server state change.",handle->master_down_script); + if(externcmd_execute(handle->master_down_script)) + skygw_log_write(LOGFILE_ERROR, + "Error: Failed to execute command " + "'%s' on server state change.", + handle->master_down_script->parameters[0]); } } /** diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 966bebd15..d6bed5578 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -20,7 +20,7 @@ #include #include #include - +#include /** * @file mysqlmon.h - The MySQL monitor functionality within the gateway * @@ -81,8 +81,7 @@ typedef struct { int write_timeout; /**< Timeout in seconds for each attempt to write to the server. * There are retries and the total effective timeout value is two times the option value. */ - char *master_down_script; - int master_down_script_called; + EXTERNCMD* master_down_script; } MYSQL_MONITOR; #define MONITOR_RUNNING 1 From da6d59798768a5a02c6331621c76e6341947a783 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 6 May 2015 17:27:30 +0300 Subject: [PATCH 07/32] Moved common monitor structures and definitions to a separate header and added master down script to galeramon. --- server/core/CMakeLists.txt | 4 +- server/{modules/monitor => core}/mon_exec.c | 0 .../{modules/monitor => include}/mon_exec.h | 0 server/modules/monitor/CMakeLists.txt | 4 +- .../monitor/{galera_mon.c => galeramon.c} | 51 ++++++++++----- server/modules/monitor/galeramon.h | 65 +++++++++++++++++++ server/modules/monitor/monitor_common.h | 57 ++++++++++++++++ server/modules/monitor/mysqlmon.h | 28 +------- 8 files changed, 163 insertions(+), 46 deletions(-) rename server/{modules/monitor => core}/mon_exec.c (100%) rename server/{modules/monitor => include}/mon_exec.h (100%) rename server/modules/monitor/{galera_mon.c => galeramon.c} (94%) create mode 100644 server/modules/monitor/galeramon.h create mode 100644 server/modules/monitor/monitor_common.h diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 4a9828ea3..e60734237 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_TESTS OR BUILD_TOOLS) - add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c) + add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c mon_exec.c) if(WITH_JEMALLOC) target_link_libraries(fullcore ${JEMALLOC_LIBRARIES}) elseif(WITH_TCMALLOC) @@ -12,7 +12,7 @@ add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c monitor.c adminusers.c secrets.c filter.c modutil.c hint.c - housekeeper.c memlog.c resultset.c) + housekeeper.c memlog.c resultset.c mon_exec.c) if(WITH_JEMALLOC) target_link_libraries(maxscale ${JEMALLOC_LIBRARIES}) diff --git a/server/modules/monitor/mon_exec.c b/server/core/mon_exec.c similarity index 100% rename from server/modules/monitor/mon_exec.c rename to server/core/mon_exec.c diff --git a/server/modules/monitor/mon_exec.h b/server/include/mon_exec.h similarity index 100% rename from server/modules/monitor/mon_exec.h rename to server/include/mon_exec.h diff --git a/server/modules/monitor/CMakeLists.txt b/server/modules/monitor/CMakeLists.txt index 3d3f83580..f53083882 100644 --- a/server/modules/monitor/CMakeLists.txt +++ b/server/modules/monitor/CMakeLists.txt @@ -1,8 +1,8 @@ -add_library(mysqlmon SHARED mysql_mon.c mon_exec.c) +add_library(mysqlmon SHARED mysql_mon.c) target_link_libraries(mysqlmon log_manager utils) install(TARGETS mysqlmon DESTINATION modules) -add_library(galeramon SHARED galera_mon.c) +add_library(galeramon SHARED galeramon.c) target_link_libraries(galeramon log_manager utils) install(TARGETS galeramon DESTINATION modules) diff --git a/server/modules/monitor/galera_mon.c b/server/modules/monitor/galeramon.c similarity index 94% rename from server/modules/monitor/galera_mon.c rename to server/modules/monitor/galeramon.c index ec9e46a61..0991e72bb 100644 --- a/server/modules/monitor/galera_mon.c +++ b/server/modules/monitor/galeramon.c @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,7 +60,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.4.0"; +static char *version_str = "V1.5.0"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -142,16 +142,16 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; +GALERA_MONITOR *handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; if (arg != NULL) { - handle = (MYSQL_MONITOR *)arg; + handle = (GALERA_MONITOR *)arg; handle->shutdown = 0; } else { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL) return NULL; handle->databases = NULL; handle->shutdown = 0; @@ -161,11 +161,12 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->interval = MONITOR_INTERVAL; handle->disableMasterFailback = 0; handle->availableWhenDonor = 0; - handle->disableMasterRoleSetting = 0; + handle->disableMasterRoleSetting = 0; handle->master = NULL; handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; handle->read_timeout=DEFAULT_READ_TIMEOUT; handle->write_timeout=DEFAULT_WRITE_TIMEOUT; + handle->master_down_script = NULL; spinlock_init(&handle->lock); } @@ -178,6 +179,12 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->availableWhenDonor = config_truth_value(params->value); else if(!strcmp(params->name,"disable_master_role_setting")) handle->disableMasterRoleSetting = config_truth_value(params->value); + else if(!strcmp(params->name,"master_down_script")) + { + if(handle->master_down_script) + externcmd_free(handle->master_down_script); + handle->master_down_script = externcmd_allocate(params->value); + } params = params->next; } @@ -193,7 +200,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; handle->shutdown = 1; thread_wait((void *)handle->tid); @@ -209,7 +216,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void registerServer(void *arg, SERVER *server) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; MONITOR_SERVERS *ptr, *db; if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) @@ -239,7 +246,7 @@ MONITOR_SERVERS *ptr, *db; static void unregisterServer(void *arg, SERVER *server) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; MONITOR_SERVERS *ptr, *lptr; spinlock_acquire(&handle->lock); @@ -278,7 +285,7 @@ MONITOR_SERVERS *ptr, *lptr; static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; MONITOR_SERVERS *db; char *sep; @@ -326,7 +333,7 @@ char *sep; static void defaultUsers(void *arg, char *uname, char *passwd) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; if (handle->defaultUser) free(handle->defaultUser); @@ -343,7 +350,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(GALERA_MONITOR *handle, MONITOR_SERVERS *database) { MYSQL_ROW row; MYSQL_RES *result; @@ -497,7 +504,7 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; MONITOR_SERVERS *ptr; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; @@ -550,6 +557,15 @@ int log_no_members = 1; { monitorDatabase(handle, ptr); + if(ptr->mon_prev_status & SERVER_MASTER && + SERVER_IS_DOWN(ptr->server)) + { + if(externcmd_execute(handle->master_down_script) == -1) + { + skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute master server failure script in galeramon."); + } + } + /* clear bits for non member nodes */ if ( ! SERVER_IN_MAINT(ptr->server) && (ptr->server->node_id < 0 || ! SERVER_IS_JOINED(ptr->server))) { ptr->server->depth = -1; @@ -584,6 +600,7 @@ int log_no_members = 1; /** Increase this server'e error count */ dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); ptr->mon_err_count += 1; + } else { @@ -673,7 +690,7 @@ int log_no_members = 1; static void setInterval(void *arg, size_t interval) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->interval, &interval, sizeof(unsigned long)); } @@ -759,7 +776,7 @@ static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *current_master, MONI static void disableMasterFailback(void *arg, int disable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->disableMasterFailback, &disable, sizeof(int)); } @@ -775,7 +792,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void availableWhenDonor(void *arg, int disable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->availableWhenDonor, &disable, sizeof(int)); } @@ -789,7 +806,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void setNetworkTimeout(void *arg, int type, int value) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; int max_timeout = (int)(handle->interval/1000); int new_timeout = max_timeout -1; diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h new file mode 100644 index 000000000..2b804965d --- /dev/null +++ b/server/modules/monitor/galeramon.h @@ -0,0 +1,65 @@ +#ifndef _GALERAMON_H +#define _GALERAMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +#include +#include +#include + +/** + * @file galeramon.h - The Galera cluster monitor + * + * @verbatim + * Revision History + * + * Date Who Description + * 07/05/15 Markus Makela Initial Implementation of galeramon.h + * @endverbatim + */ + + +/** + * The handle for an instance of a MySQL Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + char *defaultUser; /**< Default username for monitoring */ + char *defaultPasswd; /**< Default password for monitoring */ + unsigned long interval; /**< Monitor sampling interval */ + unsigned long id; /**< Monitor ID */ + int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */ + int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */ + int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ + int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ + MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ + MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ + int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ + int read_timeout; /**< Timeout in seconds to read from the server. + * There are retries and the total effective timeout value is three times the option value. + */ + int write_timeout; /**< Timeout in seconds for each attempt to write to the server. + * There are retries and the total effective timeout value is two times the option value. + */ + EXTERNCMD* master_down_script; +} GALERA_MONITOR; + +#endif diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h new file mode 100644 index 000000000..1efbf1dbc --- /dev/null +++ b/server/modules/monitor/monitor_common.h @@ -0,0 +1,57 @@ +#ifndef _MONITOR_COMMON_HG +#define _MONITOR_COMMON_HG +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +#include +#include + +/** + * @file monitor_common.h - The generic monitor structures all monitors use + * + * Revision History + * + * Date Who Description + * 07/05/15 Markus Makela Initial Implementation of galeramon.h + * @endverbatim + */ + +#define MONITOR_RUNNING 1 +#define MONITOR_STOPPING 2 +#define MONITOR_STOPPED 3 + +#define MONITOR_INTERVAL 10000 // in milliseconds +#define MONITOR_DEFAULT_ID 1UL // unsigned long value +#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server + + +/** + * The linked list of servers that are being monitored by the MySQL + * Monitor module. + */ +typedef struct monitor_servers { + SERVER *server; /**< The server being monitored */ + MYSQL *con; /**< The MySQL connection */ + int mon_err_count; + unsigned int mon_prev_status; + unsigned int pending_status; /**< Pending Status flag bitmap */ + struct monitor_servers + *next; /**< The next server in the list */ +} MONITOR_SERVERS; + +#endif \ No newline at end of file diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index d6bed5578..93ba2093e 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -17,9 +17,9 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ -#include + #include -#include +#include #include /** * @file mysqlmon.h - The MySQL monitor functionality within the gateway @@ -37,24 +37,10 @@ * 07/11/14 Massimiliano Pinto Addition of NetworkTimeout: connect, read, write * 20/04/15 Guillaume Lefranc Addition of availableWhenDonor * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting - * + * 07/05/15 Markus Makela Addition of command execution on Master server failure * @endverbatim */ -/** - * The linked list of servers that are being monitored by the MySQL - * Monitor module. - */ -typedef struct monitor_servers { - SERVER *server; /**< The server being monitored */ - MYSQL *con; /**< The MySQL connection */ - int mon_err_count; - unsigned int mon_prev_status; - unsigned int pending_status; /**< Pending Status flag bitmap */ - struct monitor_servers - *next; /**< The next server in the list */ -} MONITOR_SERVERS; - /** * The handle for an instance of a MySQL Monitor module */ @@ -84,12 +70,4 @@ typedef struct { EXTERNCMD* master_down_script; } MYSQL_MONITOR; -#define MONITOR_RUNNING 1 -#define MONITOR_STOPPING 2 -#define MONITOR_STOPPED 3 - -#define MONITOR_INTERVAL 10000 // in milliseconds -#define MONITOR_DEFAULT_ID 1UL // unsigned long value -#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server - #endif From 5a3ed0de9bf8309a8dfc8d338cb8bddd78178165 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 03:54:54 +0300 Subject: [PATCH 08/32] Moved common monitor functionality to the MONITOR type Common variables, like databases, timeouts and interval, and functionality was moved to the MONITOR type. This reduces the redundant functionality of the monitor API's functions like registerServer and setInterval. --- server/core/monitor.c | 98 +++++- server/include/monitor.h | 42 ++- server/modules/monitor/galeramon.c | 252 ++------------- server/modules/monitor/galeramon.h | 13 +- server/modules/monitor/mm_mon.c | 180 ++--------- server/modules/monitor/monitor_common.h | 23 -- server/modules/monitor/mysql_mon.c | 409 +++++++----------------- server/modules/monitor/mysqlmon.h | 13 +- server/modules/monitor/ndbcluster_mon.c | 229 ++----------- 9 files changed, 323 insertions(+), 936 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index a8401e6bb..70f3f9efa 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -80,7 +80,13 @@ MONITOR *mon; } mon->handle = NULL; - + mon->databases = NULL; + mon->name = NULL; + mon->password = NULL; + mon->read_timeout = DEFAULT_READ_TIMEOUT; + mon->write_timeout = DEFAULT_WRITE_TIMEOUT; + mon->connect_timeout = DEFAULT_CONNECT_TIMEOUT; + spinlock_init(&mon->lock); spinlock_acquire(&monLock); mon->next = allMonitors; allMonitors = mon; @@ -100,7 +106,7 @@ monitor_free(MONITOR *mon) { MONITOR *ptr; - mon->module->stopMonitor(mon->handle); + mon->module->stopMonitor(mon); mon->state = MONITOR_STATE_FREED; spinlock_acquire(&monLock); if (allMonitors == mon) @@ -127,7 +133,7 @@ MONITOR *ptr; void monitorStart(MONITOR *monitor, void* params) { - monitor->handle = (*monitor->module->startMonitor)(monitor->handle,params); + monitor->handle = (*monitor->module->startMonitor)(monitor,params); monitor->state = MONITOR_STATE_RUNNING; } @@ -142,7 +148,7 @@ monitorStop(MONITOR *monitor) if(monitor->state != MONITOR_STATE_STOPPED) { monitor->state = MONITOR_STATE_STOPPING; - monitor->module->stopMonitor(monitor->handle); + monitor->module->stopMonitor(monitor); monitor->state = MONITOR_STATE_STOPPED; } } @@ -175,7 +181,30 @@ MONITOR *ptr; void monitorAddServer(MONITOR *mon, SERVER *server) { - mon->module->registerServer(mon->handle, server); + MONITOR_SERVERS *ptr, *db; + + if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) + return; + db->server = server; + db->con = NULL; + db->next = NULL; + db->mon_err_count = 0; + db->mon_prev_status = 0; + /* pending status is updated by get_replication_tree */ + db->pending_status = 0; + + spinlock_acquire(&mon->lock); + + if (mon->databases == NULL) + mon->databases = db; + else + { + ptr = mon->databases; + while (ptr->next != NULL) + ptr = ptr->next; + ptr->next = db; + } + spinlock_release(&mon->lock); } /** @@ -189,7 +218,9 @@ monitorAddServer(MONITOR *mon, SERVER *server) void monitorAddUser(MONITOR *mon, char *user, char *passwd) { - mon->module->defaultUser(mon->handle, user, passwd); + mon->user = strdup(user); + mon->password = strdup(passwd); + //mon->module->defaultUser(mon->handle, user, passwd); } /** @@ -288,10 +319,7 @@ MONITOR *ptr; void monitorSetInterval (MONITOR *mon, unsigned long interval) { - if (mon->module->setInterval != NULL) { - mon->interval = interval; - mon->module->setInterval(mon->handle, interval); - } + mon->interval = interval; } /** @@ -303,9 +331,55 @@ monitorSetInterval (MONITOR *mon, unsigned long interval) */ void monitorSetNetworkTimeout(MONITOR *mon, int type, int value) { - if (mon->module->setNetworkTimeout != NULL) { - mon->module->setNetworkTimeout(mon->handle, type, value); + + int max_timeout = (int)(mon->interval/1000); + int new_timeout = max_timeout -1; + + if (new_timeout <= 0) + new_timeout = DEFAULT_CONNECT_TIMEOUT; + + switch(type) { + case MONITOR_CONNECT_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->connect_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->connect_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); } + break; + + case MONITOR_READ_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->read_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->read_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); + } + break; + + case MONITOR_WRITE_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->write_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->write_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); + } + break; + default: + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); + break; + } } /** diff --git a/server/include/monitor.h b/server/include/monitor.h index e114f3424..681f34bac 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -17,6 +17,7 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ +#include #include #include #include @@ -69,19 +70,14 @@ typedef struct { void *(*startMonitor)(void *, void*); void (*stopMonitor)(void *); - void (*registerServer)(void *, SERVER *); - void (*unregisterServer)(void *, SERVER *); - void (*defaultUser)(void *, char *, char *); void (*diagnostics)(DCB *, void *); - void (*setInterval)(void *, size_t); - void (*setNetworkTimeout)(void *, int, int); } MONITOR_OBJECT; /** * The monitor API version number. Any change to the monitor module API * must change these versions usign the rules defined in modinfo.h */ -#define MONITOR_VERSION {2, 0, 0} +#define MONITOR_VERSION {3, 0, 0} /** Monitor's poll frequency */ #define MON_BASE_INTERVAL_MS 100 @@ -112,12 +108,46 @@ typedef enum #define DEFAULT_READ_TIMEOUT 1 #define DEFAULT_WRITE_TIMEOUT 2 + +#define MONITOR_RUNNING 1 +#define MONITOR_STOPPING 2 +#define MONITOR_STOPPED 3 + +#define MONITOR_INTERVAL 10000 // in milliseconds +#define MONITOR_DEFAULT_ID 1UL // unsigned long value +#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server + + +/** + * The linked list of servers that are being monitored by the monitor module. + */ +typedef struct monitor_servers { + SERVER *server; /**< The server being monitored */ + MYSQL *con; /**< The MySQL connection */ + int mon_err_count; + unsigned int mon_prev_status; + unsigned int pending_status; /**< Pending Status flag bitmap */ + struct monitor_servers + *next; /**< The next server in the list */ +} MONITOR_SERVERS; + /** * Representation of the running monitor. */ typedef struct monitor { char *name; /**< The name of the monitor module */ + char* user; /*< Monitor username */ + char* password; /*< Monitor password */ + SPINLOCK lock; + MONITOR_SERVERS* databases; /*< List of databases the monitor monitors */ monitor_state_t state; /**< The state of the monitor */ + int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ + int read_timeout; /**< Timeout in seconds to read from the server. + * There are retries and the total effective timeout value is three times the option value. + */ + int write_timeout; /**< Timeout in seconds for each attempt to write to the server. + * There are retries and the total effective timeout value is two times the option value. + */ MONITOR_OBJECT *module; /**< The "monitor object" */ void *handle; /**< Handle returned from startMonitor */ size_t interval; /**< The monitor interval */ diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index 0991e72bb..fae50ef4e 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -60,7 +60,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.5.0"; +static char *version_str = "V2.0.0"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -85,13 +85,8 @@ static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_OBJECT MyObject = { startMonitor, - stopMonitor, - registerServer, - unregisterServer, - defaultUsers, - diagnostics, - setInterval, - setNetworkTimeout + stopMonitor, + diagnostics }; /** @@ -142,30 +137,23 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -GALERA_MONITOR *handle; + MONITOR* mon = arg; +GALERA_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg != NULL) + if (handle != NULL) { - handle = (GALERA_MONITOR *)arg; handle->shutdown = 0; } else { if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL) return NULL; - handle->databases = NULL; handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; handle->disableMasterFailback = 0; handle->availableWhenDonor = 0; - handle->disableMasterRoleSetting = 0; + handle->disableMasterRoleSetting = 0; handle->master = NULL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; handle->master_down_script = NULL; spinlock_init(&handle->lock); } @@ -200,82 +188,13 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; handle->shutdown = 1; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - /** * Diagnostic interface * @@ -285,7 +204,8 @@ MONITOR_SERVERS *ptr, *lptr; static void diagnostics(DCB *dcb, void *arg) { -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -302,16 +222,16 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); dcb_printf(dcb,"\tMaster Failback:\t%s\n", (handle->disableMasterFailback == 1) ? "off" : "on"); dcb_printf(dcb,"\tAvailable when Donor:\t%s\n", (handle->availableWhenDonor == 1) ? "on" : "off"); dcb_printf(dcb,"\tMaster Role Setting Disabled:\t%s\n", (handle->disableMasterRoleSetting == 1) ? "on" : "off"); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -322,27 +242,6 @@ char *sep; dcb_printf(dcb, "\n"); } -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUsers(void *arg, char *uname, char *passwd) -{ -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Monitor an individual server * @@ -350,14 +249,14 @@ GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; * @param database The database to probe */ static void -monitorDatabase(GALERA_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) { + GALERA_MONITOR* handle = (GALERA_MONITOR*)mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isjoined = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; +char *uname = mon->user; +char *passwd = mon->password; unsigned long int server_version = 0; char *server_string; @@ -379,16 +278,15 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); if (mysql_real_connect(database->con, database->server->name, uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) @@ -433,9 +331,6 @@ char *server_string; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); - /* get server version from current server */ - server_version = mysql_get_server_version(database->con); - /* get server version string */ server_string = (char *)mysql_get_server_info(database->con); if (server_string) { @@ -448,7 +343,6 @@ char *server_string; if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { if (strcmp(row[1], "4") == 0) @@ -459,7 +353,6 @@ char *server_string; if (mysql_query(database->con, "SHOW VARIABLES LIKE 'wsrep_sst_method'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { if (strncmp(row[1], "xtrabackup", 10) == 0) @@ -476,7 +369,6 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long local_index = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { local_index = strtol(row[1], NULL, 10); @@ -504,7 +396,8 @@ char *server_string; static void monitorMain(void *arg) { -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; @@ -540,7 +433,7 @@ int log_no_members = 1; * round. */ - if (nrounds != 0 && ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= MON_BASE_INTERVAL_MS) + if (nrounds != 0 && ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; continue; @@ -551,11 +444,11 @@ int log_no_members = 1; /* reset cluster members counter */ is_cluster=0; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { - monitorDatabase(handle, ptr); + monitorDatabase(mon, ptr); if(ptr->mon_prev_status & SERVER_MASTER && SERVER_IS_DOWN(ptr->server)) @@ -619,7 +512,7 @@ int log_no_members = 1; */ /* get the candidate master, following MIN(node_id) rule */ - candidate_master = get_candidate_master(handle->databases); + candidate_master = get_candidate_master(mon->databases); /* Select the master, based on master_stickiness */ if (1 == handle->disableMasterRoleSetting) { @@ -629,7 +522,7 @@ int log_no_members = 1; handle->master = set_cluster_master(handle->master, candidate_master, master_stickiness); } - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (!SERVER_IS_JOINED(ptr->server) || SERVER_IN_MAINT(ptr->server)) { @@ -681,19 +574,6 @@ int log_no_members = 1; } } -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} - /** * get candidate master from all nodes * @@ -796,67 +676,6 @@ GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->availableWhenDonor, &disable, sizeof(int)); } -/** - * Set the timeouts to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set - */ -static void -setNetworkTimeout(void *arg, int type, int value) -{ -GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - /** * Check if current monitored server status has changed * @@ -890,10 +709,7 @@ static bool mon_print_fail_status( { bool succp; int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); - + if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) { succp = true; diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 2b804965d..956be528f 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -42,24 +42,13 @@ typedef struct { pthread_t tid; /**< id of monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ - char *defaultUser; /**< Default username for monitoring */ - char *defaultPasswd; /**< Default password for monitoring */ - unsigned long interval; /**< Monitor sampling interval */ unsigned long id; /**< Monitor ID */ int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */ int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */ int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ - MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ - int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ - int read_timeout; /**< Timeout in seconds to read from the server. - * There are retries and the total effective timeout value is three times the option value. - */ - int write_timeout; /**< Timeout in seconds for each attempt to write to the server. - * There are retries and the total effective timeout value is two times the option value. - */ - EXTERNCMD* master_down_script; + EXTERNCMD* master_down_script; } GALERA_MONITOR; #endif diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mm_mon.c index 60acea825..705583f6c 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mm_mon.c @@ -49,7 +49,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.0.1"; +static char *version_str = "V1.1.1"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -60,26 +60,18 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUser(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); static void detectStaleMaster(void *, int); static bool mon_status_changed(MONITOR_SERVERS* mon_srv); static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); -static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *); +static MONITOR_SERVERS *get_current_master(MONITOR *); static void monitor_set_pending_status(MONITOR_SERVERS *, int); static void monitor_clear_pending_status(MONITOR_SERVERS *, int); static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, - registerServer, - unregisterServer, - defaultUser, - diagnostics, - setInterval + diagnostics }; /** @@ -131,23 +123,19 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg) + if (handle) { - handle = arg; /* Must be a restart */ handle->shutdown = 0; } else { if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) return NULL; - handle->databases = NULL; handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; handle->replicationHeartbeat = 0; handle->detectStaleMaster = 0; handle->master = NULL; @@ -179,103 +167,6 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - db->mon_err_count = 0; - db->mon_prev_status = 0; - /* pending status is updated by monitorMain */ - db->pending_status = 0; - - spinlock_acquire(&handle->lock); - - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUser(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Daignostic interface * @@ -284,7 +175,8 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; */ static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -301,11 +193,11 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -327,15 +219,16 @@ char *sep; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR* mon, MONITOR_SERVERS *database) { + MYSQL_MONITOR *handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; int num_fields; int isslave = 0; int ismaster = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; +char *uname = mon->user; +char *passwd = mon->password; unsigned long int server_version = 0; char *server_string; @@ -358,12 +251,11 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; int read_timeout = 1; database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); if (mysql_real_connect(database->con, database->server->name, @@ -437,7 +329,7 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long server_id = -1; - num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) { server_id = strtol(row[0], NULL, 10); @@ -463,7 +355,7 @@ char *server_string; { int i = 0; long master_id = -1; - num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -502,7 +394,7 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long master_id = -1; - num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -534,7 +426,7 @@ char *server_string; if (mysql_query(database->con, "SHOW GLOBAL VARIABLES LIKE 'read_only'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) { if (strncasecmp(row[1], "OFF", 3) == 0) { @@ -584,7 +476,8 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; int detect_stale_master = handle->detectStaleMaster; MONITOR_SERVERS *root_master; @@ -619,7 +512,7 @@ size_t nrounds = 0; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; @@ -628,7 +521,7 @@ size_t nrounds = 0; nrounds += 1; /* start from the first server in the list */ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { @@ -636,7 +529,7 @@ size_t nrounds = 0; ptr->pending_status = ptr->server->status; /* monitor current node */ - monitorDatabase(handle, ptr); + monitorDatabase(mon, ptr); if (mon_status_changed(ptr)) { @@ -668,11 +561,11 @@ size_t nrounds = 0; } /* Get Master server pointer */ - root_master = get_current_master(handle); + root_master = get_current_master(mon); /* Update server status from monitor pending status on that server*/ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (! SERVER_IN_MAINT(ptr->server)) { @@ -691,19 +584,6 @@ size_t nrounds = 0; } } } - -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} /** * Enable/Disable the MySQL Replication Stale Master dectection, allowing a previouvsly detected master to still act as a Master. @@ -716,7 +596,8 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void detectStaleMaster(void *arg, int enable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; memcpy(&handle->detectStaleMaster, &enable, sizeof(int)); } @@ -790,10 +671,11 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) * @return The server at root level with SERVER_MASTER bit */ -static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *handle) { +static MONITOR_SERVERS *get_current_master(MONITOR *mon) { + MYSQL_MONITOR* handle = mon->handle; MONITOR_SERVERS *ptr; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index 1efbf1dbc..e3ba513e4 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -31,27 +31,4 @@ * @endverbatim */ -#define MONITOR_RUNNING 1 -#define MONITOR_STOPPING 2 -#define MONITOR_STOPPED 3 - -#define MONITOR_INTERVAL 10000 // in milliseconds -#define MONITOR_DEFAULT_ID 1UL // unsigned long value -#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server - - -/** - * The linked list of servers that are being monitored by the MySQL - * Monitor module. - */ -typedef struct monitor_servers { - SERVER *server; /**< The server being monitored */ - MYSQL *con; /**< The MySQL connection */ - int mon_err_count; - unsigned int mon_prev_status; - unsigned int pending_status; /**< Pending Status flag bitmap */ - struct monitor_servers - *next; /**< The next server in the list */ -} MONITOR_SERVERS; - #endif \ No newline at end of file diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index aced27a13..19eb91b96 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -84,20 +84,15 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUser(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); static void defaultId(void *, unsigned long); -static void setNetworkTimeout(void *, int, int); static bool mon_status_changed(MONITOR_SERVERS* mon_srv); static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_SERVERS *getServerByNodeId(MONITOR_SERVERS *, long); static MONITOR_SERVERS *getSlaveOfNodeId(MONITOR_SERVERS *, long); -static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *, int); +static MONITOR_SERVERS *get_replication_tree(MONITOR *, int); static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); -static void set_slave_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); +static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *); static int add_slave_to_master(long *, int, long); static void monitor_set_pending_status(MONITOR_SERVERS *, int); static void monitor_clear_pending_status(MONITOR_SERVERS *, int); @@ -105,12 +100,7 @@ static void monitor_clear_pending_status(MONITOR_SERVERS *, int); static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, - registerServer, - unregisterServer, - defaultUser, - diagnostics, - setInterval, - setNetworkTimeout + diagnostics }; /** @@ -157,55 +147,50 @@ GetModuleObject() * This function creates a thread to execute the actual monitoring. * * @param arg The current handle - NULL if first start + * @param opt Configuration parameters * @return A handle to use when interacting with the monitor */ static void * startMonitor(void *arg, void* opt) { -MYSQL_MONITOR *handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg) - { - handle = arg; /* Must be a restart */ - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->databases = NULL; - handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; - handle->id = config_get_gateway_id(); - handle->interval = MONITOR_INTERVAL; - handle->replicationHeartbeat = 0; - handle->detectStaleMaster = 0; - handle->master = NULL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; - handle->master_down_script = NULL; - spinlock_init(&handle->lock); - } + MONITOR* monitor = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR*)monitor->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + if (arg) + { + handle = arg; /* Must be a restart */ + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = config_get_gateway_id(); + handle->replicationHeartbeat = 0; + handle->detectStaleMaster = 0; + handle->master = NULL; + handle->master_down_script = NULL; + spinlock_init(&handle->lock); + } - while(params) + while(params) + { + if(!strcmp(params->name,"detect_stale_master")) + handle->detectStaleMaster = config_truth_value(params->value); + else if(!strcmp(params->name,"detect_replication_lag")) + handle->replicationHeartbeat = config_truth_value(params->value); + else if(!strcmp(params->name,"master_down_script")) { - if(!strcmp(params->name,"detect_stale_master")) - handle->detectStaleMaster = config_truth_value(params->value); - else if(!strcmp(params->name,"detect_replication_lag")) - handle->replicationHeartbeat = config_truth_value(params->value); - else if(!strcmp(params->name,"master_down_script")) - { - if(handle->master_down_script) - externcmd_free(handle->master_down_script); - handle->master_down_script = externcmd_allocate(params->value); - } - params = params->next; + if(handle->master_down_script) + externcmd_free(handle->master_down_script); + handle->master_down_script = externcmd_allocate(params->value); } + params = params->next; + } - handle->tid = (THREAD)thread_start(monitorMain, handle); - return handle; + handle->tid = (THREAD)thread_start(monitorMain, handle); + return handle; } /** @@ -216,107 +201,11 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; - handle->shutdown = 1; - thread_wait((void *)handle->tid); -} - -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - db->mon_err_count = 0; - db->mon_prev_status = 0; - /* pending status is updated by get_replication_tree */ - db->pending_status = 0; - - spinlock_acquire(&handle->lock); - - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUser(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); + handle->shutdown = 1; + thread_wait((void *)handle->tid); } /** @@ -327,45 +216,46 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; */ static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *db; -char *sep; + MONITOR* mon = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; + MONITOR_SERVERS *db; + char *sep; - switch (handle->status) - { - case MONITOR_RUNNING: - dcb_printf(dcb, "\tMonitor running\n"); - break; - case MONITOR_STOPPING: - dcb_printf(dcb, "\tMonitor stopping\n"); - break; - case MONITOR_STOPPED: - dcb_printf(dcb, "\tMonitor stopped\n"); - break; - } + switch (handle->status) + { + case MONITOR_RUNNING: + dcb_printf(dcb, "\tMonitor running\n"); + break; + case MONITOR_STOPPING: + dcb_printf(dcb, "\tMonitor stopping\n"); + break; + case MONITOR_STOPPED: + dcb_printf(dcb, "\tMonitor stopped\n"); + break; + } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); - dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id); - dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled"); - dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); - dcb_printf(dcb, "\tMonitored servers: "); - - db = handle->databases; - sep = ""; - while (db) - { - dcb_printf(dcb, - "%s%s:%d", - sep, - db->server->name, - db->server->port); - sep = ", "; - db = db->next; - } - dcb_printf(dcb, "\n"); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); + dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id); + dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled"); + dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); + dcb_printf(dcb, "\tMonitored servers: "); + + db = mon->databases; + sep = ""; + while (db) + { + dcb_printf(dcb, + "%s%s:%d", + sep, + db->server->name, + db->server->port); + sep = ", "; + db = db->next; + } + dcb_printf(dcb, "\n"); } /** @@ -375,14 +265,14 @@ char *sep; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) { + MYSQL_MONITOR* handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isslave = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; +char *uname = mon->user; +char *passwd = mon->password; unsigned long int server_version = 0; char *server_string; @@ -405,16 +295,15 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); if (mysql_real_connect(database->con, database->server->name, @@ -493,7 +382,6 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long server_id = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { server_id = strtol(row[0], NULL, 10); @@ -519,7 +407,6 @@ char *server_string; { int i = 0; long master_id = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -558,7 +445,6 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long master_id = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -615,7 +501,8 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*) arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; int replication_heartbeat = handle->replicationHeartbeat; int detect_stale_master = handle->detectStaleMaster; @@ -652,7 +539,7 @@ int log_no_master = 1; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; @@ -663,7 +550,7 @@ int log_no_master = 1; num_servers = 0; /* start from the first server in the list */ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { @@ -671,7 +558,7 @@ int log_no_master = 1; ptr->pending_status = ptr->server->status; /* monitor current node */ - monitorDatabase(handle, ptr); + monitorDatabase(mon, ptr); /* reset the slave list of current node */ if (ptr->server->slaves) { @@ -753,7 +640,7 @@ int log_no_master = 1; ptr = ptr->next; } - ptr = handle->databases; + ptr = mon->databases; /* if only one server is configured, that's is Master */ if (num_servers == 1) { if (SERVER_IS_RUNNING(ptr->server)) { @@ -770,12 +657,12 @@ int log_no_master = 1; } } else { /* Compute the replication tree */ - root_master = get_replication_tree(handle, num_servers); + root_master = get_replication_tree(mon, num_servers); } /* Update server status from monitor pending status on that server*/ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (! SERVER_IN_MAINT(ptr->server)) { @@ -852,7 +739,7 @@ int log_no_master = 1; SERVER_IS_RELAY_SERVER(root_master->server))) { set_master_heartbeat(handle, root_master); - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if( (! SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server)) @@ -861,7 +748,7 @@ int log_no_master = 1; (SERVER_IS_SLAVE(ptr->server) || SERVER_IS_RELAY_SERVER(ptr->server))) { - set_slave_heartbeat(handle, ptr); + set_slave_heartbeat(mon, ptr); } } ptr = ptr->next; @@ -883,19 +770,6 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; memcpy(&handle->id, &id, sizeof(unsigned long)); } -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} - /** * Enable/Disable the MySQL Replication hearbeat, detecting slave lag behind master. * @@ -957,9 +831,6 @@ static bool mon_print_fail_status( { bool succp; int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) { @@ -1133,13 +1004,13 @@ static void set_master_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *databas * @param handle The monitor handle * @param database The number database server */ -static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { +static void set_slave_heartbeat(MONITOR* mon, MONITOR_SERVERS *database) { + MYSQL_MONITOR *handle = (MYSQL_MONITOR*)mon->handle; unsigned long id = handle->id; time_t heartbeat; char select_heartbeat_query[256] = ""; MYSQL_ROW row; MYSQL_RES *result; - int num_fields; if (handle->master == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -1159,7 +1030,6 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database if (handle->master !=NULL && (mysql_query(database->con, select_heartbeat_query) == 0 && (result = mysql_store_result(database->con)) != NULL)) { int rows_found = 0; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { int rlag = -1; @@ -1184,7 +1054,7 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database if (rlag >= 0) { /* store rlag only if greater than monitor sampling interval */ - database->server->rlag = (rlag > (handle->interval / 1000)) ? rlag : 0; + database->server->rlag = (rlag > (mon->interval / 1000)) ? rlag : 0; } else { database->server->rlag = -1; } @@ -1238,7 +1108,8 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database * @return The server at root level with SERVER_MASTER bit */ -static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_servers) { +static MONITOR_SERVERS *get_replication_tree(MONITOR *mon, int num_servers) { + MYSQL_MONITOR* handle = (MYSQL_MONITOR*)mon->handle; MONITOR_SERVERS *ptr; MONITOR_SERVERS *backend; SERVER *current; @@ -1246,7 +1117,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv long node_id; int root_level; - ptr = handle->databases; + ptr = mon->databases; root_level = num_servers; while (ptr) @@ -1265,7 +1136,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv node_id = current->master_id; if (node_id < 1) { MONITOR_SERVERS *find_slave; - find_slave = getSlaveOfNodeId(handle->databases, current->node_id); + find_slave = getSlaveOfNodeId(mon->databases, current->node_id); if (find_slave == NULL) { current->depth = -1; @@ -1285,7 +1156,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv root_level = current->depth; handle->master = ptr; } - backend = getServerByNodeId(handle->databases, node_id); + backend = getServerByNodeId(mon->databases, node_id); if (backend) { node_id = backend->server->master_id; @@ -1301,7 +1172,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv MONITOR_SERVERS *master; current->depth = depth; - master = getServerByNodeId(handle->databases, current->master_id); + master = getServerByNodeId(mon->databases, current->master_id); if (master && master->server && master->server->node_id > 0) { add_slave_to_master(master->server->slaves, MONITOR_MAX_NUM_SLAVES, current->node_id); master->server->depth = current->depth -1; @@ -1383,65 +1254,3 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) { ptr->pending_status &= ~bit; } - -/** - * Set the default id to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set - */ -static void -setNetworkTimeout(void *arg, int type, int value) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 93ba2093e..ff2bf0d3e 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -49,9 +49,6 @@ typedef struct { pthread_t tid; /**< id of monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ - char *defaultUser; /**< Default username for monitoring */ - char *defaultPasswd; /**< Default password for monitoring */ - unsigned long interval; /**< Monitor sampling interval */ unsigned long id; /**< Monitor ID */ int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */ int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */ @@ -59,15 +56,7 @@ typedef struct { int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ - MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ - int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ - int read_timeout; /**< Timeout in seconds to read from the server. - * There are retries and the total effective timeout value is three times the option value. - */ - int write_timeout; /**< Timeout in seconds for each attempt to write to the server. - * There are retries and the total effective timeout value is two times the option value. - */ - EXTERNCMD* master_down_script; + EXTERNCMD* master_down_script; } MYSQL_MONITOR; #endif diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbcluster_mon.c index 8f7d00964..ca9ff4210 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbcluster_mon.c @@ -50,7 +50,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.1.0"; +static char *version_str = "V2.1.0"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -61,22 +61,12 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUsers(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); -static void setNetworkTimeout(void *arg, int type, int value); static MONITOR_OBJECT MyObject = { startMonitor, - stopMonitor, - registerServer, - unregisterServer, - defaultUsers, - diagnostics, - setInterval, - setNetworkTimeout + stopMonitor, + diagnostics }; /** @@ -127,26 +117,19 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg != NULL) + if (handle != NULL) { - handle = (MYSQL_MONITOR *)arg; handle->shutdown = 0; } else { if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) return NULL; - handle->databases = NULL; handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; spinlock_init(&handle->lock); } @@ -162,82 +145,13 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; handle->shutdown = 1; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - /** * Diagnostic interface * @@ -247,7 +161,8 @@ MONITOR_SERVERS *ptr, *lptr; static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -264,13 +179,13 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -281,35 +196,15 @@ char *sep; dcb_printf(dcb, "\n"); } -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUsers(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Monitor an individual server * * @param database The database to probe */ static void -monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd, MYSQL_MONITOR *handle) +monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd, MONITOR *mon) { + MYSQL_MONITOR* handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; int num_fields; @@ -334,9 +229,9 @@ char *server_string; { char *dpwd = decryptPassword(passwd); int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; database->con = mysql_init(NULL); @@ -433,7 +328,8 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; long master_id; size_t nrounds = 0; @@ -467,7 +363,7 @@ size_t nrounds = 0; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; @@ -475,12 +371,12 @@ size_t nrounds = 0; } nrounds += 1; master_id = -1; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { unsigned int prev_status = ptr->server->status; - monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd,handle); + monitorDatabase(ptr, mon->user, mon->password,mon); if (ptr->server->status != prev_status || SERVER_IS_DOWN(ptr->server)) @@ -497,78 +393,3 @@ size_t nrounds = 0; } } } - -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} - -/** - * Set the timeouts to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set - */ -static void -setNetworkTimeout(void *arg, int type, int value) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - From ba3c29c72b28fcbf84a511700d384689a42cd074 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 11:01:16 +0300 Subject: [PATCH 09/32] Fixed old function calls using monitor handle instead of MONITOR type. --- server/core/monitor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index 70f3f9efa..9981b2188 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -28,6 +28,7 @@ * and monitor id * 30/10/14 Massimiliano Pinto Addition of disable_master_failback parameter * 07/11/14 Massimiliano Pinto Addition of monitor network timeouts + * 08/05/15 Markus Makela Moved common monitor variables to MONITOR struct * * @endverbatim */ @@ -220,7 +221,6 @@ monitorAddUser(MONITOR *mon, char *user, char *passwd) { mon->user = strdup(user); mon->password = strdup(passwd); - //mon->module->defaultUser(mon->handle, user, passwd); } /** @@ -240,7 +240,7 @@ MONITOR *ptr; dcb_printf(dcb, "Monitor: %p\n", ptr); dcb_printf(dcb, "\tName: %s\n", ptr->name); if (ptr->module->diagnostics) - ptr->module->diagnostics(dcb, ptr->handle); + ptr->module->diagnostics(dcb, ptr); ptr = ptr->next; } spinlock_release(&monLock); @@ -258,7 +258,7 @@ monitorShow(DCB *dcb, MONITOR *monitor) dcb_printf(dcb, "Monitor: %p\n", monitor); dcb_printf(dcb, "\tName: %s\n", monitor->name); if (monitor->module->diagnostics) - monitor->module->diagnostics(dcb, monitor->handle); + monitor->module->diagnostics(dcb, monitor); } /** From 6e1a69df7e4d544c618b482acafad37f4018788e Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 14:03:52 +0300 Subject: [PATCH 10/32] Changed callable monitor scripts to be called with command line parameters instead of having multiple scripts. This reduces the amount of guesswork the monitor has to do when executing the script and places that burden on the called script. --- server/core/monitor.c | 3 +- server/modules/monitor/mysql_mon.c | 138 +++++++++++++++++++++++++---- server/modules/monitor/mysqlmon.h | 2 +- 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index 9981b2188..b2ed8d1a2 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -190,7 +190,8 @@ monitorAddServer(MONITOR *mon, SERVER *server) db->con = NULL; db->next = NULL; db->mon_err_count = 0; - db->mon_prev_status = 0; + /** Server status is uninitialized */ + db->mon_prev_status = -1; /* pending status is updated by get_replication_tree */ db->pending_status = 0; diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index f3a383c2e..5f6fe6535 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -22,7 +22,7 @@ * @verbatim * Revision History * - * Date Who Demaster_down_scription + * Date Who Description * 08/07/13 Mark Riddoch Initial implementation * 11/07/13 Mark Riddoch Addition of code to check replication * status @@ -46,6 +46,7 @@ * 10/11/14 Massimiliano Pinto Addition of setNetworkTimeout for connect, read, write * 18/11/14 Massimiliano Pinto One server only in configuration becomes master. * servers=server1 must be present in mysql_mon and in router sections as well. + * 08/05/15 Markus Makela Added launchable scripts * * @endverbatim */ @@ -66,6 +67,8 @@ #include #include +#define MON_ARG_MAX 8192 + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -96,6 +99,8 @@ static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *); static int add_slave_to_master(long *, int, long); static void monitor_set_pending_status(MONITOR_SERVERS *, int); static void monitor_clear_pending_status(MONITOR_SERVERS *, int); +char* mon_get_event_type(MONITOR_SERVERS*); +void mon_append_node_names(MONITOR_SERVERS*,char*,int); static MONITOR_OBJECT MyObject = { startMonitor, @@ -156,9 +161,8 @@ startMonitor(void *arg, void* opt) MONITOR* monitor = (MONITOR*)arg; MYSQL_MONITOR *handle = (MYSQL_MONITOR*)monitor->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg) + if (handle) { - handle = arg; /* Must be a restart */ handle->shutdown = 0; } else @@ -170,7 +174,7 @@ startMonitor(void *arg, void* opt) handle->replicationHeartbeat = 0; handle->detectStaleMaster = 0; handle->master = NULL; - handle->master_down_script = NULL; + handle->script = NULL; spinlock_init(&handle->lock); } @@ -180,16 +184,16 @@ startMonitor(void *arg, void* opt) handle->detectStaleMaster = config_truth_value(params->value); else if(!strcmp(params->name,"detect_replication_lag")) handle->replicationHeartbeat = config_truth_value(params->value); - else if(!strcmp(params->name,"master_down_script")) + else if(!strcmp(params->name,"script")) { - if(handle->master_down_script) - externcmd_free(handle->master_down_script); - handle->master_down_script = externcmd_allocate(params->value); + if(handle->script) + free(handle->script); + handle->script = strdup(params->value); } params = params->next; } - handle->tid = (THREAD)thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, monitor); return handle; } @@ -554,6 +558,8 @@ int log_no_master = 1; while (ptr) { + ptr->mon_prev_status = ptr->server->status; + /* copy server status into monitor pending_status */ ptr->pending_status = ptr->server->status; @@ -579,14 +585,6 @@ int log_no_master = 1; "Server %s:%d lost the master status.", ptr->server->name, ptr->server->port))); - if(handle->master_down_script) - { - if(externcmd_execute(handle->master_down_script)) - skygw_log_write(LOGFILE_ERROR, - "Error: Failed to execute command " - "'%s' on server state change.", - handle->master_down_script->parameters[0]); - } } /** * Here we say: If the server's state changed @@ -694,6 +692,33 @@ int log_no_master = 1; } } else { ptr->server->status = ptr->pending_status; + + if(mon_status_changed(ptr)) + { + /** Execute monitor script */ + if(handle->script && strcmp(mon_get_event_type(ptr),"unknown") != 0) + { + char argstr[PATH_MAX + MON_ARG_MAX + 1]; + snprintf(argstr,PATH_MAX + MON_ARG_MAX, + "%s --event=%s --node=%s --nodelist=", + handle->script, + mon_get_event_type(ptr), + ptr->server->unique_name); + mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1); + + EXTERNCMD* cmd = externcmd_allocate(argstr); + if(externcmd_execute(cmd)) + { + skygw_log_write(LOGFILE_ERROR, + "Error: Failed to execute script " + "'%s' on server state change.", + handle->script); + } + externcmd_free(cmd); + skygw_log_write(LOGFILE_TRACE,"monitor_state_change: %s: %s", + ptr->server->unique_name,mon_get_event_type(ptr)); + } + } } } ptr = ptr->next; @@ -808,7 +833,11 @@ static bool mon_status_changed( MONITOR_SERVERS* mon_srv) { bool succp; - + + /** This is the first time the server was set with a status*/ + if (mon_srv->mon_prev_status == -1) + return false; + if (mon_srv->mon_prev_status != mon_srv->server->status) { succp = true; @@ -1255,3 +1284,76 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) ptr->pending_status &= ~bit; } +char* mon_get_event_type(MONITOR_SERVERS* node) +{ + unsigned int prev = node->mon_prev_status; + + if((prev & (SERVER_MASTER|SERVER_RUNNING)) == (SERVER_MASTER|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return "master_down"; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return "master_up"; + } + if((prev & (SERVER_SLAVE|SERVER_RUNNING)) == (SERVER_SLAVE|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return "slave_down"; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return "slave_up"; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return "new_master"; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return "new_slave"; + } + if((prev & (SERVER_RUNNING|SERVER_MASTER)) == (SERVER_RUNNING|SERVER_MASTER) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_MASTER(node->server)) + { + return "lost_master"; + } + if((prev & (SERVER_RUNNING|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_SLAVE(node->server)) + { + return "lost_slave"; + } + if((prev & SERVER_RUNNING) == 0 && + SERVER_IS_RUNNING(node->server)) + { + return "server_up"; + } + if((prev & SERVER_RUNNING) == SERVER_RUNNING && + SERVER_IS_DOWN(node->server)) + { + return "server_down"; + } + return "unknown"; +} + +void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) +{ + MONITOR_SERVERS* ptr = start; + bool first = true; + + while(ptr) + { + if(!first) + { + strncat(str,",",len); + } + first = false; + strncat(str,ptr->server->unique_name,len); + ptr = ptr->next; + } +} \ No newline at end of file diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index ff2bf0d3e..836131a63 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -56,7 +56,7 @@ typedef struct { int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ - EXTERNCMD* master_down_script; + char* script; /*< Script to call when state changes occur on servers */ } MYSQL_MONITOR; #endif From 8af302d785a3c3fde9e6cf716e6474d8cdaf8543 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 14:19:25 +0300 Subject: [PATCH 11/32] Removed unused variables from monitors. --- server/modules/monitor/CMakeLists.txt | 8 +- server/modules/monitor/galeramon.c | 14 +-- server/modules/monitor/galeramon.h | 19 +++- server/modules/monitor/mm_mon.c | 15 +-- server/modules/monitor/monitor_common.c | 115 +++++++++++++++++++++++ server/modules/monitor/monitor_common.h | 16 ++-- server/modules/monitor/mysql_mon.c | 118 +----------------------- server/modules/monitor/mysqlmon.h | 20 +++- server/modules/monitor/ndbcluster_mon.c | 31 +------ 9 files changed, 171 insertions(+), 185 deletions(-) create mode 100644 server/modules/monitor/monitor_common.c diff --git a/server/modules/monitor/CMakeLists.txt b/server/modules/monitor/CMakeLists.txt index f53083882..8f364fe5b 100644 --- a/server/modules/monitor/CMakeLists.txt +++ b/server/modules/monitor/CMakeLists.txt @@ -1,16 +1,16 @@ -add_library(mysqlmon SHARED mysql_mon.c) +add_library(mysqlmon SHARED mysql_mon.c monitor_common.c) target_link_libraries(mysqlmon log_manager utils) install(TARGETS mysqlmon DESTINATION modules) -add_library(galeramon SHARED galeramon.c) +add_library(galeramon SHARED galeramon.c monitor_common.c) target_link_libraries(galeramon log_manager utils) install(TARGETS galeramon DESTINATION modules) -add_library(ndbclustermon SHARED ndbcluster_mon.c) +add_library(ndbclustermon SHARED ndbcluster_mon.c monitor_common.c) target_link_libraries(ndbclustermon log_manager utils) install(TARGETS ndbclustermon DESTINATION modules) if(BUILD_MMMON) - add_library(mmmon SHARED mm_mon.c) + add_library(mmmon SHARED mm_mon.c monitor_common.c) target_link_libraries(mmmon log_manager utils) install(TARGETS mmmon DESTINATION modules) endif() diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index 0bc393580..e04fcef8d 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -38,20 +38,8 @@ * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 956be528f..8a9b95fc9 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -18,9 +18,22 @@ * Copyright MariaDB Corporation Ab 2013-2014 */ -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /** * @file galeramon.h - The Galera cluster monitor diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mm_mon.c index 4c6050cab..acb5b20b1 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mm_mon.c @@ -28,20 +28,9 @@ * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c new file mode 100644 index 000000000..b9c6ce28e --- /dev/null +++ b/server/modules/monitor/monitor_common.c @@ -0,0 +1,115 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2015 + */ + +#include + +/** + * Set a pending status bit in the monitor server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status |= bit; +} + +/** + * Clear a pending status bit in the monitor server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status &= ~bit; +} + +char* mon_get_event_type(MONITOR_SERVERS* node) +{ + unsigned int prev = node->mon_prev_status; + + if((prev & (SERVER_MASTER|SERVER_RUNNING)) == (SERVER_MASTER|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return "master_down"; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return "master_up"; + } + if((prev & (SERVER_SLAVE|SERVER_RUNNING)) == (SERVER_SLAVE|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return "slave_down"; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return "slave_up"; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return "new_master"; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return "new_slave"; + } + if((prev & (SERVER_RUNNING|SERVER_MASTER)) == (SERVER_RUNNING|SERVER_MASTER) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_MASTER(node->server)) + { + return "lost_master"; + } + if((prev & (SERVER_RUNNING|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_SLAVE(node->server)) + { + return "lost_slave"; + } + if((prev & SERVER_RUNNING) == 0 && + SERVER_IS_RUNNING(node->server)) + { + return "server_up"; + } + if((prev & SERVER_RUNNING) == SERVER_RUNNING && + SERVER_IS_DOWN(node->server)) + { + return "server_down"; + } + return "unknown"; +} + +void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) +{ + MONITOR_SERVERS* ptr = start; + bool first = true; + + while(ptr) + { + if(!first) + { + strncat(str,",",len); + } + first = false; + strncat(str,ptr->server->unique_name,len); + ptr = ptr->next; + } +} \ No newline at end of file diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index e3ba513e4..2d56c7e81 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -15,20 +15,24 @@ * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright MariaDB Corporation Ab 2013-2014 + * Copyright MariaDB Corporation Ab 2013-2015 */ -#include -#include - +#include +#include +#include /** * @file monitor_common.h - The generic monitor structures all monitors use * * Revision History * * Date Who Description - * 07/05/15 Markus Makela Initial Implementation of galeramon.h + * 07/05/15 Markus Makela Initial Implementation * @endverbatim */ -#endif \ No newline at end of file +void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len); +char* mon_get_event_type(MONITOR_SERVERS* node); +void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit); +void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit); +#endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 5f6fe6535..8cd83763e 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -51,21 +51,9 @@ * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + #define MON_ARG_MAX 8192 @@ -97,10 +85,6 @@ static MONITOR_SERVERS *get_replication_tree(MONITOR *, int); static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *); static int add_slave_to_master(long *, int, long); -static void monitor_set_pending_status(MONITOR_SERVERS *, int); -static void monitor_clear_pending_status(MONITOR_SERVERS *, int); -char* mon_get_event_type(MONITOR_SERVERS*); -void mon_append_node_names(MONITOR_SERVERS*,char*,int); static MONITOR_OBJECT MyObject = { startMonitor, @@ -1259,101 +1243,3 @@ static int add_slave_to_master(long *slaves_list, int list_size, long node_id) { } return 0; } - -/** - * Set a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status |= bit; -} - -/** - * Clear a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status &= ~bit; -} - -char* mon_get_event_type(MONITOR_SERVERS* node) -{ - unsigned int prev = node->mon_prev_status; - - if((prev & (SERVER_MASTER|SERVER_RUNNING)) == (SERVER_MASTER|SERVER_RUNNING) && - SERVER_IS_DOWN(node->server)) - { - return "master_down"; - } - if((prev & (SERVER_RUNNING)) == 0 && - SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) - { - return "master_up"; - } - if((prev & (SERVER_SLAVE|SERVER_RUNNING)) == (SERVER_SLAVE|SERVER_RUNNING) && - SERVER_IS_DOWN(node->server)) - { - return "slave_down"; - } - if((prev & (SERVER_RUNNING)) == 0 && - SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) - { - return "slave_up"; - } - if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && - SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) - { - return "new_master"; - } - if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && - SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) - { - return "new_slave"; - } - if((prev & (SERVER_RUNNING|SERVER_MASTER)) == (SERVER_RUNNING|SERVER_MASTER) && - SERVER_IS_RUNNING(node->server) && !SERVER_IS_MASTER(node->server)) - { - return "lost_master"; - } - if((prev & (SERVER_RUNNING|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE) && - SERVER_IS_RUNNING(node->server) && !SERVER_IS_SLAVE(node->server)) - { - return "lost_slave"; - } - if((prev & SERVER_RUNNING) == 0 && - SERVER_IS_RUNNING(node->server)) - { - return "server_up"; - } - if((prev & SERVER_RUNNING) == SERVER_RUNNING && - SERVER_IS_DOWN(node->server)) - { - return "server_down"; - } - return "unknown"; -} - -void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) -{ - MONITOR_SERVERS* ptr = start; - bool first = true; - - while(ptr) - { - if(!first) - { - strncat(str,",",len); - } - first = false; - strncat(str,ptr->server->unique_name,len); - ptr = ptr->next; - } -} \ No newline at end of file diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 836131a63..f5d0e2a37 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -17,10 +17,22 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ - -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /** * @file mysqlmon.h - The MySQL monitor functionality within the gateway * diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbcluster_mon.c index 7ea091264..26d3db708 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbcluster_mon.c @@ -29,20 +29,9 @@ * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -207,10 +196,8 @@ monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPassw MYSQL_MONITOR* handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isjoined = 0; char *uname = defaultUser, *passwd = defaultPasswd; -unsigned long int server_version = 0; char *server_string; if (database->server->monuser != NULL) @@ -228,7 +215,6 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; int connect_timeout = mon->connect_timeout; int read_timeout = mon->read_timeout; int write_timeout = mon->write_timeout; @@ -237,9 +223,9 @@ char *server_string; mysql_close(database->con); database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); if (mysql_real_connect(database->con, database->server->name, uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) @@ -270,9 +256,6 @@ char *server_string; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); - /* get server version from current server */ - server_version = mysql_get_server_version(database->con); - /* get server version string */ server_string = (char *)mysql_get_server_info(database->con); if (server_string) { @@ -285,7 +268,6 @@ char *server_string; if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { if (atoi(row[1]) > 0) @@ -299,7 +281,6 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long cluster_node_id = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { cluster_node_id = strtol(row[1], NULL, 10); @@ -333,7 +314,6 @@ monitorMain(void *arg) MONITOR* mon = arg; MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; -long master_id; size_t nrounds = 0; if (mysql_thread_init()) @@ -372,7 +352,6 @@ size_t nrounds = 0; continue; } nrounds += 1; - master_id = -1; ptr = mon->databases; while (ptr) From 0c15812340d60f0325d34ffe663c09d85d196506 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 14:29:26 +0300 Subject: [PATCH 12/32] Moved common functions to monitor_common.c --- server/modules/monitor/galeramon.c | 71 +++---------------------- server/modules/monitor/galeramon.h | 2 +- server/modules/monitor/mm_mon.c | 7 +-- server/modules/monitor/monitor_common.c | 51 +++++++++++++++++- server/modules/monitor/monitor_common.h | 2 + server/modules/monitor/mysql_mon.c | 51 ------------------ server/modules/monitor/ndbcluster_mon.c | 6 +-- 7 files changed, 63 insertions(+), 127 deletions(-) diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index e04fcef8d..ea45e8ff1 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -59,17 +59,10 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUsers(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *); static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *, MONITOR_SERVERS *, int); static void disableMasterFailback(void *, int); -static void setNetworkTimeout(void *arg, int type, int value); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_OBJECT MyObject = { startMonitor, @@ -142,7 +135,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->availableWhenDonor = 0; handle->disableMasterRoleSetting = 0; handle->master = NULL; - handle->master_down_script = NULL; + handle->script = NULL; spinlock_init(&handle->lock); } @@ -157,9 +150,9 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->disableMasterRoleSetting = config_truth_value(params->value); else if(!strcmp(params->name,"master_down_script")) { - if(handle->master_down_script) - externcmd_free(handle->master_down_script); - handle->master_down_script = externcmd_allocate(params->value); + if(handle->script) + free(handle->script); + handle->script = strdup(params->value); } params = params->next; } @@ -438,16 +431,9 @@ int log_no_members = 1; while (ptr) { - monitorDatabase(mon, ptr); + ptr->mon_prev_status = ptr->server->status; - if(ptr->mon_prev_status & SERVER_MASTER && - SERVER_IS_DOWN(ptr->server)) - { - if(externcmd_execute(handle->master_down_script) == -1) - { - skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute master server failure script in galeramon."); - } - } + monitorDatabase(mon, ptr); /* clear bits for non member nodes */ if ( ! SERVER_IN_MAINT(ptr->server) && (ptr->server->node_id < 0 || ! SERVER_IS_JOINED(ptr->server))) { @@ -665,48 +651,3 @@ availableWhenDonor(void *arg, int disable) GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->availableWhenDonor, &disable, sizeof(int)); } - -/** - * Check if current monitored server status has changed - * - * @param mon_srv The monitored server - * @return true if status has changed or false - */ -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Check if current monitored server has a loggable failure status - * - * @param mon_srv The monitored server - * @return true if failed status can be logged or false - */ -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - - if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 8a9b95fc9..3a9c41e7f 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -61,7 +61,7 @@ typedef struct { int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ - EXTERNCMD* master_down_script; + char* script; } GALERA_MONITOR; #endif diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mm_mon.c index acb5b20b1..46adc68b5 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mm_mon.c @@ -51,11 +51,7 @@ static void *startMonitor(void *,void*); static void stopMonitor(void *); static void diagnostics(DCB *, void *); static void detectStaleMaster(void *, int); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_SERVERS *get_current_master(MONITOR *); -static void monitor_set_pending_status(MONITOR_SERVERS *, int); -static void monitor_clear_pending_status(MONITOR_SERVERS *, int); static MONITOR_OBJECT MyObject = { startMonitor, @@ -138,7 +134,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; params = params->next; } - handle->tid = (THREAD)thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, mon); return handle; } @@ -213,7 +209,6 @@ monitorDatabase(MONITOR* mon, MONITOR_SERVERS *database) MYSQL_MONITOR *handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isslave = 0; int ismaster = 0; char *uname = mon->user; diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c index b9c6ce28e..2001b2bec 100644 --- a/server/modules/monitor/monitor_common.c +++ b/server/modules/monitor/monitor_common.c @@ -112,4 +112,53 @@ void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) strncat(str,ptr->server->unique_name,len); ptr = ptr->next; } -} \ No newline at end of file +} + +/** + * Check if current monitored server status has changed + * + * @param mon_srv The monitored server + * @return true if status has changed or false + */ +bool mon_status_changed( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + + /** This is the first time the server was set with a status*/ + if (mon_srv->mon_prev_status == -1) + return false; + + if (mon_srv->mon_prev_status != mon_srv->server->status) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} + +/** + * Check if current monitored server has a loggable failure status + * + * @param mon_srv The monitored server + * @return true if failed status can be logged or false + */ +bool mon_print_fail_status( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + int errcount = mon_srv->mon_err_count; + + if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index 2d56c7e81..1c493b10a 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -35,4 +35,6 @@ void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len); char* mon_get_event_type(MONITOR_SERVERS* node); void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit); void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit); +bool mon_status_changed(MONITOR_SERVERS* mon_srv); +bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); #endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 8cd83763e..a60e10e5e 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -77,8 +77,6 @@ static void *startMonitor(void *,void*); static void stopMonitor(void *); static void diagnostics(DCB *, void *); static void defaultId(void *, unsigned long); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_SERVERS *getServerByNodeId(MONITOR_SERVERS *, long); static MONITOR_SERVERS *getSlaveOfNodeId(MONITOR_SERVERS *, long); static MONITOR_SERVERS *get_replication_tree(MONITOR *, int); @@ -807,55 +805,6 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; memcpy(&handle->detectStaleMaster, &enable, sizeof(int)); } -/** - * Check if current monitored server status has changed - * - * @param mon_srv The monitored server - * @return true if status has changed or false - */ -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - /** This is the first time the server was set with a status*/ - if (mon_srv->mon_prev_status == -1) - return false; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Check if current monitored server has a loggable failure status - * - * @param mon_srv The monitored server - * @return true if failed status can be logged or false - */ -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - - if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - /** * Fetch a MySQL node by node_id * diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbcluster_mon.c index 26d3db708..235eb7f00 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbcluster_mon.c @@ -122,7 +122,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; spinlock_init(&handle->lock); } - handle->tid = (THREAD)thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, mon); return handle; } @@ -356,10 +356,10 @@ size_t nrounds = 0; while (ptr) { - unsigned int prev_status = ptr->server->status; + ptr->mon_prev_status = ptr->server->status; monitorDatabase(ptr, mon->user, mon->password,mon); - if (ptr->server->status != prev_status || + if (ptr->server->status != ptr->mon_prev_status || SERVER_IS_DOWN(ptr->server)) { LOGIF(LD, (skygw_log_write_flush( From 9845923f816355acd3786c2cb1315a820643f4e8 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 17:37:34 +0300 Subject: [PATCH 13/32] Added full implementation of MXS-121 to all monitors. --- server/modules/monitor/galeramon.c | 24 +++- server/modules/monitor/mm_mon.c | 76 +++++++---- server/modules/monitor/monitor_common.c | 174 ++++++++++++++++++++++-- server/modules/monitor/monitor_common.h | 36 ++++- server/modules/monitor/mysql_mon.c | 51 +++---- server/modules/monitor/ndbcluster_mon.c | 57 ++++++-- 6 files changed, 334 insertions(+), 84 deletions(-) diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index ea45e8ff1..a99ea952b 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -148,7 +148,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; handle->availableWhenDonor = config_truth_value(params->value); else if(!strcmp(params->name,"disable_master_role_setting")) handle->disableMasterRoleSetting = config_truth_value(params->value); - else if(!strcmp(params->name,"master_down_script")) + else if(!strcmp(params->name,"script")) { if(handle->script) free(handle->script); @@ -157,7 +157,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; params = params->next; } - handle->tid = (THREAD)thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, mon); return handle; } @@ -547,6 +547,26 @@ int log_no_members = 1; log_no_members = 1; } } + + + ptr = mon->databases; + + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + ptr = ptr->next; + } } } diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mm_mon.c index 46adc68b5..71bf0022b 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mm_mon.c @@ -109,33 +109,44 @@ static void * startMonitor(void *arg,void* opt) { MONITOR* mon = (MONITOR*)arg; -MYSQL_MONITOR *handle = mon->handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (handle) - { - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->shutdown = 0; - handle->id = MONITOR_DEFAULT_ID; - handle->replicationHeartbeat = 0; - handle->detectStaleMaster = 0; - handle->master = NULL; - spinlock_init(&handle->lock); - } + MYSQL_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + if (handle) + { + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + handle->replicationHeartbeat = 0; + handle->detectStaleMaster = 0; + handle->master = NULL; + spinlock_init(&handle->lock); + } - while(params) + while(params) + { + if(!strcmp(params->name,"detect_stale_master")) { - if(!strcmp(params->name,"detect_stale_master")) - handle->detectStaleMaster = config_truth_value(params->value); - params = params->next; + handle->detectStaleMaster = config_truth_value(params->value); } + else if(!strcmp(params->name,"script")) + { + if(handle->script) + { + free(handle->script); + } - handle->tid = (THREAD)thread_start(monitorMain, mon); - return handle; + handle->script = strdup(params->value); + } + params = params->next; + } + + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -567,6 +578,25 @@ size_t nrounds = 0; } ptr = ptr->next; } + + ptr = mon->databases; + + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + ptr = ptr->next; + } } } diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c index 2001b2bec..37a8e6de2 100644 --- a/server/modules/monitor/monitor_common.c +++ b/server/modules/monitor/monitor_common.c @@ -40,61 +40,187 @@ void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) ptr->pending_status &= ~bit; } -char* mon_get_event_type(MONITOR_SERVERS* node) + + monitor_event_t mon_get_event_type(MONITOR_SERVERS* node) { unsigned int prev = node->mon_prev_status; if((prev & (SERVER_MASTER|SERVER_RUNNING)) == (SERVER_MASTER|SERVER_RUNNING) && SERVER_IS_DOWN(node->server)) { - return "master_down"; + return MASTER_DOWN_EVENT; } if((prev & (SERVER_RUNNING)) == 0 && SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) { - return "master_up"; + return MASTER_UP_EVENT; } if((prev & (SERVER_SLAVE|SERVER_RUNNING)) == (SERVER_SLAVE|SERVER_RUNNING) && SERVER_IS_DOWN(node->server)) { - return "slave_down"; + return SLAVE_DOWN_EVENT; } if((prev & (SERVER_RUNNING)) == 0 && SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) { - return "slave_up"; + return SLAVE_UP_EVENT; } + + /** Galera specific events */ + if((prev & (SERVER_JOINED|SERVER_RUNNING)) == (SERVER_JOINED|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return SYNCED_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_JOINED(node->server)) + { + return SYNCED_UP_EVENT; + } + + /** NDB events*/ + if((prev & (SERVER_NDB|SERVER_RUNNING)) == (SERVER_NDB|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return NDB_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_NDB(node->server)) + { + return NDB_UP_EVENT; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) { - return "new_master"; + return NEW_MASTER_EVENT; } if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) { - return "new_slave"; + return NEW_SLAVE_EVENT; } + + /** Status loss events */ if((prev & (SERVER_RUNNING|SERVER_MASTER)) == (SERVER_RUNNING|SERVER_MASTER) && SERVER_IS_RUNNING(node->server) && !SERVER_IS_MASTER(node->server)) { - return "lost_master"; + return LOST_MASTER_EVENT; } if((prev & (SERVER_RUNNING|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE) && SERVER_IS_RUNNING(node->server) && !SERVER_IS_SLAVE(node->server)) { - return "lost_slave"; + return LOST_SLAVE_EVENT; } + if((prev & (SERVER_RUNNING|SERVER_JOINED)) == (SERVER_RUNNING|SERVER_JOINED) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_JOINED(node->server)) + { + return LOST_SYNCED_EVENT; + } + if((prev & (SERVER_RUNNING|SERVER_NDB)) == (SERVER_RUNNING|SERVER_NDB) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_NDB(node->server)) + { + return LOST_NDB_EVENT; + } + + + /** Generic server failure */ if((prev & SERVER_RUNNING) == 0 && SERVER_IS_RUNNING(node->server)) { - return "server_up"; + return SERVER_UP_EVENT; } if((prev & SERVER_RUNNING) == SERVER_RUNNING && SERVER_IS_DOWN(node->server)) { - return "server_down"; + return SERVER_DOWN_EVENT; } - return "unknown"; + + /** Something else, most likely a state that does not matter. + * For example SERVER_DOWN -> SERVER_MASTER|SERVER_DOWN still results in a + * server state equal to not running.*/ + return UNDEFINED_MONITOR_EVENT; +} + +char* mon_get_event_name(MONITOR_SERVERS* node) +{ + switch(mon_get_event_type(node)) + { +case UNDEFINED_MONITOR_EVENT: + return "undefined"; + +case MASTER_DOWN_EVENT: + return "master_down"; + +case MASTER_UP_EVENT: + return "master_up"; + +case SLAVE_DOWN_EVENT: + return "slave_down"; + +case SLAVE_UP_EVENT: + return "slave_up"; + +case SERVER_DOWN_EVENT: + return "server_down"; + +case SERVER_UP_EVENT: + return "server_up"; + +case SYNCED_DOWN_EVENT: + return "synced_down"; + +case SYNCED_UP_EVENT: + return "synced_up"; + +case DONOR_DOWN_EVENT: + return "donor_down"; + +case DONOR_UP_EVENT: + return "donor_up"; + +case NDB_DOWN_EVENT: + return "ndb_down"; + +case NDB_UP_EVENT: + return "ndb_up"; + +case LOST_MASTER_EVENT: + return "lost_master"; + +case LOST_SLAVE_EVENT: + return "lost_slave"; + +case LOST_SYNCED_EVENT: + return "lost_synced"; + +case LOST_DONOR_EVENT: + return "lost_donor"; + +case LOST_NDB_EVENT: + return "lost_ndb"; + +case NEW_MASTER_EVENT: + return "new_master"; + +case NEW_SLAVE_EVENT: + return "new_slave"; + +case NEW_SYNCED_EVENT: + return "new_synced"; + +case NEW_DONOR_EVENT: + return "new_donor"; + + case NEW_NDB_EVENT: + return "new_ndb"; + + default: + return "MONITOR_EVENT_FAILURE"; + + } + + } void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) @@ -162,3 +288,27 @@ bool mon_print_fail_status( } return succp; } + +void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script) +{ + char argstr[PATH_MAX + MON_ARG_MAX + 1]; + EXTERNCMD* cmd; + + snprintf(argstr,PATH_MAX + MON_ARG_MAX, + "%s --event=%s --node=%s --nodelist=", + script, + mon_get_event_name(ptr), + ptr->server->unique_name); + + mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1); + cmd = externcmd_allocate(argstr); + + if(externcmd_execute(cmd)) + { + skygw_log_write(LOGFILE_ERROR, + "Error: Failed to execute script " + "'%s' on server state change event %s.", + script,mon_get_event_type(ptr)); + } + externcmd_free(cmd); +} \ No newline at end of file diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index 1c493b10a..c171998b2 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include /** * @file monitor_common.h - The generic monitor structures all monitors use * @@ -31,10 +33,42 @@ * @endverbatim */ +#define MON_ARG_MAX 8192 + +/** Monitor events that are caused by servers moving from + * one state to another.*/ +typedef enum { + UNDEFINED_MONITOR_EVENT, + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + SYNCED_DOWN_EVENT, + SYNCED_UP_EVENT, + DONOR_DOWN_EVENT, + DONOR_UP_EVENT, + NDB_DOWN_EVENT, + NDB_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_SYNCED_EVENT, + LOST_DONOR_EVENT, + LOST_NDB_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_SYNCED_EVENT, + NEW_DONOR_EVENT, + NEW_NDB_EVENT + +}monitor_event_t; void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len); -char* mon_get_event_type(MONITOR_SERVERS* node); +monitor_event_t mon_get_event_type(MONITOR_SERVERS* node); +char* mon_get_event_name(MONITOR_SERVERS* node); void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit); void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit); bool mon_status_changed(MONITOR_SERVERS* mon_srv); bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); +void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script); #endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index a60e10e5e..f935fcd13 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -54,9 +54,6 @@ #include - -#define MON_ARG_MAX 8192 - /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -674,39 +671,31 @@ int log_no_master = 1; } } else { ptr->server->status = ptr->pending_status; - - if(mon_status_changed(ptr)) - { - /** Execute monitor script */ - if(handle->script && strcmp(mon_get_event_type(ptr),"unknown") != 0) - { - char argstr[PATH_MAX + MON_ARG_MAX + 1]; - snprintf(argstr,PATH_MAX + MON_ARG_MAX, - "%s --event=%s --node=%s --nodelist=", - handle->script, - mon_get_event_type(ptr), - ptr->server->unique_name); - mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1); - - EXTERNCMD* cmd = externcmd_allocate(argstr); - if(externcmd_execute(cmd)) - { - skygw_log_write(LOGFILE_ERROR, - "Error: Failed to execute script " - "'%s' on server state change.", - handle->script); - } - externcmd_free(cmd); - skygw_log_write(LOGFILE_TRACE,"monitor_state_change: %s: %s", - ptr->server->unique_name,mon_get_event_type(ptr)); - } - } } } ptr = ptr->next; } - /* log master detection failure od first master becomes available after failure */ + ptr = mon->databases; + + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + ptr = ptr->next; + } + + /* log master detection failure of first master becomes available after failure */ if (root_master && mon_status_changed(root_master) && !(root_master->server->status & SERVER_STALE_STATUS)) diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbcluster_mon.c index 235eb7f00..6917f414d 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbcluster_mon.c @@ -107,23 +107,31 @@ static void * startMonitor(void *arg,void* opt) { MONITOR* mon = (MONITOR*)arg; -MYSQL_MONITOR *handle = mon->handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (handle != NULL) + MYSQL_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + if (handle != NULL) + { + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + spinlock_init(&handle->lock); + } + while(params) + { + if(!strcmp(params->name,"script")) { - handle->shutdown = 0; + if(handle->script) + free(handle->script); + handle->script = strdup(params->value); } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->shutdown = 0; - handle->id = MONITOR_DEFAULT_ID; - spinlock_init(&handle->lock); - } - - handle->tid = (THREAD)thread_start(monitorMain, mon); - return handle; + } + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -372,5 +380,24 @@ size_t nrounds = 0; ptr = ptr->next; } + + ptr = mon->databases; + + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + ptr = ptr->next; + } } } From 49faa88d14320a195dd5f6ef0b5e04543c6e2d5a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 19:03:27 +0300 Subject: [PATCH 14/32] Added MySQL monitor documentation. --- Documentation/monitors/MySQL-Monitor.md | 115 ++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 Documentation/monitors/MySQL-Monitor.md diff --git a/Documentation/monitors/MySQL-Monitor.md b/Documentation/monitors/MySQL-Monitor.md new file mode 100644 index 000000000..ca04d5ae8 --- /dev/null +++ b/Documentation/monitors/MySQL-Monitor.md @@ -0,0 +1,115 @@ +# MySQL Monitor + +## Overview + +The MySQL monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the acutal replication tree in the cluster. + +## Configuration + +A minimal configuration of the MySQL monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters + +Here are optional parameters for the MySQL monitor that change the way it behaves. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +### `detect_replication_lag` + +Detect replication lag between the master and the slaves. This allows the routers to route read queries to only slaves that are up to date. + +``` +detect_replication_lag=true +``` + +### `detect_stale_master` + +Allow previous master to be available even in case of stopped or misconfigured +replication. This allows services that depend on master and slave roles to continue functioning as long as the master server is available. + +This is a situation which can happen if all slave servers are unreachable or the replication breaks for some reason. + +``` +detect_stale_master=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --node= --nodelist= +``` +Here is a table of all possible event names and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has done down +server_up|A server with no assigned role has come up +synced_down|A synced Galera node has come up +synced_up|A synced Galera node has gone down +donor_down|A donor Galera node has come up +donor_up|A donor Galera node has gone down +ndb_down|A MySQL Cluster node has gone down +ndb_up|A MySQL Cluster node has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +lost_synced|A Galera node lost synced status +lost_donor|A Galera node lost donor status +lost_ndb|A MySQL Cluster node lost node membership +new_master|A new Master was detected +new_slave|A new Slave was detected +new_synced|A new synced Galera node was detected +new_donor|A new donor Galera node was detected +new_ndb|A new MySQL Cluster node was found + From 1d0740c1c17a3b7129bcc18e7b0618962fb7ea31 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 9 May 2015 21:16:35 +0300 Subject: [PATCH 15/32] Renamed node to initiator in the arguments a monitor calls the script. --- Documentation/monitors/MySQL-Monitor.md | 2 +- server/modules/monitor/monitor_common.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/monitors/MySQL-Monitor.md b/Documentation/monitors/MySQL-Monitor.md index ca04d5ae8..be8a7b20c 100644 --- a/Documentation/monitors/MySQL-Monitor.md +++ b/Documentation/monitors/MySQL-Monitor.md @@ -84,7 +84,7 @@ script=/home/user/script.sh This script will be called with the following command line arguments. ``` - --event= --node= --nodelist= + --event= --initiator= --nodelist= ``` Here is a table of all possible event names and their descriptions. diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c index 37a8e6de2..352626984 100644 --- a/server/modules/monitor/monitor_common.c +++ b/server/modules/monitor/monitor_common.c @@ -295,7 +295,7 @@ void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script) EXTERNCMD* cmd; snprintf(argstr,PATH_MAX + MON_ARG_MAX, - "%s --event=%s --node=%s --nodelist=", + "%s --event=%s --initiator=%s --nodelist=", script, mon_get_event_name(ptr), ptr->server->unique_name); From 8f019b451420b4136511081f947822afb46b288c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 11 May 2015 06:45:37 +0300 Subject: [PATCH 16/32] Added Galera monitor documentation. --- Documentation/monitors/Galera-Monitor.md | 124 +++++++++++++++++++++++ Documentation/monitors/MySQL-Monitor.md | 14 ++- 2 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 Documentation/monitors/Galera-Monitor.md diff --git a/Documentation/monitors/Galera-Monitor.md b/Documentation/monitors/Galera-Monitor.md new file mode 100644 index 000000000..3872c30f7 --- /dev/null +++ b/Documentation/monitors/Galera-Monitor.md @@ -0,0 +1,124 @@ +# Galera Monitor + +## Overview + +The Galera Monitor is a monitoring module for MaxScale that monitors a Galera cluster. It detects whether nodes are a part of the cluster and if they are in sync with the rest of the cluster. It can also assign master and slave roles inside MaxScale, allowing Galera clusters to be used with modules designed for traditional master-slave clusters. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[MySQL Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## Galera Monitor optional parameters + +These are optional parameters specific to the Galera Monitor. + +### `disable_master_failback` + +If a node marked as master inside MaxScale happens to fail and the master status is assigned to another node MaxScale will normally return the master status to the original node after it comes back up. With this option enabled, if the master status is assigned to a new node it will not be reassigned to the original node for as long as the new master node is running. + +``` +disable_master_failback=true +``` + +### `available_when_donor` + +This option only has an effect if there is a single Galera node being backed up an XtraBackup instance. This causes the initial node to go into Donor state which would normally prevent if from being marked as a valid server inside MaxScale. If this option is enabled, a single node in Donor state where the method is XtraBackup will be kept in Synced state. + +``` +available_when_donor=true +``` + +### `disable_master_role_setting` + +This disables the assingment of master and slave roles to the Galera cluster nodes. If this option is enabled, Synced is the only status assigned by this monitor. + +``` +disable_master_role_setting=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +Here is a table of all possible event types and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has done down +server_up|A server with no assigned role has come up +synced_down|A synced Galera node has come up +synced_up|A synced Galera node has gone down +donor_down|A donor Galera node has come up +donor_up|A donor Galera node has gone down +ndb_down|A MySQL Cluster node has gone down +ndb_up|A MySQL Cluster node has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +lost_synced|A Galera node lost synced status +lost_donor|A Galera node lost donor status +lost_ndb|A MySQL Cluster node lost node membership +new_master|A new Master was detected +new_slave|A new Slave was detected +new_synced|A new synced Galera node was detected +new_donor|A new donor Galera node was detected +new_ndb|A new MySQL Cluster node was found + diff --git a/Documentation/monitors/MySQL-Monitor.md b/Documentation/monitors/MySQL-Monitor.md index be8a7b20c..4e6e949e0 100644 --- a/Documentation/monitors/MySQL-Monitor.md +++ b/Documentation/monitors/MySQL-Monitor.md @@ -2,11 +2,11 @@ ## Overview -The MySQL monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the acutal replication tree in the cluster. +The MySQL Monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the acutal replication tree in the cluster. ## Configuration -A minimal configuration of the MySQL monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. ``` [MySQL Monitor] @@ -18,9 +18,9 @@ passwd=mypwd ``` -## Optional parameters +## Optional parameters for all monitors -Here are optional parameters for the MySQL monitor that change the way it behaves. +Here are optional parameters that are common for all the monitors. ### `monitor_interval` @@ -54,6 +54,10 @@ This parameter controls the timeout for reading from a monitored server. It is i backend_read_timeout=2 ``` +## MySQL Monitor optional parameters + +These are optional parameters specific to the MySQL Monitor. + ### `detect_replication_lag` Detect replication lag between the master and the slaves. This allows the routers to route read queries to only slaves that are up to date. @@ -86,7 +90,7 @@ This script will be called with the following command line arguments. ``` --event= --initiator= --nodelist= ``` -Here is a table of all possible event names and their descriptions. +Here is a table of all possible event types and their descriptions. Event Name|Description ----------|---------- From 1aba13177be93f2b8fb05219692ff1d1f1f3cd91 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 11 May 2015 19:12:45 +0300 Subject: [PATCH 17/32] Added ndbclustermon and mmmon headers. --- server/core/mon_exec.c | 1 + server/include/mon_exec.h | 1 + server/modules/monitor/CMakeLists.txt | 4 +- server/modules/monitor/galeramon.c | 1 + server/modules/monitor/galeramon.h | 1 - server/modules/monitor/{mm_mon.c => mmmon.c} | 89 +++---------------- server/modules/monitor/mmmon.h | 51 +++++++++++ server/modules/monitor/mysqlmon.h | 2 +- .../{ndbcluster_mon.c => ndbclustermon.c} | 1 + server/modules/monitor/ndbclustermon.h | 55 ++++++++++++ 10 files changed, 127 insertions(+), 79 deletions(-) rename server/modules/monitor/{mm_mon.c => mmmon.c} (90%) create mode 100644 server/modules/monitor/mmmon.h rename server/modules/monitor/{ndbcluster_mon.c => ndbclustermon.c} (99%) create mode 100644 server/modules/monitor/ndbclustermon.h diff --git a/server/core/mon_exec.c b/server/core/mon_exec.c index bae0d31fc..01640bcd6 100644 --- a/server/core/mon_exec.c +++ b/server/core/mon_exec.c @@ -141,6 +141,7 @@ int externcmd_execute(EXTERNCMD* cmd) } else { + cmd->child = pid; cmd->n_exec++; LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd)); } diff --git a/server/include/mon_exec.h b/server/include/mon_exec.h index e1ef7ad37..25390bb82 100644 --- a/server/include/mon_exec.h +++ b/server/include/mon_exec.h @@ -11,6 +11,7 @@ typedef struct extern_cmd_t{ char* parameters[MAXSCALE_EXTCMD_ARG_MAX]; /*< Command arguments */ int n_exec; /*< Number of times executed */ + pid_t child; /*< PID of the child process */ }EXTERNCMD; EXTERNCMD* externcmd_allocate(char* argstr); diff --git a/server/modules/monitor/CMakeLists.txt b/server/modules/monitor/CMakeLists.txt index 8f364fe5b..9f08a5b32 100644 --- a/server/modules/monitor/CMakeLists.txt +++ b/server/modules/monitor/CMakeLists.txt @@ -6,11 +6,11 @@ add_library(galeramon SHARED galeramon.c monitor_common.c) target_link_libraries(galeramon log_manager utils) install(TARGETS galeramon DESTINATION modules) -add_library(ndbclustermon SHARED ndbcluster_mon.c monitor_common.c) +add_library(ndbclustermon SHARED ndbclustermon.c monitor_common.c) target_link_libraries(ndbclustermon log_manager utils) install(TARGETS ndbclustermon DESTINATION modules) if(BUILD_MMMON) - add_library(mmmon SHARED mm_mon.c monitor_common.c) + add_library(mmmon SHARED mmmon.c monitor_common.c) target_link_libraries(mmmon log_manager utils) install(TARGETS mmmon DESTINATION modules) endif() diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index a99ea952b..66ccc28ce 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -34,6 +34,7 @@ * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write * 20/04/15 Guillaume Lefranc Added availableWhenDonor feature * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 3a9c41e7f..30b642921 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -56,7 +56,6 @@ typedef struct { int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ unsigned long id; /**< Monitor ID */ - int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */ int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */ int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mmmon.c similarity index 90% rename from server/modules/monitor/mm_mon.c rename to server/modules/monitor/mmmon.c index 71bf0022b..d4e04d288 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mmmon.c @@ -17,19 +17,20 @@ */ /** - * @file mysql_mon.c - A MySQL Multi Muster cluster monitor + * @file mm_mon.c - A Multi-Master Multi Muster cluster monitor * * @verbatim * Revision History * * Date Who Description * 08/09/14 Massimiliano Pinto Initial implementation + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ -#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -44,7 +45,7 @@ MODULE_INFO info = { MODULE_API_MONITOR, MODULE_BETA_RELEASE, MONITOR_VERSION, - "A MySQL Multi Master monitor" + "A Multi-Master Multi Master monitor" }; static void *startMonitor(void *,void*); @@ -79,7 +80,7 @@ ModuleInit() { LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, - "Initialise the MySQL Monitor module %s.", + "Initialise the Multi-Master Monitor module %s.", version_str))); } @@ -109,7 +110,7 @@ static void * startMonitor(void *arg,void* opt) { MONITOR* mon = (MONITOR*)arg; - MYSQL_MONITOR *handle = mon->handle; + MM_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; if (handle) { @@ -117,12 +118,10 @@ startMonitor(void *arg,void* opt) } else { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + if ((handle = (MM_MONITOR *)malloc(sizeof(MM_MONITOR))) == NULL) return NULL; handle->shutdown = 0; handle->id = MONITOR_DEFAULT_ID; - handle->replicationHeartbeat = 0; - handle->detectStaleMaster = 0; handle->master = NULL; spinlock_init(&handle->lock); } @@ -157,7 +156,7 @@ startMonitor(void *arg,void* opt) static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MM_MONITOR *handle = (MM_MONITOR *)arg; handle->shutdown = 1; thread_wait((void *)handle->tid); @@ -172,7 +171,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void diagnostics(DCB *dcb, void *arg) { MONITOR* mon = (MONITOR*)arg; -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; +MM_MONITOR *handle = (MM_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -217,7 +216,7 @@ char *sep; static void monitorDatabase(MONITOR* mon, MONITOR_SERVERS *database) { - MYSQL_MONITOR *handle = mon->handle; + MM_MONITOR *handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; int isslave = 0; @@ -240,7 +239,7 @@ char *server_string; if (SERVER_IN_MAINT(database->server)) return; - /** Store prevous status */ + /** Store previous status */ database->mon_prev_status = database->server->status; if (database->con == NULL || mysql_ping(database->con) != 0) @@ -473,7 +472,7 @@ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*)arg; -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; +MM_MONITOR *handle = (MM_MONITOR *)mon->handle; MONITOR_SERVERS *ptr; int detect_stale_master = handle->detectStaleMaster; MONITOR_SERVERS *root_master; @@ -612,70 +611,10 @@ static void detectStaleMaster(void *arg, int enable) { MONITOR* mon = (MONITOR*)arg; -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; +MM_MONITOR *handle = (MM_MONITOR *)mon->handle; memcpy(&handle->detectStaleMaster, &enable, sizeof(int)); } -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); - - if (SERVER_IS_DOWN(mon_srv->server) && errcount%modval == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Set a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status |= bit; -} - -/** - * Clear a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status &= ~bit; -} - /******* * This function returns the master server * from a set of MySQL Multi Master monitored servers @@ -687,7 +626,7 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) */ static MONITOR_SERVERS *get_current_master(MONITOR *mon) { - MYSQL_MONITOR* handle = mon->handle; + MM_MONITOR* handle = mon->handle; MONITOR_SERVERS *ptr; ptr = mon->databases; diff --git a/server/modules/monitor/mmmon.h b/server/modules/monitor/mmmon.h new file mode 100644 index 000000000..fb035a594 --- /dev/null +++ b/server/modules/monitor/mmmon.h @@ -0,0 +1,51 @@ +#ifndef _MMMON_H +#define _MMMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2015 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * The handle for an instance of a Multi-Master Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + unsigned long id; /**< Monitor ID */ + int detectStaleMaster; /**< Monitor flag for Stale Master detection */ + MONITOR_SERVERS *master; /**< Master server for Master/Slave replication */ + char* script; /*< Script to call when state changes occur on servers */ +} MM_MONITOR; + +#endif diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index f5d0e2a37..9bd6a2da9 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -49,7 +49,7 @@ * 07/11/14 Massimiliano Pinto Addition of NetworkTimeout: connect, read, write * 20/04/15 Guillaume Lefranc Addition of availableWhenDonor * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting - * 07/05/15 Markus Makela Addition of command execution on Master server failure + * 07/05/15 Markus Makela Addition of command execution on Master server failure * @endverbatim */ diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbclustermon.c similarity index 99% rename from server/modules/monitor/ndbcluster_mon.c rename to server/modules/monitor/ndbclustermon.c index 6917f414d..8385131e3 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -25,6 +25,7 @@ * Date Who Description * 25/07/14 Massimiliano Pinto Initial implementation * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ diff --git a/server/modules/monitor/ndbclustermon.h b/server/modules/monitor/ndbclustermon.h new file mode 100644 index 000000000..7066a5a4e --- /dev/null +++ b/server/modules/monitor/ndbclustermon.h @@ -0,0 +1,55 @@ +#ifndef _MYSQLMON_H +#define _MYSQLMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file ndbclustermon.h - The NDB Cluster monitor + * + */ + +/** + * The handle for an instance of a NDB Cluster Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + unsigned long id; /**< Monitor ID */ + MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ + char* script; /*< Script to call when state changes occur on servers */ +} MYSQL_MONITOR; + +#endif From b94f494d8052ca52414448f68cb445444c395be6 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 14 May 2015 12:05:58 +0300 Subject: [PATCH 18/32] Added filtering of monitor events. --- server/core/monitor.c | 6 +- server/modules/monitor/galeramon.c | 89 +++++++++++++++---------- server/modules/monitor/galeramon.h | 1 + server/modules/monitor/mmmon.c | 20 ++++-- server/modules/monitor/mmmon.h | 1 + server/modules/monitor/monitor_common.c | 86 +++++++++++++++++++++++- server/modules/monitor/monitor_common.h | 5 +- server/modules/monitor/mysql_mon.c | 20 ++++-- server/modules/monitor/mysqlmon.h | 1 + server/modules/monitor/ndbclustermon.c | 19 +++++- server/modules/monitor/ndbclustermon.h | 1 + 11 files changed, 195 insertions(+), 54 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index b2ed8d1a2..9d945572a 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -66,8 +66,6 @@ MONITOR *mon; { return NULL; } - mon->state = MONITOR_STATE_ALLOC; - mon->name = strdup(name); if ((mon->module = load_module(module, MODULE_MONITOR)) == NULL) { @@ -79,10 +77,10 @@ MONITOR *mon; free(mon); return NULL; } - + mon->state = MONITOR_STATE_ALLOC; + mon->name = strdup(name); mon->handle = NULL; mon->databases = NULL; - mon->name = NULL; mon->password = NULL; mon->read_timeout = DEFAULT_READ_TIMEOUT; mon->write_timeout = DEFAULT_WRITE_TIMEOUT; diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index 66ccc28ce..eb75c2160 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -120,46 +120,59 @@ static void * startMonitor(void *arg,void* opt) { MONITOR* mon = arg; -GALERA_MONITOR *handle = mon->handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (handle != NULL) - { - handle->shutdown = 0; - } - else - { - if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL) - return NULL; - handle->shutdown = 0; - handle->id = MONITOR_DEFAULT_ID; - handle->disableMasterFailback = 0; - handle->availableWhenDonor = 0; - handle->disableMasterRoleSetting = 0; - handle->master = NULL; - handle->script = NULL; - spinlock_init(&handle->lock); - } + GALERA_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false; + if (handle != NULL) + { + handle->shutdown = 0; + } + else + { + if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + handle->disableMasterFailback = 0; + handle->availableWhenDonor = 0; + handle->disableMasterRoleSetting = 0; + handle->master = NULL; + handle->script = NULL; + memset(handle->events,false,sizeof(handle->events)); + spinlock_init(&handle->lock); + } - while(params) + while(params) + { + if(!strcmp(params->name,"disable_master_failback")) + handle->disableMasterFailback = config_truth_value(params->value); + else if(!strcmp(params->name,"available_when_donor")) + handle->availableWhenDonor = config_truth_value(params->value); + else if(!strcmp(params->name,"disable_master_role_setting")) + handle->disableMasterRoleSetting = config_truth_value(params->value); + else if(!strcmp(params->name,"script")) { - if(!strcmp(params->name,"disable_master_failback")) - handle->disableMasterFailback = config_truth_value(params->value); - else if(!strcmp(params->name,"available_when_donor")) - handle->availableWhenDonor = config_truth_value(params->value); - else if(!strcmp(params->name,"disable_master_role_setting")) - handle->disableMasterRoleSetting = config_truth_value(params->value); - else if(!strcmp(params->name,"script")) - { - if(handle->script) - free(handle->script); - handle->script = strdup(params->value); - } - params = params->next; + if(handle->script) + free(handle->script); + handle->script = strdup(params->value); } + else if(!strcmp(params->name,"events")) + { + mon_parse_event_string(&handle->events,sizeof(handle->events),params->value); + have_events = true; + } + params = params->next; + } - handle->tid = (THREAD)thread_start(monitorMain, mon); - return handle; + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } + + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -388,6 +401,7 @@ MONITOR_SERVERS *candidate_master = NULL; int master_stickiness = handle->disableMasterFailback; int is_cluster=0; int log_no_members = 1; +monitor_event_t evtype; if (mysql_thread_init()) { @@ -554,14 +568,15 @@ int log_no_members = 1; while(ptr) { + /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); - if(handle->script) + if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 30b642921..02d8f08c7 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -61,6 +61,7 @@ typedef struct { int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ char* script; + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ } GALERA_MONITOR; #endif diff --git a/server/modules/monitor/mmmon.c b/server/modules/monitor/mmmon.c index d4e04d288..8188c40c9 100644 --- a/server/modules/monitor/mmmon.c +++ b/server/modules/monitor/mmmon.c @@ -112,6 +112,8 @@ startMonitor(void *arg,void* opt) MONITOR* mon = (MONITOR*)arg; MM_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false; + if (handle) { handle->shutdown = 0; @@ -123,6 +125,7 @@ startMonitor(void *arg,void* opt) handle->shutdown = 0; handle->id = MONITOR_DEFAULT_ID; handle->master = NULL; + memset(handle->events,false,sizeof(handle->events)); spinlock_init(&handle->lock); } @@ -141,9 +144,18 @@ startMonitor(void *arg,void* opt) handle->script = strdup(params->value); } + else if(!strcmp(params->name,"events")) + { + mon_parse_event_string(&handle->events,sizeof(handle->events),params->value); + have_events = true; + } params = params->next; } - + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } handle->tid = (THREAD)thread_start(monitorMain, mon); return handle; } @@ -579,17 +591,17 @@ size_t nrounds = 0; } ptr = mon->databases; - + monitor_event_t evtype; while(ptr) { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); - if(handle->script) + if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } diff --git a/server/modules/monitor/mmmon.h b/server/modules/monitor/mmmon.h index fb035a594..e2532d407 100644 --- a/server/modules/monitor/mmmon.h +++ b/server/modules/monitor/mmmon.h @@ -46,6 +46,7 @@ typedef struct { int detectStaleMaster; /**< Monitor flag for Stale Master detection */ MONITOR_SERVERS *master; /**< Master server for Master/Slave replication */ char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ } MM_MONITOR; #endif diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c index 352626984..6f9be583b 100644 --- a/server/modules/monitor/monitor_common.c +++ b/server/modules/monitor/monitor_common.c @@ -18,6 +18,8 @@ #include +monitor_event_t mon_name_to_event(char* tok); + /** * Set a pending status bit in the monitor server * @@ -311,4 +313,86 @@ void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script) script,mon_get_event_type(ptr)); } externcmd_free(cmd); -} \ No newline at end of file +} + +/** + * Parse a string of event names to an array with enabled events. + * @param events Pointer to an array of boolean values + * @param count Size of the array + * @param string String to parse + * @return 0 on success. 1 when an error has occurred or an unexpected event was + * found. + */ +int mon_parse_event_string(bool* events, size_t count,char* string) +{ + char *tok,*saved; + monitor_event_t event; + + tok = strtok_r(string,",| ",&saved); + + if(tok == NULL) + return -1; + + while(tok) + { + event = mon_name_to_event(tok); + if(event == UNDEFINED_MONITOR_EVENT) + return -1; + events[event] = true; + tok = strtok_r(NULL,",| ",&saved); + } + + return 0; +} + +monitor_event_t mon_name_to_event(char* tok) +{ + if(!strcasecmp("master_down",tok)) + return MASTER_DOWN_EVENT; + else if(!strcasecmp("master_up",tok)) + return MASTER_UP_EVENT; + else if(!strcasecmp("slave_down",tok)) + return SLAVE_DOWN_EVENT; + else if(!strcasecmp("slave_up",tok)) + return SLAVE_UP_EVENT; + else if(!strcasecmp("server_down",tok)) + return SERVER_DOWN_EVENT; + else if(!strcasecmp("server_up",tok)) + return SERVER_UP_EVENT; + else if(!strcasecmp("synced_down",tok)) + return SYNCED_DOWN_EVENT; + else if(!strcasecmp("synced_up",tok)) + return SYNCED_UP_EVENT; + else if(!strcasecmp("donor_down",tok)) + return DONOR_DOWN_EVENT; + else if(!strcasecmp("donor_up",tok)) + return DONOR_UP_EVENT; + else if(!strcasecmp("ndb_down",tok)) + return NDB_DOWN_EVENT; + else if(!strcasecmp("ndb_up",tok)) + return NDB_UP_EVENT; + else if(!strcasecmp("lost_master",tok)) + return LOST_MASTER_EVENT; + else if(!strcasecmp("lost_slave",tok)) + return LOST_SLAVE_EVENT; + else if(!strcasecmp("lost_synced",tok)) + return LOST_SYNCED_EVENT; + else if(!strcasecmp("lost_donor",tok)) + return LOST_DONOR_EVENT; + else if(!strcasecmp("lost_ndb",tok)) + return LOST_NDB_EVENT; + else if(!strcasecmp("new_master",tok)) + return NEW_MASTER_EVENT; + else if(!strcasecmp("new_slave",tok)) + return NEW_SLAVE_EVENT; + else if(!strcasecmp("new_synced",tok)) + return NEW_SYNCED_EVENT; + else if(!strcasecmp("new_donor",tok)) + return NEW_DONOR_EVENT; + else if(!strcasecmp("new_ndb",tok)) + return NEW_NDB_EVENT; + else + return UNDEFINED_MONITOR_EVENT; + + } + diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index c171998b2..e4cd864d1 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -60,8 +60,8 @@ typedef enum { NEW_SLAVE_EVENT, NEW_SYNCED_EVENT, NEW_DONOR_EVENT, - NEW_NDB_EVENT - + NEW_NDB_EVENT, + MAX_MONITOR_EVENT }monitor_event_t; void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len); monitor_event_t mon_get_event_type(MONITOR_SERVERS* node); @@ -71,4 +71,5 @@ void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit); bool mon_status_changed(MONITOR_SERVERS* mon_srv); bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script); +int mon_parse_event_string(bool* events, size_t count,char* string); #endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index f935fcd13..3a6d22391 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -140,6 +140,8 @@ startMonitor(void *arg, void* opt) MONITOR* monitor = (MONITOR*)arg; MYSQL_MONITOR *handle = (MYSQL_MONITOR*)monitor->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false; + if (handle) { handle->shutdown = 0; @@ -154,6 +156,7 @@ startMonitor(void *arg, void* opt) handle->detectStaleMaster = 0; handle->master = NULL; handle->script = NULL; + memset(handle->events,false,sizeof(handle->events)); spinlock_init(&handle->lock); } @@ -169,9 +172,18 @@ startMonitor(void *arg, void* opt) free(handle->script); handle->script = strdup(params->value); } + else if(!strcmp(params->name,"events")) + { + mon_parse_event_string(handle->events,sizeof(handle->events),params->value); + have_events = true; + } params = params->next; } - + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } handle->tid = (THREAD)thread_start(monitorMain, monitor); return handle; } @@ -677,17 +689,17 @@ int log_no_master = 1; } ptr = mon->databases; - + monitor_event_t evtype; while(ptr) { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); - if(handle->script) + if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 9bd6a2da9..574ae7c88 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -69,6 +69,7 @@ typedef struct { int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ } MYSQL_MONITOR; #endif diff --git a/server/modules/monitor/ndbclustermon.c b/server/modules/monitor/ndbclustermon.c index 8385131e3..ad41ab1e8 100644 --- a/server/modules/monitor/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -110,6 +110,8 @@ startMonitor(void *arg,void* opt) MONITOR* mon = (MONITOR*)arg; MYSQL_MONITOR *handle = mon->handle; CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false; + if (handle != NULL) { handle->shutdown = 0; @@ -120,6 +122,7 @@ startMonitor(void *arg,void* opt) return NULL; handle->shutdown = 0; handle->id = MONITOR_DEFAULT_ID; + memset(handle->events,false,sizeof(handle->events)); spinlock_init(&handle->lock); } while(params) @@ -130,6 +133,17 @@ startMonitor(void *arg,void* opt) free(handle->script); handle->script = strdup(params->value); } + else if(!strcmp(params->name,"events")) + { + mon_parse_event_string(&handle->events,sizeof(handle->events),params->value); + have_events = true; + } + params = params->next; + } + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); } handle->tid = (THREAD)thread_start(monitorMain, mon); return handle; @@ -383,17 +397,18 @@ size_t nrounds = 0; } ptr = mon->databases; + monitor_event_t evtype; while(ptr) { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && mon_get_event_type(ptr) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); - if(handle->script) + if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } diff --git a/server/modules/monitor/ndbclustermon.h b/server/modules/monitor/ndbclustermon.h index 7066a5a4e..081f5e3da 100644 --- a/server/modules/monitor/ndbclustermon.h +++ b/server/modules/monitor/ndbclustermon.h @@ -50,6 +50,7 @@ typedef struct { unsigned long id; /**< Monitor ID */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ } MYSQL_MONITOR; #endif From 055950acedec78e2eb1191afef424cac3268b2b8 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 14 May 2015 15:19:11 +0300 Subject: [PATCH 19/32] Updated monitor documentation. --- Documentation/monitors/Galera-Monitor.md | 20 ++-- Documentation/monitors/MM-Monitor.md | 107 ++++++++++++++++++ Documentation/monitors/MySQL-Monitor.md | 22 ++-- Documentation/monitors/NDB-Cluster-Monitor.md | 102 +++++++++++++++++ 4 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 Documentation/monitors/MM-Monitor.md create mode 100644 Documentation/monitors/NDB-Cluster-Monitor.md diff --git a/Documentation/monitors/Galera-Monitor.md b/Documentation/monitors/Galera-Monitor.md index 3872c30f7..8e1a403c9 100644 --- a/Documentation/monitors/Galera-Monitor.md +++ b/Documentation/monitors/Galera-Monitor.md @@ -9,7 +9,7 @@ The Galera Monitor is a monitoring module for MaxScale that monitors a Galera cl A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. ``` -[MySQL Monitor] +[Galera Monitor] type=monitor module=galeramon servers=server1,server2,server3 @@ -90,11 +90,16 @@ This script will be executed when a server changes its state. The parameter shou script=/home/user/script.sh ``` -This script will be called with the following command line arguments. +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. ``` - --event= --initiator= --nodelist= +events=master_down,slave_down ``` + +## Script events + Here is a table of all possible event types and their descriptions. Event Name|Description @@ -107,18 +112,9 @@ server_down|A server with no assigned role has done down server_up|A server with no assigned role has come up synced_down|A synced Galera node has come up synced_up|A synced Galera node has gone down -donor_down|A donor Galera node has come up -donor_up|A donor Galera node has gone down -ndb_down|A MySQL Cluster node has gone down -ndb_up|A MySQL Cluster node has come up lost_master|A server lost Master status lost_slave|A server lost Slave status lost_synced|A Galera node lost synced status -lost_donor|A Galera node lost donor status -lost_ndb|A MySQL Cluster node lost node membership new_master|A new Master was detected new_slave|A new Slave was detected new_synced|A new synced Galera node was detected -new_donor|A new donor Galera node was detected -new_ndb|A new MySQL Cluster node was found - diff --git a/Documentation/monitors/MM-Monitor.md b/Documentation/monitors/MM-Monitor.md new file mode 100644 index 000000000..c9b4a852d --- /dev/null +++ b/Documentation/monitors/MM-Monitor.md @@ -0,0 +1,107 @@ +# Multi-Master Monitor + +## Overview + +The Multi-Master Monitor is a monitoring module for MaxScale that monitors Master-Master replicatio. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[Multi-Master Monitor] +type=monitor +module=mmmon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## Multi-Master Monitor optional parameters + +These are optional parameters specific to the Multi-Master Monitor. + +### `detect_stale_master` + +Allow previous master to be available even in case of stopped or misconfigured replication. This allows services that depend on master and slave roles to continue functioning as long as the master server is available. + +This is a situation which can happen if all slave servers are unreachable or the replication breaks for some reason. + +``` +detect_stale_master=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +## Script events + +Here is a table of all possible event types and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has done down +server_up|A server with no assigned role has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +new_master|A new Master was detected +new_slave|A new Slave was detected diff --git a/Documentation/monitors/MySQL-Monitor.md b/Documentation/monitors/MySQL-Monitor.md index 4e6e949e0..a3560c548 100644 --- a/Documentation/monitors/MySQL-Monitor.md +++ b/Documentation/monitors/MySQL-Monitor.md @@ -90,6 +90,16 @@ This script will be called with the following command line arguments. ``` --event= --initiator= --nodelist= ``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +## Script events + Here is a table of all possible event types and their descriptions. Event Name|Description @@ -100,20 +110,8 @@ slave_down|A Slave server has gone down slave_up|A Slave server has come up server_down|A server with no assigned role has done down server_up|A server with no assigned role has come up -synced_down|A synced Galera node has come up -synced_up|A synced Galera node has gone down -donor_down|A donor Galera node has come up -donor_up|A donor Galera node has gone down -ndb_down|A MySQL Cluster node has gone down -ndb_up|A MySQL Cluster node has come up lost_master|A server lost Master status lost_slave|A server lost Slave status -lost_synced|A Galera node lost synced status -lost_donor|A Galera node lost donor status -lost_ndb|A MySQL Cluster node lost node membership new_master|A new Master was detected new_slave|A new Slave was detected -new_synced|A new synced Galera node was detected -new_donor|A new donor Galera node was detected -new_ndb|A new MySQL Cluster node was found diff --git a/Documentation/monitors/NDB-Cluster-Monitor.md b/Documentation/monitors/NDB-Cluster-Monitor.md new file mode 100644 index 000000000..a2e608b7e --- /dev/null +++ b/Documentation/monitors/NDB-Cluster-Monitor.md @@ -0,0 +1,102 @@ +# NDB Cluster Monitor + +## Overview + +The MySQL Cluster Monitor is a monitoring module for MaxScale that monitors Master-Master replicatio. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[MySQL Cluster Monitor] +type=monitor +module=ndbclustermon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## MySQL Cluster Monitor optional parameters + +These are optional parameters specific to the MySQL Cluster Monitor. + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +## Script events + +Here is a table of all possible event types and their descriptions that the MySQL Cluster monitor can be called with. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has done down +server_up|A server with no assigned role has come up +ndb_down|A MySQL Cluster node has gone down +ndb_up|A MySQL Cluster node has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +lost_ndb|A MySQL Cluster node lost node membership +new_master|A new Master was detected +new_slave|A new Slave was detected +new_ndb|A new MySQL Cluster node was found + From c3a243cd30f50e1ff9a23144c9c03205e1ec4e8a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 14 May 2015 15:32:57 +0300 Subject: [PATCH 20/32] Fixed typos. --- Documentation/Documentation-Contents.md | 6 ++++++ Documentation/monitors/Galera-Monitor.md | 4 ++-- Documentation/monitors/MM-Monitor.md | 4 ++-- Documentation/monitors/MySQL-Monitor.md | 4 ++-- Documentation/monitors/NDB-Cluster-Monitor.md | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index 74969c7d1..47e2c77b2 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -53,6 +53,12 @@ Here are detailed documents about the filters MaxScale offers. They contain conf - [Database Firewall Filter](filters/Database-Firewall-Filter.md) - [RabbitMQ Filter](filters/RabbitMQ-Filter.md) +## Monitors + - [MySQL Monitor](monitors/MySQL-Monitor.md) + - [Galera Monitor](monitors/Galera-Monitor.md) + - [Multi-Master Monitor](monitors/MM-Monitor.md) + - [MySQL Cluster Monitor](monitors/NDB-Cluster-Monitor.md) + ## Utilities - [RabbitMQ Consumer Client](filters/RabbitMQ-Consumer-Client.md) diff --git a/Documentation/monitors/Galera-Monitor.md b/Documentation/monitors/Galera-Monitor.md index 8e1a403c9..ffe54fb83 100644 --- a/Documentation/monitors/Galera-Monitor.md +++ b/Documentation/monitors/Galera-Monitor.md @@ -24,7 +24,7 @@ Here are optional parameters that are common for all the monitors. ### `monitor_interval` -This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. ``` monitor_interval=2500 @@ -76,7 +76,7 @@ available_when_donor=true ### `disable_master_role_setting` -This disables the assingment of master and slave roles to the Galera cluster nodes. If this option is enabled, Synced is the only status assigned by this monitor. +This disables the assignment of master and slave roles to the Galera cluster nodes. If this option is enabled, Synced is the only status assigned by this monitor. ``` disable_master_role_setting=true diff --git a/Documentation/monitors/MM-Monitor.md b/Documentation/monitors/MM-Monitor.md index c9b4a852d..00375d4bf 100644 --- a/Documentation/monitors/MM-Monitor.md +++ b/Documentation/monitors/MM-Monitor.md @@ -2,7 +2,7 @@ ## Overview -The Multi-Master Monitor is a monitoring module for MaxScale that monitors Master-Master replicatio. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. +The Multi-Master Monitor is a monitoring module for MaxScale that monitors Master-Master replication. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. ## Configuration @@ -24,7 +24,7 @@ Here are optional parameters that are common for all the monitors. ### `monitor_interval` -This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. ``` monitor_interval=2500 diff --git a/Documentation/monitors/MySQL-Monitor.md b/Documentation/monitors/MySQL-Monitor.md index a3560c548..ab643109e 100644 --- a/Documentation/monitors/MySQL-Monitor.md +++ b/Documentation/monitors/MySQL-Monitor.md @@ -2,7 +2,7 @@ ## Overview -The MySQL Monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the acutal replication tree in the cluster. +The MySQL Monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the actual replication tree in the cluster. ## Configuration @@ -24,7 +24,7 @@ Here are optional parameters that are common for all the monitors. ### `monitor_interval` -This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. ``` monitor_interval=2500 diff --git a/Documentation/monitors/NDB-Cluster-Monitor.md b/Documentation/monitors/NDB-Cluster-Monitor.md index a2e608b7e..a9f453678 100644 --- a/Documentation/monitors/NDB-Cluster-Monitor.md +++ b/Documentation/monitors/NDB-Cluster-Monitor.md @@ -2,7 +2,7 @@ ## Overview -The MySQL Cluster Monitor is a monitoring module for MaxScale that monitors Master-Master replicatio. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. +The MySQL Cluster Monitor is a monitoring module for MaxScale that monitors a MySQL Cluster. It assigns a NDB status for the server if it is a part of a MySQL Cluster. ## Configuration @@ -24,7 +24,7 @@ Here are optional parameters that are common for all the monitors. ### `monitor_interval` -This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 illiseconds. +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. ``` monitor_interval=2500 From 68c5dedeec78b04075808e26e99844645b6ef7ee Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 18 May 2015 16:25:50 +0300 Subject: [PATCH 21/32] Removed plainrouter from MXS-121 branch. --- server/modules/include/plainprotocol.h | 260 ------- server/modules/protocol/CMakeLists.txt | 9 - server/modules/protocol/plainbackend.c | 894 ------------------------ server/modules/protocol/plainclient.c | 714 ------------------- server/modules/routing/plainroute.c | 923 ------------------------- 5 files changed, 2800 deletions(-) delete mode 100644 server/modules/include/plainprotocol.h delete mode 100644 server/modules/protocol/plainbackend.c delete mode 100644 server/modules/protocol/plainclient.c delete mode 100644 server/modules/routing/plainroute.c diff --git a/server/modules/include/plainprotocol.h b/server/modules/include/plainprotocol.h deleted file mode 100644 index 35ecd1cd0..000000000 --- a/server/modules/include/plainprotocol.h +++ /dev/null @@ -1,260 +0,0 @@ -#ifndef _MYSQL_PROTOCOL_H -#define _MYSQL_PROTOCOL_H -/* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free - * software: you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation, - * version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright MariaDB Corporation Ab 2013-2015 - */ - -/* - * Revision History - * - * Date Who Description - * 24-03-2015 Markus Makela Initial implementation - - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define GW_MYSQL_VERSION "MaxScale " MAXSCALE_VERSION -#define GW_MYSQL_LOOP_TIMEOUT 300000000 -#define GW_MYSQL_READ 0 -#define GW_MYSQL_WRITE 1 -#define MYSQL_HEADER_LEN 4L - -#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_SCRAMBLE_SIZE 20 -#define GW_SCRAMBLE_LENGTH_323 8 - -#ifndef MYSQL_SCRAMBLE_LEN -# define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE -#endif - -#define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR) -#define SMALL_CHUNK 1024 -#define MAX_CHUNK SMALL_CHUNK * 8 * 4 -#define ToHex(Y) (Y>='0'&&Y<='9'?Y-'0':Y-'A'+10) -struct dcb; - -/** Protocol packing macros. */ -#define gw_mysql_set_byte2(__buffer, __int) do { \ - (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ - (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); } while (0) -#define gw_mysql_set_byte3(__buffer, __int) do { \ - (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ - (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \ - (__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); } while (0) -#define gw_mysql_set_byte4(__buffer, __int) do { \ - (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ - (__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \ - (__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); \ - (__buffer)[3]= (uint8_t)(((__int) >> 24) & 0xFF); } while (0) - -/** Protocol unpacking macros. */ -#define gw_mysql_get_byte2(__buffer) \ - (uint16_t)((__buffer)[0] | \ - ((__buffer)[1] << 8)) -#define gw_mysql_get_byte3(__buffer) \ - (uint32_t)((__buffer)[0] | \ - ((__buffer)[1] << 8) | \ - ((__buffer)[2] << 16)) -#define gw_mysql_get_byte4(__buffer) \ - (uint32_t)((__buffer)[0] | \ - ((__buffer)[1] << 8) | \ - ((__buffer)[2] << 16) | \ - ((__buffer)[3] << 24)) -#define gw_mysql_get_byte8(__buffer) \ - ((uint64_t)(__buffer)[0] | \ - ((uint64_t)(__buffer)[1] << 8) | \ - ((uint64_t)(__buffer)[2] << 16) | \ - ((uint64_t)(__buffer)[3] << 24) | \ - ((uint64_t)(__buffer)[4] << 32) | \ - ((uint64_t)(__buffer)[5] << 40) | \ - ((uint64_t)(__buffer)[6] << 48) | \ - ((uint64_t)(__buffer)[7] << 56)) - -/** MySQL protocol constants */ -typedef enum -{ - GW_MYSQL_CAPABILITIES_NONE= 0, - GW_MYSQL_CAPABILITIES_LONG_PASSWORD= (1 << 0), - GW_MYSQL_CAPABILITIES_FOUND_ROWS= (1 << 1), - GW_MYSQL_CAPABILITIES_LONG_FLAG= (1 << 2), - GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB= (1 << 3), - GW_MYSQL_CAPABILITIES_NO_SCHEMA= (1 << 4), - GW_MYSQL_CAPABILITIES_COMPRESS= (1 << 5), - GW_MYSQL_CAPABILITIES_ODBC= (1 << 6), - GW_MYSQL_CAPABILITIES_LOCAL_FILES= (1 << 7), - GW_MYSQL_CAPABILITIES_IGNORE_SPACE= (1 << 8), - GW_MYSQL_CAPABILITIES_PROTOCOL_41= (1 << 9), - GW_MYSQL_CAPABILITIES_INTERACTIVE= (1 << 10), - GW_MYSQL_CAPABILITIES_SSL= (1 << 11), - GW_MYSQL_CAPABILITIES_IGNORE_SIGPIPE= (1 << 12), - GW_MYSQL_CAPABILITIES_TRANSACTIONS= (1 << 13), - GW_MYSQL_CAPABILITIES_RESERVED= (1 << 14), - GW_MYSQL_CAPABILITIES_SECURE_CONNECTION= (1 << 15), - GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS= (1 << 16), - 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_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_t; - -/** Copy from enum in mariadb-5.5 mysql_com.h */ -typedef enum mysql_server_cmd { - MYSQL_COM_UNDEFINED = -1, - MYSQL_COM_SLEEP = 0, - MYSQL_COM_QUIT, - MYSQL_COM_INIT_DB, - MYSQL_COM_QUERY, - MYSQL_COM_FIELD_LIST, - MYSQL_COM_CREATE_DB, - MYSQL_COM_DROP_DB, - MYSQL_COM_REFRESH, - MYSQL_COM_SHUTDOWN, - MYSQL_COM_STATISTICS, - MYSQL_COM_PROCESS_INFO, - MYSQL_COM_CONNECT, - MYSQL_COM_PROCESS_KILL, - MYSQL_COM_DEBUG, - MYSQL_COM_PING, - MYSQL_COM_TIME, - MYSQL_COM_DELAYED_INSERT, - MYSQL_COM_CHANGE_USER, - MYSQL_COM_BINLOG_DUMP, - MYSQL_COM_TABLE_DUMP, - MYSQL_COM_CONNECT_OUT, - MYSQL_COM_REGISTER_SLAVE, - MYSQL_COM_STMT_PREPARE, - MYSQL_COM_STMT_EXECUTE, - MYSQL_COM_STMT_SEND_LONG_DATA, - MYSQL_COM_STMT_CLOSE, - MYSQL_COM_STMT_RESET, - MYSQL_COM_SET_OPTION, - MYSQL_COM_STMT_FETCH, - MYSQL_COM_DAEMON, - MYSQL_COM_END /*< Must be the last */ -} mysql_server_cmd_t; - -/** - * MySQL Protocol specific state data. - * - * Protocol carries information from client side to backend side, such as - * MySQL session command information and history of earlier session commands. - */ -typedef struct { -#if defined(SS_DEBUG) - skygw_chk_t protocol_chk_top; -#endif - int fd; /*< The socket descriptor */ - struct dcb *owner_dcb; /*< The DCB of the socket - * we are running on */ - SPINLOCK protocol_lock; - 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 */ - unsigned long tid; /*< MySQL Thread ID, in - * handshake */ - unsigned int charset; /*< MySQL character set at connect time */ -#if defined(SS_DEBUG) - skygw_chk_t protocol_chk_tail; -#endif -} PlainProtocol; - - - -#define MYSQL_GET_COMMAND(payload) (payload[4]) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) -#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) -#define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9])) -#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) -#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) -#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) -#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02) -#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11) -#define MYSQL_GET_NATTR(payload) ((int)payload[4]) - -#endif /** _MYSQL_PROTOCOL_H */ - -PlainProtocol* mysql_protocol_init(DCB* dcb, int fd); -void mysql_protocol_done (DCB* dcb); -PlainProtocol *gw_mysql_init(PlainProtocol *data); -int gw_receive_backend_auth(PlainProtocol *protocol); -int gw_decode_mysql_server_handshake(PlainProtocol *protocol, uint8_t *payload); -int gw_read_backend_handshake(PlainProtocol *protocol); -int gw_send_authentication_to_backend( - char *dbname, - char *user, - uint8_t *passwd, - PlainProtocol *protocol); - -int plain_do_connect_to_backend(char *host, int port, int* fd); diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index 7d118c3ec..fa1c2ab34 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -6,15 +6,6 @@ add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c) target_link_libraries(MySQLBackend log_manager utils) install(TARGETS MySQLBackend DESTINATION modules) -add_library(plainclient SHARED plainclient.c mysql_common.c) -target_link_libraries(plainclient log_manager utils) -install(TARGETS plainclient DESTINATION modules) - -add_library(plainbackend SHARED plainbackend.c mysql_common.c) -target_link_libraries(plainbackend log_manager utils) -install(TARGETS plainbackend DESTINATION modules) - - add_library(telnetd SHARED telnetd.c) target_link_libraries(telnetd log_manager utils) install(TARGETS telnetd DESTINATION modules) diff --git a/server/modules/protocol/plainbackend.c b/server/modules/protocol/plainbackend.c deleted file mode 100644 index b52663b6a..000000000 --- a/server/modules/protocol/plainbackend.c +++ /dev/null @@ -1,894 +0,0 @@ -/* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free - * software: you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation, - * version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright MariaDB Corporation Ab 2013-2014 - */ - -#include -#include -#include -#include -#include - -#define PLAIN_BACKEND_SO_SNDBUF (128 * 1024) -#define PLAIN_BACKEND_SO_RCVBUF (128 * 1024) -/* - * MySQL Protocol module for handling the protocol between the gateway - * and the backend MySQL database. - * - * Revision History - * Date Who Description - * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added MaxScale To Backends routines - * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. - * 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection - * 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend - * 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails - * 12/07/2013 Massimiliano Pinto Added Mysql Change User via dcb->func.auth() - * 15/07/2013 Massimiliano Pinto Added Mysql session change via dcb->func.session() - * 17/07/2013 Massimiliano Pinto Added dcb->command update from gwbuf->command for proper routing - server replies to client via router->clientReply - * 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL - * 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake - * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0 - * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support - * 10/11/2014 Massimiliano Pinto Client charset is passed to backend - * - */ -#include - -MODULE_INFO info = { - MODULE_API_PROTOCOL, - MODULE_GA, - GWPROTOCOL_VERSION, - "The plain protocol" -}; - -/** Defined in log_manager.cc */ -extern int lm_enabled_logfiles_bitmask; -extern size_t log_ses_count[]; -extern __thread log_info_t tls_log_info; - -static char *version_str = "V2.0.0"; -static int plain_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session); -static int plain_read_backend_event(DCB* dcb); -static int plain_write_ready_backend_event(DCB *dcb); -static int plain_write_backend(DCB *dcb, GWBUF *queue); -static int plain_error_backend_event(DCB *dcb); -static int plain_backend_close(DCB *dcb); -static int plain_backend_hangup(DCB *dcb); -static int backend_write_delayqueue(DCB *dcb); -static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); -static int plain_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); -static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); -extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1); -extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); -static bool sescmd_response_complete(DCB* dcb); - -static GWPROTOCOL MyObject = { - plain_read_backend_event, /* Read - EPOLLIN handler */ - plain_write_backend, /* Write - data from gateway */ - plain_write_ready_backend_event, /* WriteReady - EPOLLOUT handler */ - plain_error_backend_event, /* Error - EPOLLERR handler */ - plain_backend_hangup, /* HangUp - EPOLLHUP handler */ - NULL, /* Accept */ - plain_create_backend_connection, /* Connect */ - plain_backend_close, /* Close */ - NULL, /* Listen */ - NULL, /* Authentication */ - NULL /* Session */ -}; - -/* - * Implementation of the mandatory version entry point - * - * @return version string of the module - */ -char * -version() -{ - return version_str; -} - -/* - * The module initialisation routine, called when the module - * is first loaded. - */ -void -ModuleInit() -{ -} - -/* - * The module entry point routine. It is this routine that - * must populate the structure that is referred to as the - * "module object", this is a structure with the set of - * external entry points for this module. - * - * @return The module object - */ -GWPROTOCOL * -GetModuleObject() -{ - return &MyObject; -} - - -/** - * Creates MySQL protocol structure - * - * @param dcb * Must be non-NULL. - * @param fd - * - * @return - * - * - * @details Protocol structure does not have fd because dcb is not - * connected yet. - * - */ -PlainProtocol* plain_protocol_init( - DCB* dcb, - int fd) -{ - PlainProtocol* p; - - p = (PlainProtocol *) calloc(1, sizeof(PlainProtocol)); - ss_dassert(p != NULL); - - if (p == NULL) { - int eno = errno; - errno = 0; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [mysql_init_protocol] MySQL protocol init failed : " - "memory allocation due error %d, %s.", - pthread_self(), - eno, - strerror(eno)))); - goto return_p; - } - - /*< Assign fd with protocol */ - p->fd = fd; - p->owner_dcb = dcb; - -return_p: - return p; -} - - -/** - * Backend Read Event for EPOLLIN on the MySQL backend protocol module - * @param dcb The backend Descriptor Control Block - * @return 1 on operation, 0 for no action - */ -static int plain_read_backend_event(DCB *dcb) { - PlainProtocol *client_protocol = NULL; - PlainProtocol *backend_protocol = NULL; - - int rc = 0; - - - backend_protocol = (PlainProtocol *) dcb->protocol; - CHK_PROTOCOL(backend_protocol); - - - /* reading MySQL command output from backend and writing to the client */ - { - GWBUF *read_buffer = NULL; - ROUTER_OBJECT *router = NULL; - ROUTER *router_instance = NULL; - SESSION *session = dcb->session; - int nbytes_read = 0; - - CHK_SESSION(session); - router = session->service->router; - router_instance = session->service->router_instance; - - /* read available backend data */ - rc = dcb_read(dcb, &read_buffer); - - if (rc < 0) - { - GWBUF* errbuf; - bool succp; - - errbuf = mysql_create_custom_error( - 1, - 0, - "Read from backend failed"); - - router->handleError( - router_instance, - session->router_session, - errbuf, - dcb, - ERRACT_NEW_CONNECTION, - &succp); - gwbuf_free(errbuf); - - if (!succp) - { - spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - } - ss_dassert(dcb->dcb_errhandle_called); - dcb_close(dcb); - rc = 0; - goto return_rc; - } - nbytes_read = gwbuf_length(read_buffer); - - if (nbytes_read == 0 && dcb->dcb_readqueue == NULL) - { - goto return_rc; - } - else - { - ss_dassert(read_buffer != NULL || dcb->dcb_readqueue != NULL); - } - - if (dcb->session->state == SESSION_STATE_ROUTER_READY && - dcb->session->client != NULL && - dcb->session->client->state == DCB_STATE_POLLING) - { - client_protocol = SESSION_PROTOCOL(dcb->session, - PlainProtocol); - - { - gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); - router->clientReply(router_instance, session->router_session, read_buffer, dcb); - rc = 1; - } - } - else /*< session is closing; replying to client isn't possible */ - { - gwbuf_free(read_buffer); - } - } - -return_rc: - return rc; - -return_with_lock: - - goto return_rc; -} - -/* - * EPOLLOUT handler for the MySQL Backend protocol module. - * - * @param dcb The descriptor control block - * @return 1 in success, 0 in case of failure, - */ -static int plain_write_ready_backend_event(DCB *dcb) { - int rc = 0; - PlainProtocol *backend_protocol = dcb->protocol; - - /*< - * Don't write to backend if backend_dcb is not in poll set anymore. - */ - if (dcb->state != DCB_STATE_POLLING) { - uint8_t* data; - - if (dcb->writeq != NULL) - { - data = (uint8_t *)GWBUF_DATA(dcb->writeq); - - - } - else - { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_write_backend_event] Dcb %p in state %s " - "but there's nothing to write either.", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state)))); - rc = 1; - } - goto return_rc; - } - - dcb_drain_writeq(dcb); - rc = 1; -return_rc: - - - return rc; -} - - -/** - * plain_do_connect_to_backend - * - * This routine creates socket and connects to a backend server. - * Connect it non-blocking operation. If connect fails, socket is closed. - * - * @param host The host to connect to - * @param port The host TCP/IP port - * @param *fd where connected fd is copied - * @return 0/1 on success and -1 on failure - * If successful, fd has file descriptor to socket which is connected to - * backend server. In failure, fd == -1 and socket is closed. - * - */ -int plain_do_connect_to_backend( - char *host, - int port, - int *fd) -{ - struct sockaddr_in serv_addr; - int rv; - int so = 0; - int bufsize; - - memset(&serv_addr, 0, sizeof serv_addr); - serv_addr.sin_family = AF_INET; - so = socket(AF_INET,SOCK_STREAM,0); - - if (so < 0) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Establishing connection to backend server " - "%s:%d failed.\n\t\t Socket creation failed " - "due %d, %s.", - host, - port, - errno, - strerror(errno)))); - rv = -1; - goto return_rv; - } - /* prepare for connect */ - setipaddress(&serv_addr.sin_addr, host); - serv_addr.sin_port = htons(port); - bufsize = PLAIN_BACKEND_SO_SNDBUF; - - if(setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to set socket options " - "%s:%d failed.\n\t\t Socket configuration failed " - "due %d, %s.", - host, - port, - errno, - strerror(errno)))); - rv = -1; - /** Close socket */ - goto close_so; - } - bufsize = PLAIN_BACKEND_SO_RCVBUF; - - if(setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to set socket options " - "%s:%d failed.\n\t\t Socket configuration failed " - "due %d, %s.", - host, - port, - errno, - strerror(errno)))); - rv = -1; - /** Close socket */ - goto close_so; - } - - /* set socket to as non-blocking here */ - setnonblocking(so); - rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); - - if (rv != 0) - { - if (errno == EINPROGRESS) - { - rv = 1; - } - else - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to connect backend server %s:%d, " - "due %d, %s.", - host, - port, - errno, - strerror(errno)))); - /** Close socket */ - goto close_so; - } - } - *fd = so; - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [plain_do_connect_to_backend] Connected to backend server " - "%s:%d, fd %d.", - pthread_self(), - host, - port, - so))); - -return_rv: - return rv; - -close_so: - /*< Close newly created socket. */ - if (close(so) != 0) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to " - "close socket %d due %d, %s.", - so, - errno, - strerror(errno)))); - } - goto return_rv; -} - -/* - * Write function for backend DCB. Store command to protocol. - * - * @param dcb The DCB of the backend - * @param queue Queue of buffers to write - * @return 0 on failure, 1 on success - */ -static int -plain_write_backend(DCB *dcb, GWBUF *queue) -{ - PlainProtocol *backend_protocol = dcb->protocol; - int rc = 0; - - rc = dcb_write(dcb, queue); - - return rc; -} - -/** - * Error event handler. - * Create error message, pass it to router's error handler and if error - * handler fails in providing enough backend servers, mark session being - * closed and call DCB close function which triggers closing router session - * and related backends (if any exists. - */ -static int plain_error_backend_event(DCB *dcb) -{ - SESSION* session; - void* rsession; - ROUTER_OBJECT* router; - ROUTER* router_instance; - GWBUF* errbuf; - bool succp; - session_state_t ses_state; - - CHK_DCB(dcb); - session = dcb->session; - CHK_SESSION(session); - rsession = session->router_session; - router = session->service->router; - router_instance = session->service->router_instance; - - /** - * Avoid running redundant error handling procedure. - * dcb_close is already called for the DCB. Thus, either connection is - * closed by router and COM_QUIT sent or there was an error which - * have already been handled. - */ - if (dcb->state != DCB_STATE_POLLING) - { - int error, len; - char buf[100]; - - len = sizeof(error); - - if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) - { - if (error != 0) - { - strerror_r(error, buf, 100); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "DCB in state %s got error '%s'.", - STRDCBSTATE(dcb->state), - buf))); - } - } - return 1; - } - - spinlock_acquire(&session->ses_lock); - ses_state = session->state; - spinlock_release(&session->ses_lock); - - /** - * Session might be initialized when DCB already is in the poll set. - * Thus hangup can occur in the middle of session initialization. - * Only complete and successfully initialized sessions allow for - * calling error handler. - */ - while (ses_state == SESSION_STATE_READY) - { - spinlock_acquire(&session->ses_lock); - ses_state = session->state; - spinlock_release(&session->ses_lock); - } - - if (ses_state != SESSION_STATE_ROUTER_READY) - { - int error, len; - char buf[100]; - - len = sizeof(error); - if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) - { - if (error != 0) - { - strerror_r(error, buf, 100); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error '%s' in session that is not ready for routing.", - buf))); - } - } - gwbuf_free(errbuf); - goto retblock; - } - -#if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend error event handling."))); -#endif - router->handleError(router_instance, - rsession, - errbuf, - dcb, - ERRACT_NEW_CONNECTION, - &succp); - gwbuf_free(errbuf); - - /** - * If error handler fails it means that routing session can't continue - * and it must be closed. In success, only this DCB is closed. - */ - if (!succp) - { - spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - } - ss_dassert(dcb->dcb_errhandle_called); - dcb_close(dcb); - -retblock: - return 1; -} - -/* - * Create a new backend connection. - * - * This routine will connect to a backend server and it is called by dbc_connect - * in router->newSession - * - * @param backend_dcb, in, out, use - backend DCB allocated from dcb_connect - * @param server, in, use - server to connect to - * @param session, in use - current session from client DCB - * @return 0/1 on Success and -1 on Failure. - * If succesful, returns positive fd to socket which is connected to - * backend server. Positive fd is copied to protocol and to dcb. - * If fails, fd == -1 and socket is closed. - */ -static int plain_create_backend_connection( - DCB *backend_dcb, - SERVER *server, - SESSION *session) -{ - PlainProtocol *protocol = NULL; - int rv = -1; - int fd = -1; - - protocol = mysql_protocol_init(backend_dcb, -1); - ss_dassert(protocol != NULL); - - if (protocol == NULL) { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_create_backend_connection] Failed to create " - "protocol object for backend connection.", - pthread_self()))); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to create " - "protocol object for backend connection."))); - goto return_fd; - } - - /*< if succeed, fd > 0, -1 otherwise */ - - rv = plain_do_connect_to_backend(server->name, server->port, &fd); - /*< Assign protocol with backend_dcb */ - backend_dcb->protocol = protocol; - - /*< Set protocol state */ - switch (rv) { - case 0: - ss_dassert(fd > 0); - protocol->fd = fd; - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_create_backend_connection] Established " - "connection to %s:%i, protocol fd %d client " - "fd %d.", - pthread_self(), - server->name, - server->port, - protocol->fd, - session->client->fd))); - break; - - case 1: - ss_dassert(fd > 0); - - protocol->fd = fd; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_create_backend_connection] Connection " - "pending to %s:%i, protocol fd %d client fd %d.", - pthread_self(), - server->name, - server->port, - protocol->fd, - session->client->fd))); - break; - - default: - ss_dassert(fd == -1); - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_create_backend_connection] Connection " - "failed to %s:%i, protocol fd %d client fd %d.", - pthread_self(), - server->name, - server->port, - protocol->fd, - session->client->fd))); - break; - } /*< switch */ - -return_fd: - return fd; -} - - -/** - * Error event handler. - * Create error message, pass it to router's error handler and if error - * handler fails in providing enough backend servers, mark session being - * closed and call DCB close function which triggers closing router session - * and related backends (if any exists. - * - * @param dcb The current Backend DCB - * @return 1 always - */ -static int -plain_backend_hangup(DCB *dcb) -{ - SESSION* session; - void* rsession; - ROUTER_OBJECT* router; - ROUTER* router_instance; - bool succp; - GWBUF* errbuf; - session_state_t ses_state; - - CHK_DCB(dcb); - session = dcb->session; - CHK_SESSION(session); - - rsession = session->router_session; - router = session->service->router; - router_instance = session->service->router_instance; - - spinlock_acquire(&session->ses_lock); - ses_state = session->state; - spinlock_release(&session->ses_lock); - - /** - * Session might be initialized when DCB already is in the poll set. - * Thus hangup can occur in the middle of session initialization. - * Only complete and successfully initialized sessions allow for - * calling error handler. - */ - while (ses_state == SESSION_STATE_READY) - { - spinlock_acquire(&session->ses_lock); - ses_state = session->state; - spinlock_release(&session->ses_lock); - } - - if (ses_state != SESSION_STATE_ROUTER_READY) - { - int error, len; - char buf[100]; - - len = sizeof(error); - if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) - { - if (error != 0) - { - strerror_r(error, buf, 100); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Hangup in session that is not ready for routing, " - "Error reported is '%s'.", - buf))); - } - } - gwbuf_free(errbuf); - goto retblock; - } -#if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend hangup error handling."))); -#endif - - router->handleError(router_instance, - rsession, - errbuf, - dcb, - ERRACT_NEW_CONNECTION, - &succp); - - gwbuf_free(errbuf); - /** There are no required backends available, close session. */ - if (!succp) - { -#if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend hangup -> closing session."))); -#endif - spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - } - ss_dassert(dcb->dcb_errhandle_called); - dcb_close(dcb); - -retblock: - return 1; -} - -/** - * Send COM_QUIT to backend so that it can be closed. - * @param dcb The current Backend DCB - * @return 1 always - */ -static int -plain_backend_close(DCB *dcb) -{ - DCB* client_dcb; - SESSION* session; - GWBUF* quitbuf; - - CHK_DCB(dcb); - session = dcb->session; - CHK_SESSION(session); - - /** - * The lock is needed only to protect the read of session->state and - * session->client values. Client's state may change by other thread - * but client's close and adding client's DCB to zombies list is executed - * only if client's DCB's state does _not_ change in parallel. - */ - spinlock_acquire(&session->ses_lock); - /** - * If session->state is STOPPING, start closing client session. - * Otherwise only this backend connection is closed. - */ - if (session != NULL && - session->state == SESSION_STATE_STOPPING && - session->client != NULL) - { - if (session->client->state == DCB_STATE_POLLING) - { - spinlock_release(&session->ses_lock); - - /** Close client DCB */ - dcb_close(session->client); - } - else - { - spinlock_release(&session->ses_lock); - } - } - else - { - spinlock_release(&session->ses_lock); - } - return 1; -} - -/** - * This routine put into the delay queue the input queue - * The input is what backend DCB is receiving - * The routine is called from func.write() when mysql backend connection - * is not yet complete buu there are inout data from client - * - * @param dcb The current backend DCB - * @param queue Input data in the GWBUF struct - */ -static void backend_set_delayqueue(DCB *dcb, GWBUF *queue) { - spinlock_acquire(&dcb->delayqlock); - - if (dcb->delayq) { - /* Append data */ - dcb->delayq = gwbuf_append(dcb->delayq, queue); - } else { - if (queue != NULL) { - /* create the delay queue */ - dcb->delayq = queue; - } - } - spinlock_release(&dcb->delayqlock); -} - -/** - * This routine writes the delayq via dcb_write - * The dcb->delayq contains data received from the client before - * mysql backend authentication succeded - * - * @param dcb The current backend DCB - * @return The dcb_write status - */ -static int backend_write_delayqueue(DCB *dcb) -{ - GWBUF *localq = NULL; - int rc; - - spinlock_acquire(&dcb->delayqlock); - - if (dcb->delayq == NULL) - { - spinlock_release(&dcb->delayqlock); - rc = 1; - } - else - { - - rc = dcb_write(dcb, localq); - } - - if (rc == 0) - { -#if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend write delayqueue error handling."))); -#endif - - } - return rc; -} diff --git a/server/modules/protocol/plainclient.c b/server/modules/protocol/plainclient.c deleted file mode 100644 index 450634e12..000000000 --- a/server/modules/protocol/plainclient.c +++ /dev/null @@ -1,714 +0,0 @@ -/* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free - * software: you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation, - * version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright MariaDB Corporation Ab 2013-2015 - */ - -/** - * @file mongo_client.c - * - * Revision History - * Date Who Description - - * - */ -#include -#include -#include -#include -#include -#include -#include - -MODULE_INFO info = { - MODULE_API_PROTOCOL, - MODULE_GA, - GWPROTOCOL_VERSION, - "The plain client protocol" -}; - -/** Defined in log_manager.cc */ -extern int lm_enabled_logfiles_bitmask; -extern size_t log_ses_count[]; -extern __thread log_info_t tls_log_info; - -static char *version_str = "V1.0.0"; - -static int plain_accept(DCB *listener); -static int plain_listen(DCB *listener, char *config_bind); -static int plain_read(DCB* dcb); -static int plain_write_ready(DCB *dcb); -static int plain_write(DCB *dcb, GWBUF *queue); -static int plain_client_error(DCB *dcb); -static int plain_client_close(DCB *dcb); -static int plain_client_hangup_event(DCB *dcb); - -/* - * The "module object" for the mysqld client protocol module. - */ -static GWPROTOCOL MyObject = { - plain_read, /* Read - EPOLLIN handler */ - plain_write, /* Write - data from gateway */ - plain_write_ready, /* WriteReady - EPOLLOUT handler */ - plain_client_error, /* Error - EPOLLERR handler */ - plain_client_hangup_event, /* HangUp - EPOLLHUP handler */ - plain_accept, /* Accept */ - NULL, /* Connect */ - plain_client_close, /* Close */ - plain_listen, /* Listen */ - NULL, /* Authentication */ - NULL /* Session */ -}; - -/** - * Implementation of the mandatory version entry point - * - * @return version string of the module - */ -char * -version() -{ - return version_str; -} - -/** - * The module initialisation routine, called when the module - * is first loaded. - */ -void -ModuleInit() -{ -} - -/** - * The module entry point routine. It is this routine that - * must populate the structure that is referred to as the - * "module object", this is a structure with the set of - * external entry points for this module. - * - * @return The module object - */ -GWPROTOCOL * -GetModuleObject() -{ - return &MyObject; -} - - -/** - * Write function for client DCB: writes data from MaxScale to Client - * - * @param dcb The DCB of the client - * @param queue Queue of buffers to write - */ -int -plain_write(DCB *dcb, GWBUF *queue) -{ - return dcb_write(dcb, queue); -} - -/** - * Client read event triggered by EPOLLIN - * - * @param dcb Descriptor control block - * @return 0 if succeed, 1 otherwise - */ -int plain_read( - DCB* dcb) -{ - SESSION *session = NULL; - ROUTER_OBJECT *router = NULL; - ROUTER *router_instance = NULL; - void *rsession = NULL; - PlainProtocol *protocol = NULL; - GWBUF *read_buffer = NULL; - int rc = 0; - int nbytes_read = 0; - uint8_t cap = 0; - bool stmt_input = false; /*< router input type */ - - CHK_DCB(dcb); - protocol = DCB_PROTOCOL(dcb, PlainProtocol); - CHK_PROTOCOL(protocol); - rc = dcb_read(dcb, &read_buffer); - - if (rc < 0) - { - dcb_close(dcb); - } - nbytes_read = gwbuf_length(read_buffer); - - if (nbytes_read == 0) - { - goto return_rc; - } - - if(dcb->session == NULL) - { - dcb->session = session_alloc(dcb->service,dcb); - } - - rc = SESSION_ROUTE_QUERY(dcb->session, read_buffer); - - -return_rc: - - return rc; -} - -/////////////////////////////////////////////// -// client write event to Client triggered by EPOLLOUT -////////////////////////////////////////////// -/** - * @node Client's fd became writable, and EPOLLOUT event - * arrived. As a consequence, client input buffer (writeq) is flushed. - * - * Parameters: - * @param dcb - in, use - * client dcb - * - * @return constantly 1 - * - * - * @details (write detailed description here) - * - */ -int plain_write_ready(DCB *dcb) -{ - PlainProtocol *protocol = NULL; - - CHK_DCB(dcb); - - ss_dassert(dcb->state != DCB_STATE_DISCONNECTED); - - if (dcb == NULL) { - goto return_1; - } - - if (dcb->state == DCB_STATE_DISCONNECTED) { - goto return_1; - } - - if (dcb->protocol == NULL) { - goto return_1; - } - protocol = (PlainProtocol *)dcb->protocol; - - - dcb_drain_writeq(dcb); - goto return_1; - -return_1: - - return 1; -} - -/** - * set listener for mysql protocol, retur 1 on success and 0 in failure - */ -int plain_listen( - DCB *listen_dcb, - char *config_bind) -{ - int l_so; - int syseno = 0; - struct sockaddr_in serv_addr; - struct sockaddr_un local_addr; - struct sockaddr *current_addr; - int one = 1; - int rc; - - if (strchr(config_bind, '/')) { - char *tmp = strrchr(config_bind, ':'); - if (tmp) - *tmp = '\0'; - - // UNIX socket create - if ((l_so = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - fprintf(stderr, - "\n* Error: can't create UNIX socket due " - "error %i, %s.\n\n\t", - errno, - strerror(errno)); - return 0; - } - memset(&local_addr, 0, sizeof(local_addr)); - local_addr.sun_family = AF_UNIX; - strncpy(local_addr.sun_path, config_bind, sizeof(local_addr.sun_path) - 1); - - current_addr = (struct sockaddr *) &local_addr; - - } else { - /* MaxScale, as default, will bind on port 4406 */ - if (!parse_bindconfig(config_bind, 4406, &serv_addr)) { - fprintf(stderr, "Error in parse_bindconfig for [%s]\n", config_bind); - return 0; - } - // TCP socket create - if ((l_so = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - fprintf(stderr, - "\n* Error: can't create socket due " - "error %i, %s.\n\n\t", - errno, - strerror(errno)); - return 0; - } - - current_addr = (struct sockaddr *) &serv_addr; - } - - listen_dcb->fd = -1; - - // socket options - if((syseno = setsockopt(l_so, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one))) != 0){ - LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); - } - - - // set NONBLOCKING mode - setnonblocking(l_so); - - /* get the right socket family for bind */ - switch (current_addr->sa_family) { - case AF_UNIX: - rc = unlink(config_bind); - if ( (rc == -1) && (errno!=ENOENT) ) { - fprintf(stderr, "Error unlink Unix Socket %s\n", config_bind); - } - - if (bind(l_so, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) { - fprintf(stderr, - "\n* Bind failed due error %i, %s.\n", - errno, - strerror(errno)); - fprintf(stderr, "* Can't bind to %s\n\n", config_bind); - close(l_so); - return 0; - } - - /* set permission for all users */ - if (chmod(config_bind, 0777) < 0) { - fprintf(stderr, - "\n* chmod failed for %s due error %i, %s.\n\n", - config_bind, - errno, - strerror(errno)); - } - - break; - - case AF_INET: - if (bind(l_so, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - fprintf(stderr, - "\n* Bind failed due error %i, %s.\n", - errno, - strerror(errno)); - fprintf(stderr, "* Can't bind to %s\n\n", config_bind); - close(l_so); - return 0; - } - break; - - default: - fprintf(stderr, "* Socket Family %i not supported\n", current_addr->sa_family); - close(l_so); - return 0; - } - - rc = listen(l_so, 10 * SOMAXCONN); - - if (rc == 0) { - LOGIF(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening MySQL connections at %s", config_bind))); - } else { - int eno = errno; - errno = 0; - fprintf(stderr, - "\n* Failed to start listening MySQL due error %d, %s\n\n", - eno, - strerror(eno)); - close(l_so); - return 0; - } - // assign l_so to dcb - listen_dcb->fd = l_so; - - // add listening socket to poll structure - if (poll_add_dcb(listen_dcb) == -1) { - fprintf(stderr, - "\n* Failed to start polling the socket due error " - "%i, %s.\n\n", - errno, - strerror(errno)); - return 0; - } -#if defined(FAKE_CODE) - conn_open[l_so] = true; -#endif /* FAKE_CODE */ - listen_dcb->func.accept = plain_accept; - - return 1; -} - - -/** - * @node (write brief function description here) - * - * Parameters: - * @param listener - - * - * - * @return 0 in success, 1 in failure - * - * - * @details (write detailed description here) - * - */ -int plain_accept(DCB *listener) -{ - int rc = 0; - DCB *client_dcb; - PlainProtocol *protocol; - int c_sock; - struct sockaddr client_conn; - socklen_t client_len = sizeof(struct sockaddr_storage); - int sendbuf = GW_BACKEND_SO_SNDBUF; - socklen_t optlen = sizeof(sendbuf); - int eno = 0; - int syseno = 0; - int i = 0; - - CHK_DCB(listener); - - while (1) { - - retry_accept: - - // new connection from client - c_sock = accept(listener->fd, - (struct sockaddr *) &client_conn, - &client_len); - eno = errno; - errno = 0; - - if (c_sock == -1) { - - if (eno == EAGAIN || eno == EWOULDBLOCK) - { - /** - * We have processed all incoming connections. - */ - rc = 1; - goto return_rc; - } - else if (eno == ENFILE || eno == EMFILE) - { - struct timespec ts1; - ts1.tv_sec = 0; - /** - * Exceeded system's (ENFILE) or processes - * (EMFILE) max. number of files limit. - */ - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [plain_accept] Error %d, %s. ", - pthread_self(), - eno, - strerror(eno)))); - - if (i == 0) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error %d, %s. " - "Failed to accept new client " - "connection.", - eno, - strerror(eno)))); - } - i++; - ts1.tv_nsec = 100*i*i*1000000; - nanosleep(&ts1, NULL); - - if (i<10) { - goto retry_accept; - } - rc = 1; - goto return_rc; - } - else - { - /** - * Other error. - */ - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [plain_accept] Error %d, %s.", - pthread_self(), - eno, - strerror(eno)))); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Failed to accept new client " - "connection due to %d, %s.", - eno, - strerror(eno)))); - rc = 1; - goto return_rc; - } /* if (eno == ..) */ - } /* if (c_sock == -1) */ - /* reset counter */ - i = 0; - - listener->stats.n_accepts++; -#if defined(SS_DEBUG) - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [plain_accept] Accepted fd %d.", - pthread_self(), - c_sock))); -#endif /* SS_DEBUG */ - - /* set nonblocking */ - sendbuf = GW_CLIENT_SO_SNDBUF; - - if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, optlen)) != 0){ - LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); - } - - sendbuf = GW_CLIENT_SO_RCVBUF; - - if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_RCVBUF, &sendbuf, optlen)) != 0){ - LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); - } - setnonblocking(c_sock); - - client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); - - if (client_dcb == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Failed to create " - "DCB object for client connection."))); - close(c_sock); - rc = 1; - goto return_rc; - } - - client_dcb->service = listener->session->service; - client_dcb->fd = c_sock; - - // get client address - if ( client_conn.sa_family == AF_UNIX) - { - // client address - client_dcb->remote = strdup("localhost_from_socket"); - // set localhost IP for user authentication - (client_dcb->ipv4).sin_addr.s_addr = 0x0100007F; - } - else - { - /* client IPv4 in raw data*/ - memcpy(&client_dcb->ipv4, - (struct sockaddr_in *)&client_conn, - sizeof(struct sockaddr_in)); - /* client IPv4 in string representation */ - client_dcb->remote = (char *)calloc(INET_ADDRSTRLEN+1, sizeof(char)); - - if (client_dcb->remote != NULL) - { - inet_ntop(AF_INET, - &(client_dcb->ipv4).sin_addr, - client_dcb->remote, - INET_ADDRSTRLEN); - } - } - protocol = mysql_protocol_init(client_dcb, c_sock); - ss_dassert(protocol != NULL); - - if (protocol == NULL) { - /** delete client_dcb */ - dcb_close(client_dcb); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [plain_accept] Failed to create " - "protocol object for client connection.", - pthread_self()))); - rc = 1; - goto return_rc; - } - client_dcb->protocol = protocol; - // assign function poiters to "func" field - memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); - - - /** - * Set new descriptor to event set. At the same time, - * change state to DCB_STATE_POLLING so that - * thread which wakes up sees correct state. - */ - if (poll_add_dcb(client_dcb) == -1) - { - /* Send a custom error as MySQL command reply */ - - /** close client_dcb */ - dcb_close(client_dcb); - - /** Previous state is recovered in poll_add_dcb. */ - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [plain_accept] Failed to add dcb %p for " - "fd %d to epoll set.", - pthread_self(), - client_dcb, - client_dcb->fd))); - rc = 1; - goto return_rc; - } - else - { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [plain_accept] Added dcb %p for fd " - "%d to epoll set.", - pthread_self(), - client_dcb, - client_dcb->fd))); - } - } /**< while 1 */ -#if defined(SS_DEBUG) - if (rc == 0) { - CHK_DCB(client_dcb); - CHK_PROTOCOL(((PlainProtocol *)client_dcb->protocol)); - } -#endif -return_rc: - - return rc; -} - -static int plain_client_error( - DCB* dcb) -{ - SESSION* session; - - CHK_DCB(dcb); - - session = dcb->session; - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [plain_client_error] Error event handling for DCB %p " - "in state %s, session %p.", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state), - (session != NULL ? session : NULL)))); - - if (session != NULL && session->state == SESSION_STATE_STOPPING) - { - goto retblock; - } - - dcb_close(dcb); - -retblock: - return 1; -} - -static int -plain_client_close(DCB *dcb) -{ - SESSION* session; - ROUTER_OBJECT* router; - void* router_instance; -#if defined(SS_DEBUG) - PlainProtocol* protocol = (PlainProtocol *)dcb->protocol; - if (dcb->state == DCB_STATE_POLLING || - dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE) - { - if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(protocol); - } -#endif - LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, - "%lu [plain_client_close]", - pthread_self()))); - mysql_protocol_done(dcb); - session = dcb->session; - /** - * session may be NULL if session_alloc failed. - * In that case, router session wasn't created. - */ - if (session != NULL) - { - CHK_SESSION(session); - spinlock_acquire(&session->ses_lock); - - if (session->state != SESSION_STATE_STOPPING) - { - session->state = SESSION_STATE_STOPPING; - } - router_instance = session->service->router_instance; - router = session->service->router; - /** - * If router session is being created concurrently router - * session might be NULL and it shouldn't be closed. - */ - if (session->router_session != NULL) - { - spinlock_release(&session->ses_lock); - /** Close router session and all its connections */ - router->closeSession(router_instance, session->router_session); - } - else - { - spinlock_release(&session->ses_lock); - } - } - return 1; -} - -/** - * Handle a hangup event on the client side descriptor. - * - * We simply close the DCB, this will propogate the closure to any - * backend descriptors and perform the session cleanup. - * - * @param dcb The DCB of the connection - */ -static int -plain_client_hangup_event(DCB *dcb) -{ - SESSION* session; - - CHK_DCB(dcb); - session = dcb->session; - - if (session != NULL && session->state == SESSION_STATE_ROUTER_READY) - { - CHK_SESSION(session); - } - - if (session != NULL && session->state == SESSION_STATE_STOPPING) - { - goto retblock; - } - - dcb_close(dcb); - -retblock: - return 1; -} - diff --git a/server/modules/routing/plainroute.c b/server/modules/routing/plainroute.c deleted file mode 100644 index 245a3427a..000000000 --- a/server/modules/routing/plainroute.c +++ /dev/null @@ -1,923 +0,0 @@ -/* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free - * software: you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation, - * version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright MariaDB Corporation Ab 2013-2014 - */ - -/** - * @file readconnroute.c - Read Connection Load Balancing Query Router - * - * This is the implementation of a simple query router that balances - * read connections. It assumes the service is configured with a set - * of slaves and that the application clients already split read and write - * queries. It offers a service to balance the client read connections - * over this set of slave servers. It does this once only, at the time - * the connection is made. It chooses the server that currently has the least - * number of connections by keeping a count for each server of how - * many connections the query router has made to the server. - * - * When two servers have the same number of current connections the one with - * the least number of connections since startup will be used. - * - * The router may also have options associated to it that will limit the - * choice of backend server. Currently two options are supported, the "master" - * option will cause the router to only connect to servers marked as masters - * and the "slave" option will limit connections to routers that are marked - * as slaves. If neither option is specified the router will connect to either - * masters or slaves. - * - * @verbatim - * Revision History - * - * Date Who Description - * 14/06/2013 Mark Riddoch Initial implementation - * 25/06/2013 Mark Riddoch Addition of checks for current server state - * 26/06/2013 Mark Riddoch Use server with least connections since - * startup if the number of current - * connections is the same for two servers - * Addition of master and slave options - * 27/06/2013 Vilho Raatikka Added skygw_log_write command as an example - * and necessary headers. - * 17/07/2013 Massimiliano Pinto Added clientReply routine: - * called by backend server to send data to client - * Included mysql_client_server_protocol.h - * with macros and MySQL commands with MYSQL_ prefix - * avoiding any conflict with the standard ones - * in mysql.h - * 22/07/2013 Mark Riddoch Addition of joined router option for Galera - * clusters - * 31/07/2013 Massimiliano Pinto Added a check for candidate server, if NULL return - * 12/08/2013 Mark Riddoch Log unsupported router options - * 04/09/2013 Massimiliano Pinto Added client NULL check in clientReply - * 22/10/2013 Massimiliano Pinto errorReply called from backend, for client error reply - * or take different actions such as open a new backend connection - * 20/02/2014 Massimiliano Pinto If router_options=slave, route traffic to master if no slaves available - * 06/03/2014 Massimiliano Pinto Server connection counter is now updated in closeSession - * 24/06/2014 Massimiliano Pinto New rules for selecting the Master server - * 27/06/2014 Mark Riddoch Addition of server weighting - * - * @endverbatim - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -/** Defined in log_manager.cc */ -extern int lm_enabled_logfiles_bitmask; -extern size_t log_ses_count[]; -extern __thread log_info_t tls_log_info; - -MODULE_INFO info = { - MODULE_API_ROUTER, - MODULE_GA, - ROUTER_VERSION, - "A connection based router to load balance based on connections" -}; - -static char *version_str = "V1.1.0"; - -/* The router entry points */ -static ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(ROUTER *instance, SESSION *session); -static void closeSession(ROUTER *instance, void *router_session); -static void freeSession(ROUTER *instance, void *router_session); -static int routeQuery(ROUTER *instance, void *router_session, GWBUF *queue); -static void diagnostics(ROUTER *instance, DCB *dcb); -static void clientReply( - ROUTER *instance, - void *router_session, - GWBUF *queue, - DCB *backend_dcb); -static void handleError( - ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, - error_action_t action, - bool *succp); -static uint8_t getCapabilities (ROUTER* inst, void* router_session); - - -/** The module object definition */ -static ROUTER_OBJECT MyObject = { - createInstance, - newSession, - closeSession, - freeSession, - routeQuery, - diagnostics, - clientReply, - handleError, - getCapabilities -}; - -static bool rses_begin_locked_router_action( - ROUTER_CLIENT_SES* rses); - -static void rses_end_locked_router_action( - ROUTER_CLIENT_SES* rses); - -static BACKEND *get_root_master( - BACKEND **servers); -static int handle_state_switch( - DCB* dcb,DCB_REASON reason, void * routersession); -static SPINLOCK instlock; -static ROUTER_INSTANCE *instances; - -/** - * Implementation of the mandatory version entry point - * - * @return version string of the module - */ -char * -version() -{ - return version_str; -} - -/** - * The module initialisation routine, called when the module - * is first loaded. - */ -void -ModuleInit() -{ - LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "Initialise readconnroute router module %s.\n", version_str))); - spinlock_init(&instlock); - instances = NULL; -} - -/** - * The module entry point routine. It is this routine that - * must populate the structure that is referred to as the - * "module object", this is a structure with the set of - * external entry points for this module. - * - * @return The module object - */ -ROUTER_OBJECT * -GetModuleObject() -{ - return &MyObject; -} - -/** - * Create an instance of the router for a particular service - * within the gateway. - * - * @param service The service this router is being create for - * @param options An array of options for this query router - * - * @return The instance data for this new instance - */ -static ROUTER * -createInstance(SERVICE *service, char **options) -{ -ROUTER_INSTANCE *inst; -SERVER *server; -SERVER_REF *sref; -int i, n; -BACKEND *backend; -char *weightby; - - if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { - return NULL; - } - - inst->service = service; - spinlock_init(&inst->lock); - - /* - * We need an array of the backend servers in the instance structure so - * that we can maintain a count of the number of connections to each - * backend server. - */ - for (sref = service->dbref, n = 0; sref; sref = sref->next) - n++; - - inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *)); - if (!inst->servers) - { - free(inst); - return NULL; - } - - for (sref = service->dbref, n = 0; sref; sref = sref->next) - { - if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL) - { - for (i = 0; i < n; i++) - free(inst->servers[i]); - free(inst->servers); - free(inst); - return NULL; - } - inst->servers[n]->server = sref->server; - inst->servers[n]->current_connection_count = 0; - inst->servers[n]->weight = 1000; - n++; - } - inst->servers[n] = NULL; - - if ((weightby = serviceGetWeightingParameter(service)) != NULL) - { - int total = 0; - for (n = 0; inst->servers[n]; n++) - { - backend = inst->servers[n]; - total += atoi(serverGetParameter(backend->server, - weightby)); - } - if (total == 0) - { - LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, - "WARNING: Weighting Parameter for service '%s' " - "will be ignored as no servers have values " - "for the parameter '%s'.\n", - service->name, weightby))); - } - else - { - for (n = 0; inst->servers[n]; n++) - { - int perc, wght; - backend = inst->servers[n]; - perc = ((wght = atoi(serverGetParameter(backend->server, - weightby))) * 1000) / total; - if (perc == 0 && wght != 0) - perc = 1; - backend->weight = perc; - if (perc == 0) - { - LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, - "Server '%s' has no value " - "for weighting parameter '%s', " - "no queries will be routed to " - "this server.\n", - inst->servers[n]->server->unique_name, - weightby))); - } - - } - } - } - - /* - * Process the options - */ - inst->bitmask = 0; - inst->bitvalue = 0; - if (options) - { - for (i = 0; options[i]; i++) - { - if (!strcasecmp(options[i], "master")) - { - inst->bitmask |= (SERVER_MASTER|SERVER_SLAVE); - inst->bitvalue |= SERVER_MASTER; - } - else if (!strcasecmp(options[i], "slave")) - { - inst->bitmask |= (SERVER_MASTER|SERVER_SLAVE); - inst->bitvalue |= SERVER_SLAVE; - } - else if (!strcasecmp(options[i], "synced")) - { - inst->bitmask |= (SERVER_JOINED); - inst->bitvalue |= SERVER_JOINED; - } - else if (!strcasecmp(options[i], "ndb")) - { - inst->bitmask |= (SERVER_NDB); - inst->bitvalue |= SERVER_NDB; - } - else - { - LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "* Warning : Unsupported router " - "option \'%s\' for readconnroute. " - "Expected router options are " - "[slave|master|synced|ndb]", - options[i]))); - } - } - } - - /* - * We have completed the creation of the instance data, so now - * insert this router instance into the linked list of routers - * that have been created with this module. - */ - spinlock_acquire(&instlock); - inst->next = instances; - instances = inst; - spinlock_release(&instlock); - - return (ROUTER *)inst; -} - -/** - * Associate a new session with this instance of the router. - * - * @param instance The router instance data - * @param session The session itself - * @return Session specific data for this session - */ -static void * -newSession(ROUTER *instance, SESSION *session) -{ -ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; -ROUTER_CLIENT_SES *client_rses; -BACKEND *candidate = NULL; -int i; - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [newSession] new router session with session " - "%p, and inst %p.", - pthread_self(), - session, - inst))); - - - client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); - - if (client_rses == NULL) { - return NULL; - } - -#if defined(SS_DEBUG) - client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; - client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; -#endif - - - /** - * Find a backend server to connect to. This is the extent of the - * load balancing algorithm we need to implement for this simple - * connection router. - */ - - /* - * Loop over all the servers and find any that have fewer connections - * than the candidate server. - * - * If a server has less connections than the current candidate we mark this - * as the new candidate to connect to. - * - * If a server has the same number of connections currently as the candidate - * and has had less connections over time than the candidate it will also - * become the new candidate. This has the effect of spreading the - * connections over different servers during periods of very low load. - */ - for (i = 0; inst->servers[i]; i++) { - - - if (SERVER_IN_MAINT(inst->servers[i]->server)) - continue; - - if (inst->servers[i]->weight == 0) - continue; - - - - /* If no candidate set, set first running server as - our initial candidate server */ - if(candidate == NULL) - { - candidate = inst->servers[i]; - } - else if((inst->servers[i]->current_connection_count - * 1000) / inst->servers[i]->weight < - (candidate->current_connection_count * - 1000) / candidate->weight) - { - /* This running server has fewer - connections, set it as a new candidate */ - candidate = inst->servers[i]; - } - else if((inst->servers[i]->current_connection_count - * 1000) / inst->servers[i]->weight == - (candidate->current_connection_count * - 1000) / candidate->weight && - inst->servers[i]->server->stats.n_connections < - candidate->server->stats.n_connections) - { - /* This running server has the same number - of connections currently as the candidate - but has had fewer connections over time - than candidate, set this server to candidate*/ - candidate = inst->servers[i]; - } - } - - - - client_rses->rses_capabilities = RCAP_TYPE_PACKET_INPUT; - - /* - * We now have the server with the least connections. - * Bump the connection count for this server - */ - atomic_add(&candidate->current_connection_count, 1); - client_rses->backend = candidate; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [newSession] Selected server in port %d. " - "Connections : %d\n", - pthread_self(), - candidate->server->port, - candidate->current_connection_count))); - /* - * Open a backend connection, putting the DCB for this - * connection in the client_rses->backend_dcb - */ - client_rses->backend_dcb = dcb_connect(candidate->server, - session, - candidate->server->protocol); - if (client_rses->backend_dcb == NULL) - { - atomic_add(&candidate->current_connection_count, -1); - free(client_rses); - return NULL; - } - dcb_add_callback( - client_rses->backend_dcb, - DCB_REASON_NOT_RESPONDING, - &handle_state_switch, - client_rses); - inst->stats.n_sessions++; - - /** - * Add this session to the list of active sessions. - */ - spinlock_acquire(&inst->lock); - client_rses->next = inst->connections; - inst->connections = client_rses; - spinlock_release(&inst->lock); - - - return (void *)client_rses; -} - -/** - * @node Unlink from backend server, unlink from router's connection list, - * and free memory of a router client session. - * - * Parameters: - * @param router - - * - * - * @param router_cli_ses - - * - * - * @return void - * - * - * @details (write detailed description here) - * - */ -static void freeSession( - ROUTER* router_instance, - void* router_client_ses) -{ - ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_instance; - ROUTER_CLIENT_SES* router_cli_ses = - (ROUTER_CLIENT_SES *)router_client_ses; - int prev_val; - - prev_val = atomic_add(&router_cli_ses->backend->current_connection_count, -1); - ss_dassert(prev_val > 0); - - spinlock_acquire(&router->lock); - - if (router->connections == router_cli_ses) { - router->connections = router_cli_ses->next; - } else { - ROUTER_CLIENT_SES *ptr = router->connections; - - while (ptr != NULL && ptr->next != router_cli_ses) { - ptr = ptr->next; - } - - if (ptr != NULL) { - ptr->next = router_cli_ses->next; - } - } - spinlock_release(&router->lock); - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [freeSession] Unlinked router_client_session %p from " - "router %p and from server on port %d. Connections : %d. ", - pthread_self(), - router_cli_ses, - router, - router_cli_ses->backend->server->port, - prev_val-1))); - - free(router_cli_ses); -} - - -/** - * Close a session with the router, this is the mechanism - * by which a router may cleanup data structure etc. - * - * @param instance The router instance data - * @param router_session The session being closed - */ -static void -closeSession(ROUTER *instance, void *router_session) -{ -ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; -DCB* backend_dcb; - - CHK_CLIENT_RSES(router_cli_ses); - /** - * Lock router client session for secure read and update. - */ - if (rses_begin_locked_router_action(router_cli_ses)) - { - /* decrease server current connection counter */ - atomic_add(&router_cli_ses->backend->server->stats.n_current, -1); - - backend_dcb = router_cli_ses->backend_dcb; - router_cli_ses->backend_dcb = NULL; - router_cli_ses->rses_closed = true; - /** Unlock */ - rses_end_locked_router_action(router_cli_ses); - - /** - * Close the backend server connection - */ - if (backend_dcb != NULL) { - CHK_DCB(backend_dcb); - dcb_close(backend_dcb); - } - } -} - -/** - * We have data from the client, we must route it to the backend. - * This is simply a case of sending it to the connection that was - * chosen when we started the client session. - * - * @param instance The router instance - * @param router_session The router session returned from the newSession call - * @param queue The queue of data buffers to route - * @return if succeed 1, otherwise 0 - */ -static int -routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) -{ - ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; - ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - uint8_t *payload = GWBUF_DATA(queue); - int mysql_command; - int rc; - DCB* backend_dcb; - bool rses_is_closed; - - inst->stats.n_queries++; - - /** Dirty read for quick check if router is closed. */ - if (router_cli_ses->rses_closed) - { - rses_is_closed = true; - } - else - { - /** - * Lock router client session for secure read of DCBs - */ - rses_is_closed = !(rses_begin_locked_router_action(router_cli_ses)); - } - - if (!rses_is_closed) - { - backend_dcb = router_cli_ses->backend_dcb; - /** unlock */ - rses_end_locked_router_action(router_cli_ses); - } - - if (rses_is_closed || backend_dcb == NULL) - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Error : Failed to route MySQL command %d to backend " - "server.", - mysql_command))); - rc = 0; - goto return_rc; - } - - - rc = backend_dcb->func.write(backend_dcb, queue); - -return_rc: - return rc; -} - -/** - * Display router diagnostics - * - * @param instance Instance of the router - * @param dcb DCB to send diagnostics to - */ -static void -diagnostics(ROUTER *router, DCB *dcb) -{ -ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router; -ROUTER_CLIENT_SES *session; -int i = 0; -BACKEND *backend; -char *weightby; - - spinlock_acquire(&router_inst->lock); - session = router_inst->connections; - while (session) - { - i++; - session = session->next; - } - spinlock_release(&router_inst->lock); - - dcb_printf(dcb, "\tNumber of router sessions: %d\n", - router_inst->stats.n_sessions); - dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", i); - dcb_printf(dcb, "\tNumber of queries forwarded: %d\n", - router_inst->stats.n_queries); - if ((weightby = serviceGetWeightingParameter(router_inst->service)) - != NULL) - { - dcb_printf(dcb, "\tConnection distribution based on %s " - "server parameter.\n", - weightby); - dcb_printf(dcb, - "\t\tServer Target %% Connections\n"); - for (i = 0; router_inst->servers[i]; i++) - { - backend = router_inst->servers[i]; - dcb_printf(dcb, "\t\t%-20s %3.1f%% %d\n", - backend->server->unique_name, - (float)backend->weight / 10, - backend->current_connection_count); - } - - } -} - -/** - * Client Reply routine - * - * The routine will reply to client data from backend server - * - * @param instance The router instance - * @param router_session The router session - * @param backend_dcb The backend DCB - * @param queue The GWBUF with reply data - */ -static void -clientReply( - ROUTER *instance, - void *router_session, - GWBUF *queue, - DCB *backend_dcb) -{ - DCB *client ; - - client = backend_dcb->session->client; - - ss_dassert(client != NULL); - - SESSION_ROUTE_REPLY(backend_dcb->session, queue); -} - -/** - * Error Handler routine - * - * The routine will handle errors that occurred in backend writes. - * - * @param instance The router instance - * @param router_session The router session - * @param message The error message to reply - * @param backend_dcb The backend DCB - * @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION - * - */ -static void handleError( - ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, - error_action_t action, - bool *succp) - -{ - DCB *client_dcb; - SESSION *session = backend_dcb->session; - session_state_t sesstate; - - /** Reset error handle flag from a given DCB */ - if (action == ERRACT_RESET) - { - backend_dcb->dcb_errhandle_called = false; - return; - } - - /** Don't handle same error twice on same DCB */ - if (backend_dcb->dcb_errhandle_called) - { - /** we optimistically assume that previous call succeed */ - *succp = true; - return; - } - else - { - backend_dcb->dcb_errhandle_called = true; - } - spinlock_acquire(&session->ses_lock); - sesstate = session->state; - client_dcb = session->client; - - if (sesstate == SESSION_STATE_ROUTER_READY) - { - CHK_DCB(client_dcb); - spinlock_release(&session->ses_lock); - client_dcb->func.write(client_dcb, gwbuf_clone(errbuf)); - } - else - { - spinlock_release(&session->ses_lock); - } - - /** false because connection is not available anymore */ - *succp = false; -} - -/** to be inline'd */ -/** - * @node 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. - * - * - * @details (write detailed description here) - * - */ -static bool rses_begin_locked_router_action( - ROUTER_CLIENT_SES* rses) -{ - bool succp = 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 */ -/** - * @node Releases router client session lock. - * - * Parameters: - * @param rses - - * - * - * @return void - * - * - * @details (write detailed description here) - * - */ -static void rses_end_locked_router_action( - ROUTER_CLIENT_SES* rses) -{ - CHK_CLIENT_RSES(rses); - spinlock_release(&rses->rses_lock); -} - - -static uint8_t getCapabilities( - ROUTER* inst, - void* router_session) -{ - return 0; -} - -/******************************** - * This routine returns the root master server from MySQL replication tree - * Get the root Master rule: - * - * find server with the lowest replication depth level - * and the SERVER_MASTER bitval - * Servers are checked even if they are in 'maintenance' - * - * @param servers The list of servers - * @return The Master found - * - */ - -static BACKEND *get_root_master(BACKEND **servers) { - int i = 0; - BACKEND *master_host = NULL; - - for (i = 0; servers[i]; i++) { - if (servers[i] && (servers[i]->server->status & (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) { - if (master_host && servers[i]->server->depth < master_host->server->depth) { - master_host = servers[i]; - } else { - if (master_host == NULL) { - master_host = servers[i]; - } - } - } - } - return master_host; -} - -static int handle_state_switch(DCB* dcb,DCB_REASON reason, void * routersession) -{ - ss_dassert(dcb != NULL); - SESSION* session = dcb->session; - ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*)routersession; - SERVICE* service = session->service; - ROUTER* router = (ROUTER *)service->router; - - switch(reason) - { - case DCB_REASON_CLOSE: - dcb->func.close(dcb); - break; - case DCB_REASON_DRAINED: - /** Do we need to do anything? */ - break; - case DCB_REASON_HIGH_WATER: - /** Do we need to do anything? */ - break; - case DCB_REASON_LOW_WATER: - /** Do we need to do anything? */ - break; - case DCB_REASON_ERROR: - dcb->func.error(dcb); - break; - case DCB_REASON_HUP: - dcb->func.hangup(dcb); - break; - case DCB_REASON_NOT_RESPONDING: - dcb->func.hangup(dcb); - break; - default: - break; - } - - return 0; -} From a73df62ebda6205c830102da131016fd13d44acd Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 18 May 2015 18:34:43 +0300 Subject: [PATCH 22/32] Renamed mon_exec files to externcmd. --- server/core/CMakeLists.txt | 4 ++-- server/core/{mon_exec.c => externcmd.c} | 2 +- server/include/{mon_exec.h => externcmd.h} | 4 ++-- server/modules/monitor/galeramon.h | 2 +- server/modules/monitor/mmmon.h | 2 +- server/modules/monitor/mysqlmon.h | 2 +- server/modules/monitor/ndbclustermon.h | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename server/core/{mon_exec.c => externcmd.c} (99%) rename server/include/{mon_exec.h => externcmd.h} (91%) diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index e60734237..88f6a9c22 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_TESTS OR BUILD_TOOLS) - add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c mon_exec.c) + add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c externcmd.c) if(WITH_JEMALLOC) target_link_libraries(fullcore ${JEMALLOC_LIBRARIES}) elseif(WITH_TCMALLOC) @@ -12,7 +12,7 @@ add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c monitor.c adminusers.c secrets.c filter.c modutil.c hint.c - housekeeper.c memlog.c resultset.c mon_exec.c) + housekeeper.c memlog.c resultset.c externcmd.c) if(WITH_JEMALLOC) target_link_libraries(maxscale ${JEMALLOC_LIBRARIES}) diff --git a/server/core/mon_exec.c b/server/core/externcmd.c similarity index 99% rename from server/core/mon_exec.c rename to server/core/externcmd.c index 01640bcd6..09c5727c4 100644 --- a/server/core/mon_exec.c +++ b/server/core/externcmd.c @@ -1,4 +1,4 @@ -#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; diff --git a/server/include/mon_exec.h b/server/include/externcmd.h similarity index 91% rename from server/include/mon_exec.h rename to server/include/externcmd.h index 25390bb82..1ab44f908 100644 --- a/server/include/mon_exec.h +++ b/server/include/externcmd.h @@ -1,5 +1,5 @@ -#ifndef MON_EXEC_HG -#define MON_EXEC_HG +#ifndef _EXTERN_CMD_HG +#define _EXTERN_CMD_HG #include #include diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h index 02d8f08c7..7838f05a1 100644 --- a/server/modules/monitor/galeramon.h +++ b/server/modules/monitor/galeramon.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/server/modules/monitor/mmmon.h b/server/modules/monitor/mmmon.h index e2532d407..8a9edfb56 100644 --- a/server/modules/monitor/mmmon.h +++ b/server/modules/monitor/mmmon.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include /** * The handle for an instance of a Multi-Master Monitor module diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 574ae7c88..0510a13cf 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include /** * @file mysqlmon.h - The MySQL monitor functionality within the gateway * diff --git a/server/modules/monitor/ndbclustermon.h b/server/modules/monitor/ndbclustermon.h index 081f5e3da..56b961c45 100644 --- a/server/modules/monitor/ndbclustermon.h +++ b/server/modules/monitor/ndbclustermon.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include /** * @file ndbclustermon.h - The NDB Cluster monitor From d8f055858f735bd7636d5a73c0b5aec0ca4a0fb7 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 14:22:54 +0300 Subject: [PATCH 23/32] Removed plainroute from the CMakeLists.txt. --- server/modules/routing/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/modules/routing/CMakeLists.txt b/server/modules/routing/CMakeLists.txt index 35d56fe1c..02535b7b9 100644 --- a/server/modules/routing/CMakeLists.txt +++ b/server/modules/routing/CMakeLists.txt @@ -9,10 +9,6 @@ add_library(readconnroute SHARED readconnroute.c) target_link_libraries(readconnroute log_manager utils) install(TARGETS readconnroute DESTINATION modules) -add_library(plainroute SHARED plainroute.c) -target_link_libraries(plainroute log_manager utils) -install(TARGETS plainroute DESTINATION modules) - add_library(debugcli SHARED debugcli.c debugcmd.c) target_link_libraries(debugcli log_manager utils) install(TARGETS debugcli DESTINATION modules) From 1403b3dfeb0fb90f1255f0a15c1b66a0a4a7438e Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 14:40:24 +0300 Subject: [PATCH 24/32] Fixed old header names form monitor_common.h --- server/modules/monitor/monitor_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h index e4cd864d1..bd4acc956 100644 --- a/server/modules/monitor/monitor_common.h +++ b/server/modules/monitor/monitor_common.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include /** * @file monitor_common.h - The generic monitor structures all monitors use * From c4f51c0b8c576bdf750b93635227485aa99e3fee Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 15:42:11 +0300 Subject: [PATCH 25/32] Fixed a possible bug with multiple monitors monitoring the same servers. --- server/modules/monitor/galeramon.c | 56 ++++++++++++++++++++++---- server/modules/monitor/mmmon.c | 48 ++++++++++++++++++---- server/modules/monitor/mysql_mon.c | 49 ++++++++++++++++++---- server/modules/monitor/ndbclustermon.c | 54 +++++++++++++++++++++---- 4 files changed, 177 insertions(+), 30 deletions(-) diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index eb75c2160..a46df8f77 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -64,6 +64,7 @@ static void diagnostics(DCB *, void *); static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *); static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *, MONITOR_SERVERS *, int); static void disableMasterFailback(void *, int); +bool isGaleraEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, @@ -570,15 +571,19 @@ monitor_event_t evtype; { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr)) { - skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", - ptr->server->unique_name, - ptr->server->name,ptr->server->port, - mon_get_event_name(ptr)); - if(handle->script && handle->events[evtype]) + evtype = mon_get_event_type(ptr); + if(isGaleraEvent(evtype)) { - monitor_launch_script(mon,ptr,handle->script); + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } } } ptr = ptr->next; @@ -687,3 +692,40 @@ availableWhenDonor(void *arg, int disable) GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->availableWhenDonor, &disable, sizeof(int)); } + +static monitor_event_t galera_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + SYNCED_DOWN_EVENT, + SYNCED_UP_EVENT, + DONOR_DOWN_EVENT, + DONOR_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_SYNCED_EVENT, + LOST_DONOR_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_SYNCED_EVENT, + NEW_DONOR_EVENT, + MAX_MONITOR_EVENT +}; +/** + * Check if the Galera monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isGaleraEvent(monitor_event_t event) +{ + int i; + for(i = 0;galera_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == galera_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/mmmon.c b/server/modules/monitor/mmmon.c index 8188c40c9..0e54d5299 100644 --- a/server/modules/monitor/mmmon.c +++ b/server/modules/monitor/mmmon.c @@ -594,16 +594,19 @@ size_t nrounds = 0; monitor_event_t evtype; while(ptr) { - /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr)) { - skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", - ptr->server->unique_name, - ptr->server->name,ptr->server->port, - mon_get_event_name(ptr)); - if(handle->script && handle->events[evtype]) + evtype = mon_get_event_type(ptr); + if(isMySQLEvent(evtype)) { - monitor_launch_script(mon,ptr,handle->script); + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } } } ptr = ptr->next; @@ -678,3 +681,32 @@ MONITOR_SERVERS *ptr; } } + +static monitor_event_t mysql_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + MAX_MONITOR_EVENT +}; +/** + * Check if the MM monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isMySQLEvent(monitor_event_t event) +{ + int i; + for(i = 0;mysql_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == mysql_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 3a6d22391..75f7027c4 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -80,7 +80,7 @@ static MONITOR_SERVERS *get_replication_tree(MONITOR *, int); static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *); static int add_slave_to_master(long *, int, long); - +bool isMySQLEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, @@ -693,15 +693,19 @@ int log_no_master = 1; while(ptr) { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr)) { - skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", - ptr->server->unique_name, - ptr->server->name,ptr->server->port, - mon_get_event_name(ptr)); - if(handle->script && handle->events[evtype]) + evtype = mon_get_event_type(ptr); + if(isMySQLEvent(evtype)) { - monitor_launch_script(mon,ptr,handle->script); + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } } } ptr = ptr->next; @@ -1193,3 +1197,32 @@ static int add_slave_to_master(long *slaves_list, int list_size, long node_id) { } return 0; } + +static monitor_event_t mysql_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + MAX_MONITOR_EVENT +}; +/** + * Check if the MySQL monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isMySQLEvent(monitor_event_t event) +{ + int i; + for(i = 0;mysql_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == mysql_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/ndbclustermon.c b/server/modules/monitor/ndbclustermon.c index ad41ab1e8..88444aaaf 100644 --- a/server/modules/monitor/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -52,6 +52,7 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); static void diagnostics(DCB *, void *); +bool isNdbEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, @@ -402,18 +403,57 @@ size_t nrounds = 0; while(ptr) { /** Execute monitor script if a server state has changed */ - if(mon_status_changed(ptr) && (evtype = mon_get_event_type(ptr)) != UNDEFINED_MONITOR_EVENT) + if(mon_status_changed(ptr)) { - skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", - ptr->server->unique_name, - ptr->server->name,ptr->server->port, - mon_get_event_name(ptr)); - if(handle->script && handle->events[evtype]) + evtype = mon_get_event_type(ptr); + if(isNdbEvent(evtype)) { - monitor_launch_script(mon,ptr,handle->script); + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } } } ptr = ptr->next; } } } + + +static monitor_event_t ndb_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + NDB_UP_EVENT, + NDB_DOWN_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_NDB_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_NDB_EVENT, + MAX_MONITOR_EVENT +}; + +/** + * Check if the event type is one the ndbcustermonitor is interested in. + * @param event Event to check + * @return True if the event is monitored, false if it is not + */ +bool isNdbEvent(monitor_event_t event) +{ + int i; + for(i = 0;ndb_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == ndb_events[i]) + return true; + } + return false; +} \ No newline at end of file From 7c7b322e1b54bcd1d5a27a2c904d93d1b84bc965 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 16:20:04 +0300 Subject: [PATCH 26/32] Added missing default monitor interval. --- server/core/monitor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/monitor.c b/server/core/monitor.c index 9d945572a..1013cf01b 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -85,6 +85,7 @@ MONITOR *mon; mon->read_timeout = DEFAULT_READ_TIMEOUT; mon->write_timeout = DEFAULT_WRITE_TIMEOUT; mon->connect_timeout = DEFAULT_CONNECT_TIMEOUT; + mon->interval = MONITOR_INTERVAL; spinlock_init(&mon->lock); spinlock_acquire(&monLock); mon->next = allMonitors; From a1270ef4475cabb51f31ae1f0091dd1dda2744d3 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 16:30:01 +0300 Subject: [PATCH 27/32] Added new monitor parameters to config.c --- server/core/config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/core/config.c b/server/core/config.c index 44de91b47..60c99bf87 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -1932,6 +1932,8 @@ static char *monitor_params[] = "servers", "user", "passwd", + "script", + "events", "monitor_interval", "detect_replication_lag", "detect_stale_master", From 8f20d23d4c99599e0f6c69626473982de3415125 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 19 May 2015 19:20:59 +0300 Subject: [PATCH 28/32] Added missing function prototype to mmmon.c --- server/modules/monitor/mmmon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/modules/monitor/mmmon.c b/server/modules/monitor/mmmon.c index 0e54d5299..bd1c6bb85 100644 --- a/server/modules/monitor/mmmon.c +++ b/server/modules/monitor/mmmon.c @@ -53,6 +53,7 @@ static void stopMonitor(void *); static void diagnostics(DCB *, void *); static void detectStaleMaster(void *, int); static MONITOR_SERVERS *get_current_master(MONITOR *); +bool isMySQLEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, @@ -146,7 +147,7 @@ startMonitor(void *arg,void* opt) } else if(!strcmp(params->name,"events")) { - mon_parse_event_string(&handle->events,sizeof(handle->events),params->value); + mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value); have_events = true; } params = params->next; From b815a796289d11ba3bfa7df9172d3477ea6e347c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 20 May 2015 13:06:08 +0300 Subject: [PATCH 29/32] Fixed failing execvp calls leaving a process running. --- server/core/externcmd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/externcmd.c b/server/core/externcmd.c index 09c5727c4..cc7989fc6 100644 --- a/server/core/externcmd.c +++ b/server/core/externcmd.c @@ -138,6 +138,7 @@ int externcmd_execute(EXTERNCMD* cmd) { /** Child process, execute command */ execvp(cmd->parameters[0],cmd->parameters); + exit(1); } else { From 7dbc0211496dd6e217507293a2579811c7b385c5 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 20 May 2015 16:16:49 +0300 Subject: [PATCH 30/32] Added more logging when a monitor script cannot be executed. --- server/core/externcmd.c | 19 ++++++++++++++++++- server/modules/monitor/galeramon.c | 12 +++++++++++- server/modules/monitor/mmmon.c | 13 +++++++++++-- server/modules/monitor/monitor_common.c | 6 +++++- server/modules/monitor/mysql_mon.c | 12 +++++++++++- server/modules/monitor/ndbclustermon.c | 12 +++++++++++- 6 files changed, 67 insertions(+), 7 deletions(-) diff --git a/server/core/externcmd.c b/server/core/externcmd.c index cc7989fc6..93579faef 100644 --- a/server/core/externcmd.c +++ b/server/core/externcmd.c @@ -97,6 +97,23 @@ EXTERNCMD* externcmd_allocate(char* argstr) free(cmd); return NULL; } + if(access(cmd->parameters[0],F_OK) != 0) + { + skygw_log_write(LE, + "Error: Cannot find file: %s", + cmd->parameters[0]); + externcmd_free(cmd); + return NULL; + } + + if(access(cmd->parameters[0],X_OK) != 0) + { + skygw_log_write(LE, + "Error: Cannot execute file: %s", + cmd->parameters[0]); + externcmd_free(cmd); + return NULL; + } } return cmd; } @@ -138,7 +155,7 @@ int externcmd_execute(EXTERNCMD* cmd) { /** Child process, execute command */ execvp(cmd->parameters[0],cmd->parameters); - exit(1); + _exit(1); } else { diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index a46df8f77..404448d82 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -156,7 +156,17 @@ startMonitor(void *arg,void* opt) { if(handle->script) free(handle->script); - handle->script = strdup(params->value); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + handle->script = NULL; + } } else if(!strcmp(params->name,"events")) { diff --git a/server/modules/monitor/mmmon.c b/server/modules/monitor/mmmon.c index bd1c6bb85..4ca8d6048 100644 --- a/server/modules/monitor/mmmon.c +++ b/server/modules/monitor/mmmon.c @@ -142,8 +142,17 @@ startMonitor(void *arg,void* opt) { free(handle->script); } - - handle->script = strdup(params->value); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + handle->script = NULL; + } } else if(!strcmp(params->name,"events")) { diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c index 6f9be583b..fb59eadbd 100644 --- a/server/modules/monitor/monitor_common.c +++ b/server/modules/monitor/monitor_common.c @@ -303,7 +303,11 @@ void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script) ptr->server->unique_name); mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1); - cmd = externcmd_allocate(argstr); + if((cmd = externcmd_allocate(argstr)) == NULL) + { + skygw_log_write(LE,"Failed to execute script: %s",script); + return; + } if(externcmd_execute(cmd)) { diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 75f7027c4..1e7fde4e6 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -170,7 +170,17 @@ startMonitor(void *arg, void* opt) { if(handle->script) free(handle->script); - handle->script = strdup(params->value); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + handle->script = NULL; + } } else if(!strcmp(params->name,"events")) { diff --git a/server/modules/monitor/ndbclustermon.c b/server/modules/monitor/ndbclustermon.c index 88444aaaf..4488786af 100644 --- a/server/modules/monitor/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -132,7 +132,17 @@ startMonitor(void *arg,void* opt) { if(handle->script) free(handle->script); - handle->script = strdup(params->value); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + handle->script = NULL; + } } else if(!strcmp(params->name,"events")) { From 91bc7b2792e1df38f3efcb4fd36547c055eb1497 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 21 May 2015 11:41:11 +0300 Subject: [PATCH 31/32] Added missing spinlock calls to all monitors. --- server/core/monitor.c | 2 ++ server/modules/monitor/galeramon.c | 8 ++++++-- server/modules/monitor/mmmon.c | 9 +++++++-- server/modules/monitor/mysql_mon.c | 12 +++++++++--- server/modules/monitor/ndbclustermon.c | 6 +++++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index 1013cf01b..30fdc756c 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -133,8 +133,10 @@ MONITOR *ptr; void monitorStart(MONITOR *monitor, void* params) { + spinlock_acquire(&monitor->lock); monitor->handle = (*monitor->module->startMonitor)(monitor,params); monitor->state = MONITOR_STATE_RUNNING; + spinlock_release(&monitor->lock); } /** diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index 404448d82..f1246afa7 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -405,15 +405,19 @@ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*)arg; -GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; +GALERA_MONITOR *handle; MONITOR_SERVERS *ptr; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; -int master_stickiness = handle->disableMasterFailback; +int master_stickiness; int is_cluster=0; int log_no_members = 1; monitor_event_t evtype; + spinlock_acquire(&mon->lock); + handle = (GALERA_MONITOR *)mon->handle; + spinlock_release(&mon->lock); + master_stickiness = handle->disableMasterFailback; if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( diff --git a/server/modules/monitor/mmmon.c b/server/modules/monitor/mmmon.c index 4ca8d6048..f56c77022 100644 --- a/server/modules/monitor/mmmon.c +++ b/server/modules/monitor/mmmon.c @@ -494,12 +494,17 @@ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*)arg; -MM_MONITOR *handle = (MM_MONITOR *)mon->handle; +MM_MONITOR *handle; MONITOR_SERVERS *ptr; -int detect_stale_master = handle->detectStaleMaster; +int detect_stale_master; MONITOR_SERVERS *root_master; size_t nrounds = 0; +spinlock_acquire(&mon->lock); +handle = (MM_MONITOR *)mon->handle; +spinlock_release(&mon->lock); +detect_stale_master = handle->detectStaleMaster; + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 1e7fde4e6..3295d23e9 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -507,15 +507,21 @@ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*) arg; -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; +MYSQL_MONITOR *handle; MONITOR_SERVERS *ptr; -int replication_heartbeat = handle->replicationHeartbeat; -int detect_stale_master = handle->detectStaleMaster; +int replication_heartbeat; +int detect_stale_master; int num_servers=0; MONITOR_SERVERS *root_master = NULL; size_t nrounds = 0; int log_no_master = 1; +spinlock_acquire(&mon->lock); +handle = (MYSQL_MONITOR *)mon->handle; +spinlock_release(&mon->lock); +replication_heartbeat = handle->replicationHeartbeat; +detect_stale_master = handle->detectStaleMaster; + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( diff --git a/server/modules/monitor/ndbclustermon.c b/server/modules/monitor/ndbclustermon.c index 4488786af..a22daecbc 100644 --- a/server/modules/monitor/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -346,10 +346,14 @@ static void monitorMain(void *arg) { MONITOR* mon = arg; -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; +MYSQL_MONITOR *handle; MONITOR_SERVERS *ptr; size_t nrounds = 0; +spinlock_acquire(&mon->lock); +handle = (MYSQL_MONITOR *)mon->handle; +spinlock_release(&mon->lock); + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( From 3301ffc76dd6457ced6cb3e089856de18770f4c0 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 21 May 2015 17:40:33 +0300 Subject: [PATCH 32/32] Added different log message when the script is missing for a monitor. --- server/modules/monitor/galeramon.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/modules/monitor/galeramon.c b/server/modules/monitor/galeramon.c index f1246afa7..6a842b8c1 100644 --- a/server/modules/monitor/galeramon.c +++ b/server/modules/monitor/galeramon.c @@ -156,21 +156,31 @@ startMonitor(void *arg,void* opt) { if(handle->script) free(handle->script); + if(access(params->value,X_OK) == 0) { handle->script = strdup(params->value); } else { + if(access(params->value,F_OK) == 0) + { skygw_log_write(LE, "Error: The file cannot be executed: %s", params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be found: %s", + params->value); + } handle->script = NULL; } } else if(!strcmp(params->name,"events")) { - mon_parse_event_string(&handle->events,sizeof(handle->events),params->value); + mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value); have_events = true; } params = params->next;