diff --git a/Documentation/Debug And Diagnostic Support.pdf b/Documentation/Debug And Diagnostic Support.pdf index 2b426b8e3..2b848d77b 100644 Binary files a/Documentation/Debug And Diagnostic Support.pdf and b/Documentation/Debug And Diagnostic Support.pdf differ diff --git a/README b/README index 1e495b1b2..3750fb332 100644 --- a/README +++ b/README @@ -174,4 +174,47 @@ home directory. The -f flag can be used to set the name and the location of the configuration file. Without path expression the file is read from $MAXSCALE_HOME/etc directory. + +\section Testing Running MaxScale testsuite + +To run "make testall" you need to have three mysqld servers running +on localhost: + +* a master on port 3000, with server_id=2 +* a slave on port 3001, server_id doesn't matter +* a slave on port 2002, server_id doesn't matter + +On the master full privileges on the databases "test" and "FOO" +are needed, on the saves SELECT permissions on test.* should +be sufficient. + +You can use different port numbers but you'll have to change +the server settings at the end of server/test/MaxScale_test.cnf then. + +You also always need to edit the top level test.inc file, +this file contains appropriate default values for the +test setup as described above, these are only given as +comments though ... + +You can then run the full testsuite using + + make testall + +in the top level directory. After testing has finished you +can find a full testlog in test/test_maxscale.log + +You may also find additional information in the following +component specific logs: + + utils/test/testutils.log + query_classifier/test/testqclass.log + server/test/MaxScale/log/skygw_msg1.log + server/test/MaxScale/log/skygw_err1.log + server/test/MaxScale/log/skygw_trace1.log + server/test/MaxScale/log/skygw_debug1.log + server/test/testserver.log + server/core/test/testhash.log + test/test_maxscale.log + + */ diff --git a/maxscale.spec b/maxscale.spec index c8d12ba2f..9ab55ef41 100644 --- a/maxscale.spec +++ b/maxscale.spec @@ -25,10 +25,10 @@ MaxScale %build ln -s /lib64/libaio.so.1 /lib64/libaio.so -make ROOT_PATH=`pwd` HOME="" clean -make ROOT_PATH=`pwd` HOME="" depend -make ROOT_PATH=`pwd` HOME="" -make DEST=`pwd`/binaries ROOT_PATH=`pwd` HOME="" ERRMSG="/usr/share/mysql/english" install +make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 clean +make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 depend +make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 +make DEST=`pwd`/binaries ROOT_PATH=`pwd` HOME="" ERRMSG="/usr/share/mysql/english" $DEBUG_FLAG1 $DEBUG_FLAG2 install %post ln -s /lib64/libaio.so.1 /lib64/libaio.so diff --git a/server/core/Makefile b/server/core/Makefile index 3f862a475..bd29fbae8 100644 --- a/server/core/Makefile +++ b/server/core/Makefile @@ -41,7 +41,7 @@ UTILSPATH := $(ROOT_PATH)/utils CC=cc -CFLAGS=-c -I/usr/include -I../include -I../inih \ +CFLAGS=-c -I/usr/include -I../include -I../modules/include -I../inih \ $(MYSQL_HEADERS) \ -I$(LOGPATH) -I$(UTILSPATH) \ -Wall -g @@ -59,7 +59,7 @@ SRCS= atomic.c buffer.c spinlock.c gateway.c \ monitor.c adminusers.c secrets.c HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.h \ - ../include/gw.h ../include/mysql_protocol.h \ + ../include/gw.h ../modules/include/mysql_client_server_protocol.h \ ../include/session.h ../include/spinlock.h ../include/thread.h \ ../include/modules.h ../include/poll.h ../include/config.h \ ../include/users.h ../include/hashtable.h ../include/gwbitmask.h \ diff --git a/server/core/buffer.c b/server/core/buffer.c index b21cf216c..11fb5b556 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -284,7 +284,11 @@ unsigned int gwbuf_length(GWBUF *head) { int rval = 0; - CHK_GWBUF(head); + + if (head) + { + CHK_GWBUF(head); + } while (head) { rval += GWBUF_LENGTH(head); diff --git a/server/core/config.c b/server/core/config.c index d61200adb..be38314ab 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -29,7 +29,8 @@ * 06/02/14 Massimiliano Pinto Added support for enable/disable root user in services * 14/02/14 Massimiliano Pinto Added enable_root_user in the service_params list * 11/03/14 Massimiliano Pinto Added Unix socket support - * 11/05/14 Massimiliano Pinto Added version_string support to service + * 11/05/14 Massimiliano Pinto Added version_string support to service + * 19/05/14 Mark Riddoch Added unique names from section headers * * @endverbatim */ @@ -129,7 +130,6 @@ int rval; if (ptr) { *ptr = '\0'; } - } mysql_close(conn); } @@ -165,7 +165,6 @@ int rval; if (!config_file) return 0; - if (gateway.version_string) free(gateway.version_string); @@ -227,7 +226,7 @@ int error_count = 0; config_get_value(obj->parameters, "passwd"); char *enable_root_user = config_get_value(obj->parameters, "enable_root_user"); - + char *version_string = config_get_value(obj->parameters, "version_string"); if (obj->element == NULL) /*< if module load failed */ @@ -242,7 +241,7 @@ int error_count = 0; obj = obj->next; continue; /*< process next obj */ } - + if (version_string) { ((SERVICE *)(obj->element))->version_string = strdup(version_string); } else { @@ -334,6 +333,7 @@ int error_count = 0; obj->element = server_alloc(address, protocol, atoi(port)); + server_set_unique_name(obj->element, obj->object); } else { @@ -802,9 +802,10 @@ SERVER *server; version_string = config_get_value(obj->parameters, "version_string"); if (version_string) { - if (service->version_string) + if (service->version_string) { free(service->version_string); - service->version_string = strdup(version_string); + } + service->version_string = strdup(version_string); } if (user && auth) { diff --git a/server/core/dbusers.c b/server/core/dbusers.c index b89f831de..41dda5a92 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -42,6 +42,7 @@ #include #include #include +#include #define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')" #define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password) AS userdata FROM mysql.user WHERE user IS NOT NULL AND user <> ''" @@ -183,6 +184,8 @@ getUsers(SERVICE *service, struct users *users) } serviceGetUser(service, &service_user, &service_passwd); + if (service_user == NULL || service_passwd == NULL) + return -1; /** multi-thread environment requires that thread init succeeds. */ if (mysql_thread_init()) { diff --git a/server/core/dcb.c b/server/core/dcb.c index 9850fb710..16ca964ac 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -47,6 +47,7 @@ * error and 0 bytes to read. * This fixes a bug with many reads from * backend + * 07/05/2014 Mark Riddoch Addition of callback mechanism * * @endverbatim */ @@ -67,6 +68,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; @@ -80,6 +82,7 @@ static bool dcb_set_state_nomutex( DCB* dcb, const dcb_state_t new_state, dcb_state_t* old_state); +static void dcb_call_callback(DCB *dcb, DCB_REASON reason); DCB* dcb_get_zombies(void) { @@ -94,8 +97,8 @@ DCB* dcb_get_zombies(void) * * @return A newly allocated DCB or NULL if non could be allocated. */ -DCB * dcb_alloc( - dcb_role_t role) +DCB * +dcb_alloc(dcb_role_t role) { DCB *rval; @@ -118,11 +121,16 @@ DCB *rval; spinlock_init(&rval->writeqlock); spinlock_init(&rval->delayqlock); spinlock_init(&rval->authlock); + spinlock_init(&rval->cb_lock); rval->fd = -1; memset(&rval->stats, 0, sizeof(DCBSTATS)); // Zero the statistics rval->state = DCB_STATE_ALLOC; bitmask_init(&rval->memdata.bitmask); + rval->writeqlen = 0; + rval->high_water = 0; + rval->low_water = 0; rval->next = NULL; + rval->callbacks = NULL; spinlock_acquire(&dcbspin); if (allDCBs == NULL) @@ -248,6 +256,8 @@ dcb_add_to_zombieslist(DCB *dcb) static void dcb_final_free(DCB *dcb) { +DCB_CALLBACK *cb; + CHK_DCB(dcb); ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED, "dcb not in DCB_STATE_DISCONNECTED state."); @@ -307,6 +317,19 @@ dcb_final_free(DCB *dcb) GWBUF *queue = dcb->delayq; while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); } + if (dcb->dcb_readqueue) + { + GWBUF* queue = dcb->dcb_readqueue; + while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); + } + + spinlock_acquire(&dcb->cb_lock); + while ((cb = dcb->callbacks) != NULL) + { + dcb->callbacks = cb->next; + free(cb); + } + spinlock_release(&dcb->cb_lock); if (dcb->dcb_readqueue) { @@ -688,9 +711,11 @@ return_n: int dcb_write(DCB *dcb, GWBUF *queue) { - int w; - int saved_errno = 0; +int w, qlen; +int saved_errno = 0; +int below_water; + below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; ss_dassert(queue != NULL); /** @@ -734,6 +759,11 @@ dcb_write(DCB *dcb, GWBUF *queue) * the routine that drains the queue data, so we should * not have a race condition on the event. */ + if (queue) + qlen = gwbuf_length(queue); + else + qlen = 0; + atomic_add(&dcb->writeqlen, qlen); dcb->writeq = gwbuf_append(dcb->writeq, queue); dcb->stats.n_buffered++; LOGIF(LD, (skygw_log_write( @@ -846,6 +876,15 @@ dcb_write(DCB *dcb, GWBUF *queue) * for suspended write. */ dcb->writeq = queue; + if (queue) + { + qlen = gwbuf_length(queue); + } + else + { + qlen = 0; + } + atomic_add(&dcb->writeqlen, qlen); if (queue != NULL) { @@ -869,6 +908,13 @@ dcb_write(DCB *dcb, GWBUF *queue) return 0; } spinlock_release(&dcb->writeqlock); + + if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water) + { + atomic_add(&dcb->stats.n_high_water, 1); + dcb_call_callback(dcb, DCB_REASON_HIGH_WATER); + } + return 1; } @@ -883,9 +929,12 @@ dcb_write(DCB *dcb, GWBUF *queue) int dcb_drain_writeq(DCB *dcb) { -int n = 0; -int w; -int saved_errno = 0; +int n = 0; +int w; +int saved_errno = 0; +int above_water; + + above_water = (dcb->low_water && dcb->writeqlen > dcb->low_water) ? 1 : 0; spinlock_acquire(&dcb->writeqlock); if (dcb->writeq) @@ -946,6 +995,17 @@ int saved_errno = 0; } } spinlock_release(&dcb->writeqlock); + atomic_add(&dcb->writeqlen, -n); + /* The write queue has drained, potentially need to call a callback function */ + if (dcb->writeq == NULL) + dcb_call_callback(dcb, DCB_REASON_DRAINED); + if (above_water && dcb->writeqlen < dcb->low_water) + { + atomic_add(&dcb->stats.n_low_water, 1); + dcb_call_callback(dcb, DCB_REASON_LOW_WATER); + } + + return n; } @@ -988,6 +1048,8 @@ dcb_close(DCB *dcb) ss_dassert(dcb->state == DCB_STATE_NOPOLLING || dcb->state == DCB_STATE_ZOMBIE); + dcb_call_callback(dcb, DCB_REASON_CLOSE); + if (rc == 0) { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -1024,12 +1086,15 @@ printDCB(DCB *dcb) printf("\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); if (dcb->remote) printf("\tConnected to: %s\n", dcb->remote); - printf("\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); + if (dcb->writeq) + printf("\tQueued write data: %d\n",gwbuf_length(dcb->writeq)); printf("\tStatistics:\n"); printf("\t\tNo. of Reads: %d\n", dcb->stats.n_reads); printf("\t\tNo. of Writes: %d\n", dcb->stats.n_writes); printf("\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); printf("\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); + printf("\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); + printf("\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); } /** @@ -1076,6 +1141,8 @@ DCB *dcb; dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); + dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); + dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); dcb = dcb->next; } spinlock_release(&dcbspin); @@ -1095,12 +1162,15 @@ dprintDCB(DCB *pdcb, DCB *dcb) if (dcb->remote) dcb_printf(pdcb, "\tConnected to: %s\n", dcb->remote); dcb_printf(pdcb, "\tOwning Session: %d\n", dcb->session); - dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); + if (dcb->writeq) + dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); dcb_printf(pdcb, "\tStatistics:\n"); dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); + dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); + dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); } /** @@ -1437,4 +1507,163 @@ int gw_write( return w; } +/** + * Add a callback + * + * Duplicate registrations are not allowed, therefore an error will be returned if + * the specific function, reason and userdata triple are already registered. + * An error will also be returned if the is insufficient memeory available to + * create the registration. + * + * @param dcb The DCB to add the callback to + * @param reason The callback reason + * @param cb The callback function to call + * @param userdata User data to send in the call + * @return Non-zero (true) if the callback was added + */ +int +dcb_add_callback(DCB *dcb, DCB_REASON reason, int (*callback)(struct dcb *, DCB_REASON, void *), void *userdata) +{ +DCB_CALLBACK *cb, *ptr; +int rval = 1; + if ((ptr = (DCB_CALLBACK *)malloc(sizeof(DCB_CALLBACK))) == NULL) + { + return 0; + } + ptr->reason = reason; + ptr->cb = callback; + ptr->userdata = userdata; + ptr->next = NULL; + spinlock_acquire(&dcb->cb_lock); + cb = dcb->callbacks; + if (cb == NULL) + { + dcb->callbacks = ptr; + spinlock_release(&dcb->cb_lock); + } + else + { + while (cb) + { + if (cb->reason == reason && cb->cb == callback && + cb->userdata == userdata) + { + free(ptr); + spinlock_release(&dcb->cb_lock); + return 0; + } + if (cb->next == NULL) + cb->next = ptr; + cb = cb->next; + } + spinlock_release(&dcb->cb_lock); + } + return rval; +} + +/** + * Remove a callback from the callback list for the DCB + * + * Searches down the linked list to find the callback with a matching reason, function + * and userdata. + * + * @param dcb The DCB to add the callback to + * @param reason The callback reason + * @param cb The callback function to call + * @param userdata User data to send in the call + * @return Non-zero (true) if the callback was removed + */ +int +dcb_remove_callback(DCB *dcb, DCB_REASON reason, int (*callback)(struct dcb *, DCB_REASON), void *userdata) +{ +DCB_CALLBACK *cb, *pcb = NULL; +int rval = 0; + + spinlock_acquire(&dcb->cb_lock); + cb = dcb->callbacks; + if (cb == NULL) + { + rval = 0; + } + else + { + while (cb) + { + if (cb->reason == reason && cb->cb == callback + && cb->userdata == userdata) + { + if (pcb == NULL) + pcb->next = cb->next; + else + dcb->callbacks = cb->next; + spinlock_release(&dcb->cb_lock); + free(cb); + rval = 1; + break; + } + pcb = cb; + cb = cb->next; + } + } + if (!rval) + spinlock_release(&dcb->cb_lock); + return rval; +} + +/** + * Call the set of callbacks registered for a particular reason. + * + * @param dcb The DCB to call the callbacks regarding + * @param reason The reason that has triggered the call + */ +static void +dcb_call_callback(DCB *dcb, DCB_REASON reason) +{ +DCB_CALLBACK *cb, *nextcb; + + spinlock_acquire(&dcb->cb_lock); + cb = dcb->callbacks; + while (cb) + { + if (cb->reason == reason) + { + nextcb = cb->next; + spinlock_release(&dcb->cb_lock); + cb->cb(dcb, reason, cb->userdata); + spinlock_acquire(&dcb->cb_lock); + cb = nextcb; + } + else + cb = cb->next; + } + spinlock_release(&dcb->cb_lock); +} + +/** + * Check the passed DCB to ensure it is in the list of allDCBS + * + * @param DCB The DCB to check + * @return 1 if the DCB is in the list, otherwise 0 + */ +int +dcb_isvalid(DCB *dcb) +{ +DCB *ptr; +int rval = 0; + + spinlock_acquire(&dcbspin); + ptr = allDCBs; + while (ptr) + { + if (ptr == dcb) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&dcbspin); + + return rval; +} diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 28c95a3ae..ba5507018 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -106,6 +106,7 @@ MODULES *mod; return NULL; } } + if ((dlhandle = dlopen(fname, RTLD_NOW|RTLD_LOCAL)) == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -156,9 +157,10 @@ MODULES *mod; LOGIF(LM, (skygw_log_write_flush( LOGFILE_MESSAGE, - "Loaded module %s: %s.", + "Loaded module %s: %s from %s", module, - version))); + version, + fname))); register_module(module, type, dlhandle, version, modobj); } else diff --git a/server/core/monitor.c b/server/core/monitor.c index 6c028cad6..cee2f2d9e 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -197,3 +197,26 @@ MONITOR *ptr; } spinlock_release(&monLock); } + +/** + * Find a monitor by name + * + * @param name The name of the monitor + * @return Pointer to the monitor or NULL + */ +MONITOR * +monitor_find(char *name) +{ +MONITOR *ptr; + + spinlock_acquire(&monLock); + ptr = allMonitors; + while (ptr) + { + if (!strcmp(ptr->name, name)) + break; + ptr = ptr->next; + } + spinlock_release(&monLock); + return ptr; +} diff --git a/server/core/poll.c b/server/core/poll.c index f1c65ebea..0b23a6b05 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -27,6 +27,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; diff --git a/server/core/secrets.c b/server/core/secrets.c index a11b0276d..c4d1822f5 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -21,6 +21,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; /** diff --git a/server/core/server.c b/server/core/server.c index 4234ecdfe..204155f5e 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -24,7 +24,9 @@ * * Date Who Description * 18/06/13 Mark Riddoch Initial implementation + * 17/05/14 Mark Riddoch Addition of unique_name * 20/05/14 Massimiliano Pinto Addition of server_string + * 21/05/14 Massimiliano Pinto Addition of node_id * * @endverbatim */ @@ -68,7 +70,9 @@ SERVER *server; server->nextdb = NULL; server->monuser = NULL; server->monpw = NULL; + server->unique_name = NULL; server->server_string = NULL; + server->node_id = -1; spinlock_acquire(&server_spin); server->next = allServers; @@ -111,12 +115,51 @@ SERVER *ptr; /* Clean up session and free the memory */ free(server->name); free(server->protocol); + if (server->unique_name) + free(server->unique_name); if (server->server_string) free(server->server_string); free(server); return 1; } +/** + * Set a unique name for the server + * + * @param server The server to ste the name on + * @param name The unique name for the server + */ +void +server_set_unique_name(SERVER *server, char *name) +{ + server->unique_name = strdup(name); +} + +/** + * Find an existing server using the unique section name in + * configuration file + * + * @param servname The Server name or address + * @param port The server port + * @return The server or NULL if not found + */ +SERVER * +server_find_by_unique_name(char *name) +{ +SERVER *server; + + spinlock_acquire(&server_spin); + server = allServers; + while (server) + { + if (strcmp(server->unique_name, name) == 0) + break; + server = server->next; + } + spinlock_release(&server_spin); + return server; +} + /** * Find an existing server * @@ -194,7 +237,7 @@ char *stat; ptr = allServers; while (ptr) { - dcb_printf(dcb, "Server %p\n", ptr); + dcb_printf(dcb, "Server %p (%s)\n", ptr, ptr->unique_name); dcb_printf(dcb, "\tServer: %s\n", ptr->name); stat = server_status(ptr); dcb_printf(dcb, "\tStatus: %s\n", stat); @@ -203,6 +246,7 @@ char *stat; dcb_printf(dcb, "\tPort: %d\n", ptr->port); if (ptr->server_string) dcb_printf(dcb, "\tServer Version:\t\t%s\n", ptr->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", ptr->node_id); dcb_printf(dcb, "\tNumber of connections: %d\n", ptr->stats.n_connections); dcb_printf(dcb, "\tCurrent no. of conns: %d\n", ptr->stats.n_current); ptr = ptr->next; @@ -221,7 +265,7 @@ dprintServer(DCB *dcb, SERVER *server) { char *stat; - dcb_printf(dcb, "Server %p\n", server); + dcb_printf(dcb, "Server %p (%s)\n", server, server->unique_name); dcb_printf(dcb, "\tServer: %s\n", server->name); stat = server_status(server); dcb_printf(dcb, "\tStatus: %s\n", stat); @@ -230,6 +274,7 @@ char *stat; dcb_printf(dcb, "\tPort: %d\n", server->port); if (server->server_string) dcb_printf(dcb, "\tServer Version:\t\t%s\n", server->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", server->node_id); dcb_printf(dcb, "\tNumber of connections: %d\n", server->stats.n_connections); dcb_printf(dcb, "\tCurrent no. of conns: %d\n", server->stats.n_current); } diff --git a/server/core/service.c b/server/core/service.c index 6e3e89ef4..efc441173 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -29,6 +29,7 @@ * 25/02/14 Massimiliano Pinto Added: service refresh limit feature * 28/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services) * 07/05/14 Massimiliano Pinto Added: version_string initialized to NULL + * 23/05/14 Mark Riddoch Addition of service validation call * * @endverbatim */ @@ -121,6 +122,33 @@ SERVICE *service; return service; } +/** + * Check to see if a service pointer is valid + * + * @param service The poitner to check + * @return 1 if the service is in the list of all services + */ +int +service_isvalid(SERVICE *service) +{ +SERVICE *ptr; +int rval = 0; + + spinlock_acquire(&service_spin); + ptr = allServices; + while (ptr) + { + if (ptr == service) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&service_spin); + return rval; +} + /** * Start an individual port/protocol pair * @@ -184,7 +212,7 @@ GWPROTOCOL *funcs; if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); - + if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; @@ -650,7 +678,7 @@ SERVICE *ptr; /** * Print all services to a DCB * - * Designed to be called within a debugger session in order + * Designed to be called within a CLI command in order * to display all active services within the gateway */ void @@ -662,30 +690,42 @@ SERVICE *ptr; ptr = allServices; while (ptr) { - SERVER *server = ptr->databases; - dcb_printf(dcb, "Service %p\n", ptr); - dcb_printf(dcb, "\tService: %s\n", ptr->name); - dcb_printf(dcb, "\tRouter: %s (%p)\n", ptr->routerModule, - ptr->router); - if (ptr->router) - ptr->router->diagnostics(ptr->router_instance, dcb); - dcb_printf(dcb, "\tStarted: %s", - asctime(localtime(&ptr->stats.started))); - dcb_printf(dcb, "\tBackend databases\n"); - while (server) - { - dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->name, server->port, - server->protocol); - server = server->nextdb; - } - dcb_printf(dcb, "\tUsers data: %p\n", ptr->users); - dcb_printf(dcb, "\tTotal connections: %d\n", ptr->stats.n_sessions); - dcb_printf(dcb, "\tCurrently connected: %d\n", ptr->stats.n_current); + dprintService(dcb, ptr); ptr = ptr->next; } spinlock_release(&service_spin); } +/** + * Print details of a single service. + * + * @param dcb DCB to print data to + * @param service The service to print + */ +void dprintService(DCB *dcb, SERVICE *service) +{ +SERVER *server = service->databases; + + dcb_printf(dcb, "Service %p\n", service); + dcb_printf(dcb, "\tService: %s\n", service->name); + dcb_printf(dcb, "\tRouter: %s (%p)\n", service->routerModule, + service->router); + if (service->router) + service->router->diagnostics(service->router_instance, dcb); + dcb_printf(dcb, "\tStarted: %s", + asctime(localtime(&service->stats.started))); + dcb_printf(dcb, "\tBackend databases\n"); + while (server) + { + dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->name, server->port, + server->protocol); + server = server->nextdb; + } + dcb_printf(dcb, "\tUsers data: %p\n", service->users); + dcb_printf(dcb, "\tTotal connections: %d\n", service->stats.n_sessions); + dcb_printf(dcb, "\tCurrently connected: %d\n", service->stats.n_current); +} + /** * Update the definition of a service * diff --git a/server/core/session.c b/server/core/session.c index 959e26c7b..bd8188bcc 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -114,7 +114,7 @@ session_alloc(SERVICE *service, DCB *client_dcb) /* * Only create a router session if we are not the listening - * DCB. Creating a router session may create a connection to a + * DCB or an internal DCB. Creating a router session may create a connection to a * backend server, depending upon the router module implementation * and should be avoided for the listener session * @@ -122,7 +122,7 @@ session_alloc(SERVICE *service, DCB *client_dcb) * session, therefore it is important that the session lock is * relinquished beforethe router call. */ - if (client_dcb->state != DCB_STATE_LISTENING) + if (client_dcb->state != DCB_STATE_LISTENING && client_dcb->dcb_role != DCB_ROLE_INTERNAL) { session->router_session = service->router->newSession(service->router_instance, @@ -273,6 +273,34 @@ return_succp : return succp; } +/** + * Check to see if a session is valid, i.e. in the list of all sessions + * + * @param session Session to check + * @return 1 if the session is valid otherwise 0 + */ +int +session_isvalid(SESSION *session) +{ +SESSION *ptr; +int rval = 0; + + spinlock_acquire(&session_spin); + ptr = allSessions; + while (ptr) + { + if (ptr == session) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&session_spin); + + return rval; +} + /** * Print details of an individual session * diff --git a/server/include/dcb.h b/server/include/dcb.h index b56a8be5f..e90a64856 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -48,6 +48,8 @@ struct service; * 15/07/2013 Massimiliano Pinto Added session entry point * 16/07/2013 Massimiliano Pinto Added command type for dcb * 07/02/2014 Massimiliano Pinto Added ipv4 data struct into for dcb + * 07/05/2014 Mark Riddoch Addition of callback mechanism + * 08/05/2014 Mark Riddoch Addition of writeq high and low watermarks * * @endverbatim */ @@ -99,6 +101,8 @@ typedef struct dcbstats { int n_writes; /*< Number of writes on this descriptor */ int n_accepts; /*< Number of accepts on this descriptor */ int n_buffered; /*< Number of buffered writes */ + int n_high_water; /*< Number of crosses of high water mark */ + int n_low_water; /*< Number of crosses of low water mark */ } DCBSTATS; /** @@ -137,10 +141,35 @@ typedef enum { } dcb_state_t; typedef enum { - DCB_ROLE_SERVICE_LISTENER, /*< Receives initial connect requests from clients */ - DCB_ROLE_REQUEST_HANDLER /*< Serves dedicated client */ + DCB_ROLE_SERVICE_LISTENER, /*< Receives initial connect requests from clients */ + DCB_ROLE_REQUEST_HANDLER, /*< Serves dedicated client */ + DCB_ROLE_INTERNAL /*< Internal DCB not connected to the outside */ } dcb_role_t; +/** + * Callback reasons for the DCB callback mechanism. + */ +typedef enum { + DCB_REASON_CLOSE, /*< The DCB is closing */ + DCB_REASON_DRAINED, /*< The write delay queue has drained */ + DCB_REASON_HIGH_WATER, /*< Cross high water mark */ + DCB_REASON_LOW_WATER, /*< Cross low water mark */ + DCB_REASON_ERROR, /*< An error was flagged on the connection */ + DCB_REASON_HUP /*< A hangup was detected */ +} DCB_REASON; + +/** + * Callback structure - used to track callbacks registered on a DCB + */ +typedef struct dcb_callback { + DCB_REASON reason; /*< The reason for the callback */ + int (*cb)(struct dcb *dcb, DCB_REASON reason, void *userdata); + void *userdata; /*< User data to be sent in the callback */ + struct dcb_callback + *next; /*< Next callback for this DCB */ +} DCB_CALLBACK; + + /** * Descriptor Control Block * @@ -172,6 +201,7 @@ typedef struct dcb { struct session *session; /**< The owning session */ GWPROTOCOL func; /**< The functions for this descriptor */ + unsigned int writeqlen; /**< Current number of byes in the write queue */ SPINLOCK writeqlock; /**< Write Queue spinlock */ GWBUF *writeq; /**< Write Data Queue */ SPINLOCK delayqlock; /**< Delay Backend Write Queue spinlock */ @@ -186,6 +216,11 @@ typedef struct dcb { void *data; /**< Specific client data */ DCBMM memdata; /**< The data related to DCB memory management */ int command; /**< Specific client command type */ + SPINLOCK cb_lock; /**< The lock for the callbacks linked list */ + DCB_CALLBACK *callbacks; /**< The list of callbacks for the DCB */ + + unsigned int high_water; /**< High water mark */ + unsigned int low_water; /**< Low water mark */ #if defined(SS_DEBUG) skygw_chk_t dcb_chk_tail; #endif @@ -204,6 +239,11 @@ int fail_accept_errno; #define DCB_SESSION(x) (x)->session #define DCB_PROTOCOL(x, type) (type *)((x)->protocol) #define DCB_ISZOMBIE(x) ((x)->state == DCB_STATE_ZOMBIE) +#define DCB_WRITEQLEN(x) (x)->writeqlen +#define DCB_SET_LOW_WATER(x, lo) (x)->low_water = (lo); +#define DCB_SET_HIGH_WATER(x, hi) (x)->low_water = (hi); +#define DCB_BELOW_LOW_WATER(x) ((x)->low_water && (x)->writeqlen < (x)->low_water) +#define DCB_ABOVE_HIGH_WATER(x) ((x)->high_water && (x)->writeqlen > (x)->high_water) DCB *dcb_get_zombies(void); int gw_write( @@ -230,6 +270,11 @@ void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */ int dcb_isclient(DCB *); /* the DCB is the client of the session */ void dcb_hashtable_stats(DCB *, void *); /**< Print statisitics */ void dcb_add_to_zombieslist(DCB* dcb); +int dcb_add_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), + void *); +int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON), + void *); +int dcb_isvalid(DCB *); /* Check the DCB is in the linked list */ bool dcb_set_state( DCB* dcb, diff --git a/server/include/monitor.h b/server/include/monitor.h index 6aa0f2430..6444fecd5 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -29,6 +29,7 @@ * Date Who Description * 07/07/13 Mark Riddoch Initial implementation * 25/07/13 Mark Riddoch Addition of diagnotics + * 23/05/14 Mark Riddoch Addition of routine to find monitors by name * * @endverbatim */ @@ -79,6 +80,7 @@ typedef struct monitor { extern MONITOR *monitor_alloc(char *, char *); extern void monitor_free(MONITOR *); +extern MONITOR *monitor_find(char *); extern void monitorAddServer(MONITOR *, SERVER *); extern void monitorAddUser(MONITOR *, char *, char *); extern void monitorStop(MONITOR *); diff --git a/server/include/server.h b/server/include/server.h index dbe50d5ac..dfd439852 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -31,7 +31,9 @@ * 14/06/13 Mark Riddoch Initial implementation * 21/06/13 Mark Riddoch Addition of server status flags * 22/07/13 Mark Riddoch Addition of JOINED status for Galera + * 18/05/14 Mark Riddoch Addition of unique_name field * 20/05/14 Massimiliano Pinto Addition of server_string field + * 20/05/14 Massimiliano Pinto Addition of node_id field * * @endverbatim */ @@ -52,6 +54,7 @@ typedef struct { * between the gateway and the server. */ typedef struct server { + char *unique_name; /**< Unique name for the server */ char *name; /**< Server name/IP address*/ unsigned short port; /**< Port to listen on */ char *protocol; /**< Protocol module to use */ @@ -62,6 +65,7 @@ typedef struct server { struct server *next; /**< Next server */ struct server *nextdb; /**< Next server in list attached to a service */ char *server_string; /**< Server version string, i.e. MySQL server version */ + long node_id; /**< Node id, server_id for M/S or local_index for Galera */ } SERVER; /** @@ -101,10 +105,11 @@ typedef struct server { * Is the server joined Galera node? The server must be running and joined. */ #define SERVER_IS_JOINED(server) \ - (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) == (SERVER_RUNNING|SERVER_JOINED)) + (((server)->status & (SERVER_RUNNING|SERVER_JOINED)) == (SERVER_RUNNING|SERVER_JOINED)) extern SERVER *server_alloc(char *, char *, unsigned short); extern int server_free(SERVER *); +extern SERVER *server_find_by_unique_name(char *); extern SERVER *server_find(char *, unsigned short); extern void printServer(SERVER *); extern void printAllServers(); diff --git a/server/include/service.h b/server/include/service.h index f77270535..9369712f0 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -110,8 +110,8 @@ typedef struct service { *router; /**< The router we are using */ void *router_instance; /**< The router instance for this service */ + char *version_string; /** version string for this service listeners */ struct server *databases; /**< The set of servers in the backend */ - char *version_string; /** version string for this service listeners */ SERVICE_USER credentials; /**< The cedentials of the service user */ SPINLOCK spin; /**< The service spinlock */ SERVICE_STATS stats; /**< The service statistics */ @@ -134,6 +134,7 @@ typedef enum count_spec_t {COUNT_ATLEAST=0, COUNT_EXACT, COUNT_ATMOST} count_spe extern SERVICE *service_alloc(char *, char *); extern int service_free(SERVICE *); extern SERVICE *service_find(char *); +extern int service_isvalid(SERVICE *); extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short); extern int serviceHasProtocol(SERVICE *, char *, unsigned short); extern void serviceAddBackend(SERVICE *, SERVER *); @@ -153,11 +154,10 @@ extern int service_refresh_users(SERVICE *); extern void printService(SERVICE *); extern void printAllServices(); extern void dprintAllServices(DCB *); - bool service_set_slave_conn_limit ( SERVICE* service, CONFIG_PARAMETER* param, char* valstr, count_spec_t count_spec); - +extern void dprintService(DCB *, SERVICE *); #endif diff --git a/server/include/session.h b/server/include/session.h index e301fa1b3..790922d25 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -88,6 +88,7 @@ typedef struct session { SESSION *session_alloc(struct service *, struct dcb *); bool session_free(SESSION *); +int session_isvalid(SESSION *); void printAllSessions(); void printSession(SESSION *); void dprintAllSessions(struct dcb *); @@ -95,4 +96,4 @@ void dprintSession(struct dcb *, SESSION *); char *session_state(int); bool session_link_dcb(SESSION *, struct dcb *); SESSION* get_session_by_router_ses(void* rses); -#endif \ No newline at end of file +#endif diff --git a/server/modules/include/debugcli.h b/server/modules/include/debugcli.h index 0e7afcfe2..b373cae6b 100644 --- a/server/modules/include/debugcli.h +++ b/server/modules/include/debugcli.h @@ -41,6 +41,7 @@ struct cli_session; typedef struct cli_instance { SPINLOCK lock; /*< The instance spinlock */ SERVICE *service; /*< The debug cli service */ + int mode; /*< CLI interface mode */ struct cli_session *sessions; /*< Linked list of sessions within this instance */ struct cli_instance @@ -53,8 +54,13 @@ typedef struct cli_instance { */ typedef struct cli_session { char cmdbuf[80]; /*< The command buffer used to build up user commands */ + int mode; /*< The CLI Mode for this session */ SESSION *session; /*< The gateway session */ struct cli_session *next; /*< The next pointer for the list of sessions */ } CLI_SESSION; + +/* Command line interface modes */ +#define CLIM_USER 1 +#define CLIM_DEVELOPER 2 #endif diff --git a/server/modules/monitor/galera_mon.c b/server/modules/monitor/galera_mon.c index ecdfa44a3..96b891d58 100644 --- a/server/modules/monitor/galera_mon.c +++ b/server/modules/monitor/galera_mon.c @@ -22,8 +22,10 @@ * @verbatim * Revision History * - * Date Who Description - * 22/07/13 Mark Riddoch Initial implementation + * Date Who Description + * 22/07/13 Mark Riddoch Initial implementation + * 21/05/14 Massimiliano Pinto Monitor sets a master server + * that has the lowest value of wsrep_local_index * * @endverbatim */ @@ -45,7 +47,7 @@ extern int lm_enabled_logfiles_bitmask; static void monitorMain(void *); -static char *version_str = "V1.0.0"; +static char *version_str = "V1.1.0"; static void *startMonitor(void *); static void stopMonitor(void *); @@ -121,7 +123,7 @@ MYSQL_MONITOR *handle; handle->defaultPasswd = NULL; spinlock_init(&handle->lock); } - handle->tid = thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, handle); return handle; } @@ -136,7 +138,7 @@ stopMonitor(void *arg) MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; handle->shutdown = 1; - thread_wait(handle->tid); + thread_wait((void *)handle->tid); } /** @@ -280,6 +282,8 @@ 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) { @@ -297,6 +301,7 @@ char *uname = defaultUser, *passwd = defaultPasswd; uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) { server_clear_status(database->server, SERVER_RUNNING); + database->server->node_id = -1; free(dpwd); return; } @@ -306,6 +311,15 @@ char *uname = defaultUser, *passwd = defaultPasswd; /* 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) { + database->server->server_string = strdup(server_string); + } + /* Check if the the Galera FSM shows this node is joined to the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state_comment'") == 0 && (result = mysql_store_result(database->con)) != NULL) @@ -319,6 +333,25 @@ char *uname = defaultUser, *passwd = defaultPasswd; mysql_free_result(result); } + /* Check the the Galera node index in the cluster */ + if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_index'") == 0 + && (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); + if ((errno == ERANGE && (local_index == LONG_MAX + || local_index == LONG_MIN)) || (errno != 0 && local_index == 0)) + { + local_index = -1; + } + database->server->node_id = local_index; + } + mysql_free_result(result); + } + if (isjoined) server_set_status(database->server, SERVER_JOINED); else @@ -335,6 +368,7 @@ monitorMain(void *arg) { MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; MONITOR_SERVERS *ptr; +long master_id; if (mysql_thread_init()) { @@ -347,6 +381,8 @@ MONITOR_SERVERS *ptr; handle->status = MONITOR_RUNNING; while (1) { + master_id = -1; + if (handle->shutdown) { handle->status = MONITOR_STOPPING; @@ -354,10 +390,48 @@ MONITOR_SERVERS *ptr; handle->status = MONITOR_STOPPED; return; } + ptr = handle->databases; + while (ptr) { monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd); + + /* set master_id to the lowest value of ptr->server->node_id */ + + if (ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) { + if (ptr->server->node_id < master_id && master_id >= 0) { + master_id = ptr->server->node_id; + } else { + if (master_id < 0) { + master_id = ptr->server->node_id; + } + } + } else { + /* clear M/S status */ + server_clear_status(ptr->server, SERVER_SLAVE); + server_clear_status(ptr->server, SERVER_MASTER); + } + ptr = ptr->next; + } + + ptr = handle->databases; + + /* this server loop sets Master and Slave roles */ + while (ptr) + { + if (ptr->server->node_id >= 0 && master_id >= 0) { + /* set the Master role */ + if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id == master_id)) { + server_set_status(ptr->server, SERVER_MASTER); + server_clear_status(ptr->server, SERVER_SLAVE); + } else if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id > master_id)) { + /* set the Slave role */ + server_set_status(ptr->server, SERVER_SLAVE); + server_clear_status(ptr->server, SERVER_MASTER); + } + } + ptr = ptr->next; } thread_millisleep(MONITOR_INTERVAL); diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 4cf7e6938..ddf1f7cbc 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -128,7 +128,7 @@ MYSQL_MONITOR *handle; handle->defaultPasswd = NULL; spinlock_init(&handle->lock); } - handle->tid = thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, handle); return handle; } @@ -143,7 +143,7 @@ stopMonitor(void *arg) MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; handle->shutdown = 1; - thread_wait(handle->tid); + thread_wait((void *)handle->tid); } /** @@ -334,6 +334,25 @@ char *server_string; database->server->server_string = strdup(server_string); } + /* get server_id form current node */ + if (mysql_query(database->con, "SELECT @@server_id") == 0 + && (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); + if ((errno == ERANGE && (server_id == LONG_MAX + || server_id == LONG_MIN)) || (errno != 0 && server_id == 0)) + { + server_id = -1; + } + database->server->node_id = server_id; + } + mysql_free_result(result); + } + /* Check SHOW SLAVE HOSTS - if we get rows then we are a master */ if (mysql_query(database->con, "SHOW SLAVE HOSTS")) { diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index d5fc4b04c..efa92a71d 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -282,7 +282,13 @@ static int gw_read_backend_event(DCB *dcb) { } /* switch */ } - if (backend_protocol->state == MYSQL_AUTH_FAILED) { + if (backend_protocol->state == MYSQL_AUTH_FAILED) + { + /** + * protocol state won't change anymore, + * lock can be freed + */ + spinlock_release(&dcb->authlock); spinlock_acquire(&dcb->delayqlock); /*< * vraa : errorHandle @@ -321,14 +327,14 @@ static int gw_read_backend_event(DCB *dcb) { if (session->client->session == NULL) { rc = 1; - goto return_with_lock; + goto return_rc; } usleep(1); } if (session->state == SESSION_STATE_STOPPING) { - goto return_with_lock; + goto return_rc; } spinlock_acquire(&session->ses_lock); session->state = SESSION_STATE_STOPPING; @@ -351,7 +357,7 @@ static int gw_read_backend_event(DCB *dcb) { /* close router_session */ router->closeSession(router_instance, rsession); rc = 1; - goto return_with_lock; + goto return_rc; } else { @@ -424,21 +430,23 @@ static int gw_read_backend_event(DCB *dcb) { if (dcb->session->client != NULL) { client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); - } - - if (client_protocol != NULL) { - CHK_PROTOCOL(client_protocol); + if (client_protocol != NULL) { + CHK_PROTOCOL(client_protocol); - if (client_protocol->state == MYSQL_IDLE) - { - router->clientReply(router_instance, + if (client_protocol->state == MYSQL_IDLE) + { + router->clientReply(router_instance, rsession, writebuf, dcb); - rc = 1; - } - goto return_rc; - } + rc = 1; + } + goto return_rc; + } else if (dcb->session->client->dcb_role == DCB_ROLE_INTERNAL) { + router->clientReply(router_instance, rsession, writebuf, dcb); + rc = 1; + } + } } return_rc: @@ -577,7 +585,8 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) snprintf(str, len+1, "%s", startpoint); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Authentication to backend failed."))); + "Error : Unable to write to backend due to " + "authentication failure."))); /** Consume query buffer */ while ((queue = gwbuf_consume( queue, diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index ecd70ecee..1d0932d7b 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -1323,9 +1323,8 @@ GWBUF* gw_MySQL_get_next_packet( size_t packetlen; size_t totalbuflen; uint8_t* data; - readbuf = *p_readbuf; - + if (readbuf == NULL) { packetbuf = NULL; diff --git a/server/modules/routing/debugcli.c b/server/modules/routing/debugcli.c index 66c02c98b..d6becdfda 100644 --- a/server/modules/routing/debugcli.c +++ b/server/modules/routing/debugcli.c @@ -45,7 +45,7 @@ extern int lm_enabled_logfiles_bitmask; -static char *version_str = "V1.0.1"; +static char *version_str = "V1.1.0"; /* The router entry points */ static ROUTER *createInstance(SERVICE *service, char **options); @@ -127,6 +127,7 @@ static ROUTER * createInstance(SERVICE *service, char **options) { CLI_INSTANCE *inst; +int i; if ((inst = malloc(sizeof(CLI_INSTANCE))) == NULL) return NULL; @@ -134,7 +135,29 @@ CLI_INSTANCE *inst; inst->service = service; spinlock_init(&inst->lock); inst->sessions = NULL; + inst->mode = CLIM_USER; + if (options) + { + for (i = 0; options[i]; i++) + { + if (!strcasecmp(options[i], "developer")) + { + inst->mode = CLIM_DEVELOPER; + } + else if (!strcasecmp(options[i], "user")) + { + inst->mode = CLIM_USER; + } + else + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Unknown option for CLI '%s'\n", + options[i]))); + } + } + } /* * We have completed the creation of the instance data, so now @@ -176,11 +199,15 @@ CLI_SESSION *client; spinlock_release(&inst->lock); session->state = SESSION_STATE_READY; + client->mode = inst->mode; dcb_printf(session->client, "Welcome the SkySQL MaxScale Debug Interface (%s).\n", version_str); - dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n"); - dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); + if (client->mode == CLIM_DEVELOPER) + { + dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n"); + dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); + } dcb_printf(session->client, "Type help for a list of available commands.\n\n"); return (void *)client; @@ -281,4 +308,4 @@ static uint8_t getCapabilities( void* router_session) { return 0; -} \ No newline at end of file +} diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 422113d6c..bab8c2f55 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -36,7 +36,10 @@ * Date Who Description * 20/06/13 Mark Riddoch Initial implementation * 17/07/13 Mark Riddoch Additional commands - * 09/08/2013 Massimiliano Pinto Addes enable/disable commands (now only for log) + * 09/08/2013 Massimiliano Pinto Added enable/disable commands (now only for log) + * 20/05/14 Mark Riddoch Added ability to give server and service names rather + * than simply addresses + * 23/05/14 Mark Riddoch Added support for developer and user modes * * @endverbatim */ @@ -68,6 +71,13 @@ #define ARG_TYPE_ADDRESS 1 #define ARG_TYPE_STRING 2 +#define ARG_TYPE_SERVICE 3 +#define ARG_TYPE_SERVER 4 +#define ARG_TYPE_DBUSERS 5 +#define ARG_TYPE_SESSION 6 +#define ARG_TYPE_DCB 7 +#define ARG_TYPE_MONITOR 8 + /** * The subcommand structure * @@ -78,6 +88,7 @@ struct subcommand { int n_args; void (*fn)(); char *help; + char *devhelp; int arg_types[3]; }; @@ -86,31 +97,59 @@ static void telnetdShowUsers(DCB *); * The subcommands of the show command */ struct subcommand showoptions[] = { - { "dcbs", 0, dprintAllDCBs, "Show all descriptor control blocks (network connections)", + { "dcbs", 0, dprintAllDCBs, + "Show all descriptor control blocks (network connections)", + "Show all descriptor control blocks (network connections)", {0, 0, 0} }, - { "dcb", 1, dprintDCB, "Show a single descriptor control block e.g. show dcb 0x493340", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "dbusers", 1, dcb_usersPrint, "Show statistics and user names for a service's user table.\n\t\tExample : show dbusers ", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "epoll", 0, dprintPollStats, "Show the poll statistics", + { "dcb", 1, dprintDCB, + "Show a single descriptor control block e.g. show dcb 0x493340", + "Show a single descriptor control block e.g. show dcb 0x493340", + {ARG_TYPE_DCB, 0, 0} }, + { "dbusers", 1, dcb_usersPrint, + "Show statistics and user names for a service's user table.\n\t\tExample : show dbusers ", + "Show statistics and user names for a service's user table.\n\t\tExample : show dbusers |", + {ARG_TYPE_DBUSERS, 0, 0} }, + { "epoll", 0, dprintPollStats, + "Show the poll statistics", + "Show the poll statistics", {0, 0, 0} }, - { "modules", 0, dprintAllModules, "Show all currently loaded modules", + { "modules", 0, dprintAllModules, + "Show all currently loaded modules", + "Show all currently loaded modules", {0, 0, 0} }, - { "monitors", 0, monitorShowAll, "Show the monitors that are configured", + { "monitors", 0, monitorShowAll, + "Show the monitors that are configured", + "Show the monitors that are configured", {0, 0, 0} }, - { "server", 1, dprintServer, "Show details for a server, e.g. show server 0x485390", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "servers", 0, dprintAllServers, "Show all configured servers", + { "server", 1, dprintServer, + "Show details for a named server, e.g. show server dbnode1", + "Show details for a server, e.g. show server 0x485390. The address may also be repalced with the server name from the configuration file", + {ARG_TYPE_SERVER, 0, 0} }, + { "servers", 0, dprintAllServers, + "Show all configured servers", + "Show all configured servers", {0, 0, 0} }, - { "services", 0, dprintAllServices, "Show all configured services in MaxScale", + { "services", 0, dprintAllServices, + "Show all configured services in MaxScale", + "Show all configured services in MaxScale", {0, 0, 0} }, - { "session", 1, dprintSession, "Show a single session in MaxScale, e.g. show session 0x284830", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "sessions", 0, dprintAllSessions, "Show all active sessions in MaxScale", + { "service", 1, dprintService, + "Show a single service in MaxScale, may be passed a service name", + "Show a single service in MaxScale, may be passed a service name or address of a service object", + {ARG_TYPE_SERVICE, 0, 0} }, + { "session", 1, dprintSession, + "Show a single session in MaxScale, e.g. show session 0x284830", + "Show a single session in MaxScale, e.g. show session 0x284830", + {ARG_TYPE_SESSION, 0, 0} }, + { "sessions", 0, dprintAllSessions, + "Show all active sessions in MaxScale", + "Show all active sessions in MaxScale", {0, 0, 0} }, - { "users", 0, telnetdShowUsers, "Show statistics and user names for the debug interface", - {ARG_TYPE_ADDRESS, 0, 0} }, - { NULL, 0, NULL, NULL, + { "users", 0, telnetdShowUsers, + "Show statistics and user names for the debug interface", + "Show statistics and user names for the debug interface", + {0, 0, 0} }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -126,7 +165,7 @@ struct subcommand shutdownoptions[] = { 0, shutdown_server, "Shutdown MaxScale", - + "Shutdown MaxScale", {0, 0, 0} }, { @@ -134,20 +173,23 @@ struct subcommand shutdownoptions[] = { 1, shutdown_monitor, "Shutdown a monitor, e.g. shutdown monitor 0x48381e0", - {ARG_TYPE_ADDRESS, 0, 0} + "Shutdown a monitor, e.g. shutdown monitor 0x48381e0", + {ARG_TYPE_MONITOR, 0, 0} }, { "service", 1, shutdown_service, - "Shutdown a service, e.g. shutdown service 0x4838320", - {ARG_TYPE_ADDRESS, 0, 0} + "Shutdown a service, e.g. shutdown service \"Sales Database\"", + "Shutdown a service, e.g. shutdown service 0x4838320 or shutdown service \"Sales Database\"", + {ARG_TYPE_SERVICE, 0, 0} }, { NULL, 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -159,11 +201,15 @@ static void restart_monitor(DCB *dcb, MONITOR *monitor); * The subcommands of the restart command */ struct subcommand restartoptions[] = { - { "monitor", 1, restart_monitor, "Restart a monitor, e.g. restart monitor 0x48181e0", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "service", 1, restart_service, "Restart a service, e.g. restart service 0x4838320", - {ARG_TYPE_ADDRESS, 0, 0} }, - { NULL, 0, NULL, NULL, + { "monitor", 1, restart_monitor, + "Restart a monitor, e.g. restart monitor 0x48181e0", + "Restart a monitor, e.g. restart monitor 0x48181e0", + {ARG_TYPE_MONITOR, 0, 0} }, + { "service", 1, restart_service, + "Restart a service, e.g. restart service \"Test Service\"", + "Restart a service, e.g. restart service 0x4838320", + {ARG_TYPE_SERVICE, 0, 0} }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -172,9 +218,11 @@ static void set_server(DCB *dcb, SERVER *server, char *bit); * The subcommands of the set command */ struct subcommand setoptions[] = { - { "server", 2, set_server, "Set the status of a server. E.g. set server 0x4838320 master", - {ARG_TYPE_ADDRESS, ARG_TYPE_STRING, 0} }, - { NULL, 0, NULL, NULL, + { "server", 2, set_server, + "Set the status of a server. E.g. set server dbnode4 master", + "Set the status of a server. E.g. set server 0x4838320 master", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -183,9 +231,11 @@ static void clear_server(DCB *dcb, SERVER *server, char *bit); * The subcommands of the clear command */ struct subcommand clearoptions[] = { - { "server", 2, clear_server, "Clear the status of a server. E.g. clear server 0x4838320 master", - {ARG_TYPE_ADDRESS, ARG_TYPE_STRING, 0} }, - { NULL, 0, NULL, NULL, + { "server", 2, clear_server, + "Clear the status of a server. E.g. clear server dbnode2 master", + "Clear the status of a server. E.g. clear server 0x4838320 master", + {ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -196,11 +246,15 @@ static void reload_config(DCB *dcb); * The subcommands of the reload command */ struct subcommand reloadoptions[] = { - { "config", 0, reload_config, "Reload the configuration data for MaxScale.", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "dbusers", 1, reload_dbusers, "Reload the dbuser data for a service. E.g. reload dbusers 0x849420", - {ARG_TYPE_ADDRESS, 0, 0} }, - { NULL, 0, NULL, NULL, + { "config", 0, reload_config, + "Reload the configuration data for MaxScale.", + "Reload the configuration data for MaxScale.", + {0, 0, 0} }, + { "dbusers", 1, reload_dbusers, + "Reload the dbuser data for a service. E.g. reload dbusers \"splitter service\"", + "Reload the dbuser data for a service. E.g. reload dbusers 0x849420", + {ARG_TYPE_DBUSERS, 0, 0} }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -217,6 +271,8 @@ struct subcommand enableoptions[] = { enable_log_action, "Enable Log options for MaxScale, options trace | error | " "message E.g. enable log message.", + "Enable Log options for MaxScale, options trace | error | " + "message E.g. enable log message.", {ARG_TYPE_STRING, 0, 0} }, { @@ -224,6 +280,7 @@ struct subcommand enableoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -239,6 +296,8 @@ struct subcommand disableoptions[] = { disable_log_action, "Disable Log for MaxScale, Options: debug | trace | error | message " "E.g. disable log debug", + "Disable Log for MaxScale, Options: debug | trace | error | message " + "E.g. disable log debug", {ARG_TYPE_STRING, 0, 0} }, { @@ -246,6 +305,7 @@ struct subcommand disableoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -264,6 +324,7 @@ struct subcommand failoptions[] = { 0, fail_backendfd, "Fail backend socket for next operation.", + "Fail backend socket for next operation.", {ARG_TYPE_STRING, 0, 0} }, { @@ -271,6 +332,7 @@ struct subcommand failoptions[] = { 0, fail_clientfd, "Fail client socket for next operation.", + "Fail client socket for next operation.", {ARG_TYPE_STRING, 0, 0} }, { @@ -278,6 +340,7 @@ struct subcommand failoptions[] = { 2, fail_accept, "Fail to accept next client connection.", + "Fail to accept next client connection.", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { @@ -285,6 +348,7 @@ struct subcommand failoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -295,9 +359,11 @@ static void telnetdAddUser(DCB *, char *, char *); * The subcommands of the add command */ struct subcommand addoptions[] = { - { "user", 2, telnetdAddUser, "Add a new user for the debug interface. E.g. add user john today", + { "user", 2, telnetdAddUser, + "Add a new user for the debug interface. E.g. add user john today", + "Add a new user for the debug interface. E.g. add user john today", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, - { NULL, 0, NULL, NULL, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -312,10 +378,11 @@ struct subcommand removeoptions[] = { 2, telnetdRemoveUser, "Remove existing maxscale user. Example : remove user john johnpwd", + "Remove existing maxscale user. Example : remove user john johnpwd", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { - NULL, 0, NULL, NULL, {0, 0, 0} + NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -348,19 +415,55 @@ static struct { * Convert a string argument to a numeric, observing prefixes * for number bases, e.g. 0x for hex, 0 for octal * + * @param mode The CLI mode * @param arg The string representation of the argument * @param arg_type The target type for the argument * @return The argument as a long integer */ static unsigned long -convert_arg(char *arg, int arg_type) +convert_arg(int mode, char *arg, int arg_type) { +unsigned long rval; +SERVICE *service; + switch (arg_type) { case ARG_TYPE_ADDRESS: return (unsigned long)strtol(arg, NULL, 0); case ARG_TYPE_STRING: return (unsigned long)arg; + case ARG_TYPE_SERVICE: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + rval = (unsigned long)service_find(arg); + return rval; + case ARG_TYPE_SERVER: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + rval = (unsigned long)server_find_by_unique_name(arg); + return rval; + case ARG_TYPE_DBUSERS: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + { + service = service_find(arg); + if (service) + return (unsigned long)(service->users); + else + return 0; + } + return rval; + case ARG_TYPE_DCB: + rval = (unsigned long)strtol(arg, NULL, 0); + if (mode == CLIM_USER && dcb_isvalid((DCB *)rval) == 0) + rval = 0; + return rval; + case ARG_TYPE_SESSION: + rval = (unsigned long)strtol(arg, NULL, 0); + if (mode == CLIM_USER && session_isvalid((SESSION *)rval) == 0) + rval = 0; + return rval; + case ARG_TYPE_MONITOR: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + rval = (unsigned long)monitor_find(arg); + return rval; } return 0; } @@ -387,33 +490,94 @@ int argc, i, j, found = 0; char *args[MAXARGS]; char *saveptr, *delim = " \t\r\n"; unsigned long arg1, arg2, arg3; +int in_quotes = 0, escape_next = 0; +char *ptr, *lptr; - /* Tokenize the input string */ - args[0] = strtok_r(cli->cmdbuf, delim, &saveptr); + args[0] = cli->cmdbuf; + ptr = args[0]; + lptr = ptr; i = 0; - do { - i++; - args[i] = strtok_r(NULL, delim, &saveptr); - } while (args[i] != NULL && i < MAXARGS); + /* + * Break the command line into a number of words. Whitespace is used + * to delimit words and may be escaped by use of the \ character or + * the use of double quotes. + * The array args contains the broken down words, one per index. + */ + while (*ptr) + { + if (escape_next) + { + *lptr++ = *ptr++; + escape_next = 0; + } + else if (*ptr == '\\') + { + escape_next = 1; + ptr++; + } + else if (in_quotes == 0 && (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')) + { + *lptr = 0; + if (args[i] == ptr) + args[i] = ptr + 1; + else + { + i++; + if (i >= MAXARGS) + break; + args[i] = ptr + 1; + } + ptr++; + lptr++; + } + else if (*ptr == '\"' && in_quotes == 0) + { + in_quotes = 1; + ptr++; + } + else if (*ptr == '\"' && in_quotes == 1) + { + in_quotes = 0; + ptr++; + } + else + { + *lptr++ = *ptr++; + } + } + *lptr = 0; + args[i+1] = NULL; - if (args[0] == NULL) + if (args[0] == NULL || *args[0] == 0) return 1; argc = i - 2; /* The number of extra arguments to commands */ if (!strcasecmp(args[0], "help")) { - if (args[1] == NULL) + if (args[1] == NULL || *args[1] == 0) { found = 1; dcb_printf(dcb, "Available commands:\n"); for (i = 0; cmds[i].cmd; i++) { - for (j = 0; cmds[i].options[j].arg1; j++) + if (cmds[i].options[1].arg1 == NULL) + dcb_printf(dcb, " %s %s\n", cmds[i].cmd, cmds[i].options[0].arg1); + else { - dcb_printf(dcb, " %s %s\n", cmds[i].cmd, cmds[i].options[j].arg1); + dcb_printf(dcb, " %s [", cmds[i].cmd); + for (j = 0; cmds[i].options[j].arg1; j++) + { + dcb_printf(dcb, "%s%s", cmds[i].options[j].arg1, + cmds[i].options[j+1].arg1 ? "|" : ""); + } + dcb_printf(dcb, "]\n"); } } + dcb_printf(dcb, "\nType help command to see details of each command.\n"); + dcb_printf(dcb, "Where commands require names as arguments and these names contain\n"); + dcb_printf(dcb, "whitespace either the \\ character may be used to escape the whitespace\n"); + dcb_printf(dcb, "or the name may be enclosed in double quotes \".\n\n"); } else { @@ -449,9 +613,9 @@ unsigned long arg1, arg2, arg3; { for (j = 0; cmds[i].options[j].arg1; j++) { - found = 1; /**< command and sub-command match */ if (strcasecmp(args[1], cmds[i].options[j].arg1) == 0) { + found = 1; /**< command and sub-command match */ if (argc != cmds[i].options[j].n_args) { dcb_printf(dcb, "Incorrect number of arguments: %s %s expects %d arguments\n", @@ -467,7 +631,7 @@ unsigned long arg1, arg2, arg3; cmds[i].options[j].fn(dcb); break; case 1: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); + arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]); if (arg1) cmds[i].options[j].fn(dcb, arg1); else @@ -475,8 +639,8 @@ unsigned long arg1, arg2, arg3; args[2]); break; case 2: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(args[3],cmds[i].options[j].arg_types[1]); + arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]); + arg2 = convert_arg(cli->mode, args[3],cmds[i].options[j].arg_types[1]); if (arg1 && arg2) cmds[i].options[j].fn(dcb, arg1, arg2); else if (arg1 == 0) @@ -487,9 +651,9 @@ unsigned long arg1, arg2, arg3; args[3]); break; case 3: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(args[3],cmds[i].options[j].arg_types[1]); - arg3 = convert_arg(args[4],cmds[i].options[j].arg_types[2]); + arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]); + arg2 = convert_arg(cli->mode, args[3],cmds[i].options[j].arg_types[1]); + arg3 = convert_arg(cli->mode, args[4],cmds[i].options[j].arg_types[2]); if (arg1 && arg2 && arg3) cmds[i].options[j].fn(dcb, arg1, arg2, arg3); else if (arg1 == 0) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index a203a9865..5b855afe4 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -1015,16 +1015,16 @@ static int routeQuery( router_cli_ses->rses_id))); ss_dassert(QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)); + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + goto return_ret; + } + succp = get_dcb(&slave_dcb, router_cli_ses, BE_SLAVE); if (succp) - { - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - goto return_ret; - } - + { if ((ret = slave_dcb->func.write(slave_dcb, querybuf)) == 1) { atomic_add(&inst->stats.n_slave, 1); @@ -1036,8 +1036,9 @@ static int routeQuery( "Error : Routing query \"%s\" failed.", querystr))); } - rses_end_locked_router_action(router_cli_ses); } + rses_end_locked_router_action(router_cli_ses); + ss_dassert(succp); goto return_ret; } @@ -1061,6 +1062,11 @@ static int routeQuery( "routing to Master."))); } } + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + goto return_ret; + } if (master_dcb == NULL) { @@ -1068,21 +1074,22 @@ static int routeQuery( } if (succp) { - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - goto return_ret; - } if ((ret = master_dcb->func.write(master_dcb, querybuf)) == 1) { atomic_add(&inst->stats.n_master, 1); } - rses_end_locked_router_action(router_cli_ses); - } + } + rses_end_locked_router_action(router_cli_ses); + ss_dassert(succp); - ss_dassert(ret == 1); - goto return_ret; + + if (ret == 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing to master failed."))); + } } return_ret: if (plainsqlbuf != NULL)