diff --git a/Documentation/Max Scale 0.6 Release Notes.pdf b/Documentation/Max Scale 0.6 Release Notes.pdf new file mode 100644 index 000000000..418572497 Binary files /dev/null and b/Documentation/Max Scale 0.6 Release Notes.pdf differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios.pdf b/Documentation/MaxScale Configuration And Usage Scenarios.pdf index b870677a3..47a24088f 100644 Binary files a/Documentation/MaxScale Configuration And Usage Scenarios.pdf and b/Documentation/MaxScale Configuration And Usage Scenarios.pdf differ diff --git a/Documentation/MaxScale 0.5 Release Notes.pdf b/Documentation/history/MaxScale 0.5 Release Notes.pdf similarity index 100% rename from Documentation/MaxScale 0.5 Release Notes.pdf rename to Documentation/history/MaxScale 0.5 Release Notes.pdf diff --git a/README b/README index 413eb1b26..1e495b1b2 100644 --- a/README +++ b/README @@ -35,7 +35,7 @@ Bugs can be reported in the SkySQL bugs database Edit the file build_gateway.inc in your MaxScale directory and set the ROOT_PATH to the directory in which you have installed the -MaxScale source code. Set the MYSQL_ROOT/MYSQL_HEADERS variables +MaxScale source code. Set the INC_PATH/MYSQL_ROOT/MYSQL_HEADERS variables to the location in which you have installed the developer package for MariaDB or checked out the source code of MariaDB and the location of your MariaDB include files. diff --git a/VERSION b/VERSION index 8f0916f76..a918a2aa1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 +0.6.0 diff --git a/build_gateway.inc b/build_gateway.inc index 26fefb0e0..83ffaf762 100644 --- a/build_gateway.inc +++ b/build_gateway.inc @@ -2,67 +2,52 @@ # This file includes all dynamically changing build-related # variables. # +# Current values are to satisfy MaxScale build process. +# # Modify to match with your needs. Do not commit any private # changes to this file! # +# # -# Use debug flags -# Values : | Y +# Set debug flags # DEBUG := # # Set build env -# Values : | Y # UNIX := Y # # Set MaxScale branch directory -# Example := $(HOME)/src/MaxScale # -ROOT_PATH := - -# -# Main directory for headers -# Example := $(HOME)/usr/include -# -INC_PATH := +ROOT_PATH := $(HOME)/src/bazaar/tmp/maxscale +INC_PATH := $(HOME)/usr/include # # Set prefix to MySQL Resources -# Example := $(INC_PATH)/mysql # -MYSQL_ROOT := +MYSQL_ROOT := $(INC_PATH)/mysql # # Set prefix of the path to development headers -# Example := -I$(INC_PATH) -I$(MYSQL_ROOT)/ -I$(MYSQL_ROOT)/private/ -I$(MYSQL_ROOT)/extra/ # -MYSQL_HEADERS := +MYSQL_HEADERS := -I$(INC_PATH) -I$(MYSQL_ROOT)/ -I$(MYSQL_ROOT)/private/ -I$(MYSQL_ROOT)/extra/ # # Set DYNLIB=Y if you want to link MaxScale with dynamic embedded lib -# Values : | Y # DYNLIB := # # Set path to Embedded MySQL Server -# Example -# EMBEDDED_LIB := $(HOME)/usr/lib64 -# ifdef DYNLIB -# EMBEDDED_LIB := $(HOME)/usr/lib64/dynlib -# endif # -EMBEDDED_LIB := +EMBEDDED_LIB := $(HOME)/usr/lib64 ifdef DYNLIB - EMBEDDED_LIB := + EMBEDDED_LIB := $(HOME)/usr/lib64/dynlib endif - # # Set path to MySQL errors file -# Example := $(HOME)/usr/share/mysql -# -ERRMSG := +# +ERRMSG := $(HOME)/usr/share/mysql diff --git a/log_manager/log_manager.cc b/log_manager/log_manager.cc index 022523488..c8f96266e 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -253,6 +253,7 @@ static int logmanager_write_log( va_list valist); static blockbuf_t* blockbuf_init(logfile_id_t id); +static void blockbuf_node_done(void* bb_data); static char* blockbuf_get_writepos( #if 0 int** refcount, @@ -996,8 +997,13 @@ static char* blockbuf_get_writepos( simple_mutex_unlock(&bb->bb_mutex); return pos; } - +static void blockbuf_node_done( + void* bb_data) +{ + blockbuf_t* bb = (blockbuf_t *)bb_data; + simple_mutex_done(&bb->bb_mutex); +} static blockbuf_t* blockbuf_init( @@ -2059,7 +2065,7 @@ static bool logfile_init( if (mlist_init(&logfile->lf_blockbuf_list, NULL, strdup("logfile block buffer list"), - NULL, + blockbuf_node_done, MAXNBLOCKBUFS) == NULL) { ss_dfprintf(stderr, diff --git a/log_manager/test/makefile b/log_manager/test/makefile index 6d37c7197..75df90c60 100644 --- a/log_manager/test/makefile +++ b/log_manager/test/makefile @@ -5,6 +5,7 @@ include ../../build_gateway.inc include ../../makefile.inc +include ../../test.inc CC = gcc CPP = g++ @@ -43,7 +44,7 @@ buildtests: runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test Log Manager" >> $(TESTLOG) @@ -62,3 +63,5 @@ runtests: @-$(LAUNCH_DEBUGGER) $(TESTAPP) "-t 16" 2>>$(TESTLOG) @echo "Log Manager PASSED" >> $(TESTLOG) @echo "" >> $(TESTLOG) + +# @cat $(TESTLOG) >> $(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/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 1976bd6b2..aafd746ce 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -50,11 +50,13 @@ #include #include #include - +#include +#include #include #include #include +#include #include extern int lm_enabled_logfiles_bitmask; @@ -73,9 +75,13 @@ static bool create_parse_tree( static skygw_query_type_t resolve_query_type( THD* thd); + static bool skygw_stmt_causes_implicit_commit( - LEX* lex, - uint mask); + LEX* lex, + int* autocommit_stmt); + +static int is_autocommit_stmt( + LEX* lex); /** * @node (write brief function description here) @@ -346,9 +352,9 @@ return_here: * restrictive, for example, QUERY_TYPE_READ is smaller than QUERY_TYPE_WRITE. * */ -static u_int8_t set_query_type( - u_int8_t* qtype, - u_int8_t new_type) +static u_int16_t set_query_type( + u_int16_t* qtype, + u_int16_t new_type) { *qtype = MAX(*qtype, new_type); return *qtype; @@ -374,7 +380,8 @@ static skygw_query_type_t resolve_query_type( THD* thd) { skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - u_int8_t type = QUERY_TYPE_UNKNOWN; + u_int16_t type = QUERY_TYPE_UNKNOWN; + int set_autocommit_stmt = -1; /*< -1 no, 0 disable, 1 enable */ LEX* lex; Item* item; /** @@ -397,7 +404,9 @@ static skygw_query_type_t resolve_query_type( goto return_qtype; } - if (skygw_stmt_causes_implicit_commit(lex, CF_AUTO_COMMIT_TRANS)) + if (skygw_stmt_causes_implicit_commit( + lex, + &set_autocommit_stmt)) { if (LOG_IS_ENABLED(LOGFILE_TRACE)) { @@ -418,7 +427,25 @@ static skygw_query_type_t resolve_query_type( "next command."); } } + + if (set_autocommit_stmt == 1) + { + type |= QUERY_TYPE_ENABLE_AUTOCOMMIT; + } type |= QUERY_TYPE_COMMIT; + } + + if (set_autocommit_stmt == 0) + { + if (LOG_IS_ENABLED(LOGFILE_TRACE)) + { + skygw_log_write( + LOGFILE_TRACE, + "Disable autocommit : implicit START TRANSACTION" + " before executing the next command."); + } + type |= QUERY_TYPE_DISABLE_AUTOCOMMIT; + type |= QUERY_TYPE_BEGIN_TRX; } /** * REVOKE ALL, ASSIGN_TO_KEYCACHE, @@ -648,11 +675,18 @@ return_qtype: return qtype; } -static bool skygw_stmt_causes_implicit_commit(LEX* lex, uint mask) +/** + * Checks if statement causes implicit COMMIT. + * autocommit_stmt gets values 1, 0 or -1 if stmt is enable, disable or + * something else than autocommit. + */ +static bool skygw_stmt_causes_implicit_commit( + LEX* lex, + int* autocommit_stmt) { bool succp; - - if (!(sql_command_flags[lex->sql_command] & mask)) + + if (!(sql_command_flags[lex->sql_command] & CF_AUTO_COMMIT_TRANS)) { succp = false; goto return_succp; @@ -668,13 +702,86 @@ static bool skygw_stmt_causes_implicit_commit(LEX* lex, uint mask) succp = !(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); break; case SQLCOM_SET_OPTION: - succp = lex->autocommit ? true : false; + if ((*autocommit_stmt = is_autocommit_stmt(lex)) == 1) + { + succp = true; + } + else + { + succp =false; + } break; default: succp = true; break; } - return_succp: return succp; } + + +/** + * Finds out if stmt is SET autocommit + * and if the new value matches with the enable_cmd argument. + * + * Returns 1, 0, or -1 if command was: + * enable, disable, or not autocommit, respectively. + */ +static int is_autocommit_stmt( + LEX* lex) +{ + struct list_node* node; + set_var* setvar; + int rc = -1; + static char target[8]; /*< for converted string */ + Item* item = NULL; + + node = lex->var_list.first_node(); + setvar=(set_var*)node->info; + + if (setvar == NULL) + { + goto return_rc; + } + + do /*< Search for the last occurrence of 'autocommit' */ + { + if ((sys_var*)setvar->var == Sys_autocommit_ptr) + { + item = setvar->value; + } + node = node->next; + } while ((setvar = (set_var*)node->info) != NULL); + + if (item != NULL) /*< found autocommit command */ + { + if (item->type() == Item::INT_ITEM) /*< '0' or '1' */ + { + rc = item->val_int(); + + if (rc > 1 || rc < 0) + { + rc = -1; + } + } + else if (item->type() == Item::STRING_ITEM) /*< 'on' or 'off' */ + { + String str(target, sizeof(target), system_charset_info); + String* res = item->val_str(&str); + + if ((rc = find_type(&bool_typelib, res->ptr(), res->length(), false))) + { + ss_dassert(rc >= 0 && rc <= 2); + /** + * rc is the position of matchin string in + * typelib's value array. + * 1=OFF, 2=ON. + */ + rc -= 1; + } + } + } + +return_rc: + return rc; +} diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 69a5f92da..64727daa9 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -29,15 +29,17 @@ EXTERN_C_BLOCK_BEGIN * is modified */ typedef enum { - QUERY_TYPE_UNKNOWN = 0, /*< Couln't find out or parse error */ - QUERY_TYPE_LOCAL_READ = (1<<0), /*< Read non-database data, execute in MaxScale */ - QUERY_TYPE_READ = (1<<1), /*< No updates */ - QUERY_TYPE_WRITE = (1<<2), /*< Master data will be modified */ - QUERY_TYPE_SESSION_WRITE = (1<<3), /*< Session data will be modified */ - QUERY_TYPE_GLOBAL_WRITE = (1<<4), /*< Global system variable modification */ - QUERY_TYPE_BEGIN_TRX = (1<<5), /*< BEGIN or START TRANSACTION */ - QUERY_TYPE_ROLLBACK = (1<<6), /*< ROLLBACK */ - QUERY_TYPE_COMMIT = (1<<7), /*< COMMIT */ + QUERY_TYPE_UNKNOWN = 0x000, /*< Initial value, can't be tested bitwisely */ + QUERY_TYPE_LOCAL_READ = 0x001, /*< Read non-database data, execute in MaxScale */ + QUERY_TYPE_READ = 0x002, /*< No updates */ + QUERY_TYPE_WRITE = 0x004, /*< Master data will be modified */ + QUERY_TYPE_SESSION_WRITE = 0x008, /*< Session data will be modified */ + QUERY_TYPE_GLOBAL_WRITE = 0x010, /*< Global system variable modification */ + QUERY_TYPE_BEGIN_TRX = 0x020, /*< BEGIN or START TRANSACTION */ + QUERY_TYPE_ENABLE_AUTOCOMMIT = 0x040,/*< SET autocommit=1 */ + QUERY_TYPE_DISABLE_AUTOCOMMIT = 0x080,/*< SET autocommit=0 */ + QUERY_TYPE_ROLLBACK = 0x100, /*< ROLLBACK */ + QUERY_TYPE_COMMIT = 0x200 /*< COMMIT */ } skygw_query_type_t; #define QUERY_IS_TYPE(mask,type) ((mask & type) == type) diff --git a/query_classifier/test/makefile b/query_classifier/test/makefile index 4ad30a16c..9e35c8b0e 100644 --- a/query_classifier/test/makefile +++ b/query_classifier/test/makefile @@ -5,6 +5,7 @@ include ../../build_gateway.inc include ../../makefile.inc +include ../../test.inc CC = gcc CPP = g++ @@ -48,7 +49,7 @@ buildtests: $(LDLIBS) $(LDMYSQL) runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test Query Classifier" >> $(TESTLOG) @@ -59,3 +60,4 @@ ifeq ($?, 0) else @echo "Query Classifier FAILED" >> $(TESTLOG) endif + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file diff --git a/server/MaxScale_template.cnf b/server/MaxScale_template.cnf index e89c2516a..ee7eb6e30 100644 --- a/server/MaxScale_template.cnf +++ b/server/MaxScale_template.cnf @@ -37,6 +37,8 @@ passwd=maxpwd # user= # passwd= # enable_root_user=<0 or 1, default is 0> +# version_string= # # Valid router modules currently are: # readwritesplit, readconnroute and debugcli diff --git a/server/core/.buffer.c.swp b/server/core/.buffer.c.swp deleted file mode 100644 index 5f600ef2f..000000000 Binary files a/server/core/.buffer.c.swp and /dev/null differ diff --git a/server/core/config.c b/server/core/config.c index d964ba5fa..4da5135e7 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -29,6 +29,7 @@ * 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 * * @endverbatim */ @@ -43,6 +44,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; @@ -56,6 +58,7 @@ static void check_config_objects(CONFIG_CONTEXT *context); static char *config_file = NULL; static GATEWAY_CONF gateway; +char *version_string = NULL; /** * Config item handler for the ini file reader @@ -116,6 +119,21 @@ config_load(char *file) CONFIG_CONTEXT config; int rval; + MYSQL *conn; + conn = mysql_init(NULL); + if (conn) { + if (mysql_real_connect(conn, NULL, NULL, NULL, NULL, 0, NULL, 0)) { + char *ptr; + version_string = (char *)mysql_get_server_info(conn); + ptr = strstr(version_string, "-embedded"); + if (ptr) { + *ptr = '\0'; + } + + } + mysql_close(conn); + } + global_defaults(); config.object = ""; @@ -146,6 +164,11 @@ int rval; if (!config_file) return 0; + + + if (gateway.version_string) + free(gateway.version_string); + global_defaults(); config.object = ""; @@ -202,7 +225,29 @@ 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 (version_string) { + ((SERVICE *)(obj->element))->version_string = strdup(version_string); + } else { + if (gateway.version_string) + ((SERVICE *)(obj->element))->version_string = strdup(gateway.version_string); + } + + if (obj->element == NULL) /*< if module load failed */ + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Reading configuration " + "for router service '%s' failed. " + "Router %s is not loaded.", + obj->object, + obj->object))); + obj = obj->next; + continue; /*< process next obj */ + } + if (enable_root_user) serviceEnableRootUser(obj->element, atoi(enable_root_user)); @@ -580,6 +625,10 @@ static void global_defaults() { gateway.n_threads = 1; + if (version_string != NULL) + gateway.version_string = strdup(version_string); + else + gateway.version_string = NULL; } /** @@ -622,6 +671,7 @@ SERVER *server; char *user; char *auth; char *enable_root_user; + char *version_string; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); @@ -629,6 +679,15 @@ SERVER *server; "user"); auth = config_get_value(obj->parameters, "passwd"); + + version_string = config_get_value(obj->parameters, "version_string"); + + if (version_string) { + if (service->version_string) + free(service->version_string); + service->version_string = strdup(version_string); + } + if (user && auth) { service_update(service, router, user, @@ -861,6 +920,7 @@ static char *service_params[] = "user", "passwd", "enable_root_user", + "version_string", NULL }; diff --git a/server/core/dcb.c b/server/core/dcb.c index 5ac95e0db..b187e9952 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -308,6 +308,13 @@ dcb_final_free(DCB *dcb) free(dcb->data); if (dcb->remote) free(dcb->remote); + + /* Consume dcb->delayq buffer */ + if (dcb->delayq) { + GWBUF *queue = dcb->delayq; + while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); + } + bitmask_free(&dcb->memdata.bitmask); simple_mutex_done(&dcb->dcb_read_lock); simple_mutex_done(&dcb->dcb_write_lock); diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 0a88b5348..28c95a3ae 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -53,6 +53,17 @@ static void register_module(const char *module, void *modobj); static void unregister_module(const char *module); +char* get_maxscale_home(void) +{ + char* home = getenv("MAXSCALE_HOME"); + if (home == NULL) + { + home = "/usr/local/skysql/MaxScale"; + } + return home; +} + + /** * Load the dynamic library related to a gateway module. The routine * will look for library files in the current directory, @@ -82,10 +93,10 @@ MODULES *mod; sprintf(fname, "./lib%s.so", module); if (access(fname, F_OK) == -1) { - if ((home = getenv("MAXSCALE_HOME")) == NULL) - home = "/usr/local/skysql/MaxScale"; + home = get_maxscale_home (); sprintf(fname, "%s/modules/lib%s.so", home, module); - if (access(fname, F_OK) == -1) + + if (access(fname, F_OK) == -1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -100,7 +111,7 @@ MODULES *mod; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load library for module: " - "%s, %s.", + "%s\n\t\t\t %s.", module, dlerror()))); return NULL; @@ -111,7 +122,7 @@ MODULES *mod; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Version interface not supported by " - "module: %s, %s.", + "module: %s\n\t\t\t %s.", module, dlerror()))); dlclose(dlhandle); @@ -134,7 +145,7 @@ MODULES *mod; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Expected entry point interface missing " - "from module: %s, %s.", + "from module: %s\n\t\t\t %s.", module, dlerror()))); dlclose(dlhandle); diff --git a/server/core/service.c b/server/core/service.c index db41f881a..e44b9c697 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -28,12 +28,15 @@ * 06/02/14 Massimiliano Pinto Added: serviceEnableRootUser routine * 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 * * @endverbatim */ #include #include #include +#include +#include #include #include #include @@ -70,11 +73,26 @@ SERVICE *service; return NULL; if ((service->router = load_module(router, MODULE_ROUTER)) == NULL) { + char* home = get_maxscale_home(); + char* ldpath = getenv("LD_LIBRARY_PATH"); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to load %s module \"%s\".\n\t\t\t" + " Ensure that lib%s.so exists in one of the " + "following directories :\n\t\t\t " + "- %s/modules\n\t\t\t - %s", + MODULE_ROUTER, + router, + router, + home, + ldpath))); free(service); return NULL; } service->name = strdup(servname); service->routerModule = strdup(router); + service->version_string = NULL; memset(&service->stats, 0, sizeof(SERVICE_STATS)); service->ports = NULL; service->stats.started = time(0); @@ -159,7 +177,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; @@ -625,7 +643,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 @@ -637,30 +655,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 + */ +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/test/makefile b/server/core/test/makefile index d0008b1ce..d3948575d 100644 --- a/server/core/test/makefile +++ b/server/core/test/makefile @@ -5,6 +5,7 @@ include ../../../build_gateway.inc include ../../../makefile.inc +include ../../../test.inc CC=cc TESTLOG := $(shell pwd)/testhash.log @@ -26,7 +27,7 @@ buildtests : testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test MaxScale core" >> $(TESTLOG) @@ -38,3 +39,4 @@ else @echo "MaxScale core FAILED" >> $(TESTLOG) endif @echo "" >> $(TESTLOG) + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file diff --git a/server/include/config.h b/server/include/config.h index 88620f015..03da9b029 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -24,8 +24,9 @@ * @verbatim * Revision History * - * Date Who Description - * 21/06/13 Mark Riddoch Initial implementation + * Date Who Description + * 21/06/13 Mark Riddoch Initial implementation + * 07/05/14 Massimiliano Pinto Added version_string to global configuration * * @endverbatim */ @@ -54,7 +55,8 @@ typedef struct config_context { * The gateway global configuration data */ typedef struct { - int n_threads; /**< Number of polling threads */ + int n_threads; /**< Number of polling threads */ + char *version_string; /**< The version string of embedded database library */ } GATEWAY_CONF; extern int config_load(char *); diff --git a/server/include/gateway_mysql.h b/server/include/gateway_mysql.h deleted file mode 100644 index 01c569bba..000000000 --- a/server/include/gateway_mysql.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is distributed as part of the SkySQL Gateway. 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 SkySQL Ab 2013 - * - */ - -/* - * MYSQL mysql protocol header file - * Revision History - * - * Date Who Description - * 10/06/13 Massimiliano Pinto Initial implementation - * - */ -#include - -/* 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)) - -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; - - -#define SMALL_CHUNK 1024 -#define MAX_CHUNK SMALL_CHUNK * 8 * 4 -#define ToHex(Y) (Y>='0'&&Y<='9'?Y-'0':Y-'A'+10) - -extern int mysql_send_ok(DCB *, int, int, const char *); -extern int MySQLSendHandshake(DCB *); diff --git a/server/include/modules.h b/server/include/modules.h index f3ec14f07..c90cf45a1 100644 --- a/server/include/modules.h +++ b/server/include/modules.h @@ -55,4 +55,6 @@ extern void *load_module(const char *module, const char *type); extern void unload_module(const char *module); extern void printModules(); extern void dprintAllModules(DCB *); +char* get_maxscale_home(void); + #endif diff --git a/server/include/service.h b/server/include/service.h index d52a7eccf..0a8db9ea2 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -38,6 +38,7 @@ * 23/06/13 Mark Riddoch Added service user and users * 06/02/14 Massimiliano Pinto Added service flag for root user access * 25/02/14 Massimiliano Pinto Added service refresh limit feature + * 07/05/14 Massimiliano Pinto Added version_string field to service struct * * @endverbatim */ @@ -108,6 +109,7 @@ 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 */ SERVICE_USER credentials; /**< The cedentials of the service user */ SPINLOCK spin; /**< The service spinlock */ diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 024a8bd88..17697f101 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -122,6 +122,8 @@ struct router_client_session { /*< cursor is pointer and status variable to current session command */ sescmd_cursor_t rses_cursor[BE_COUNT]; int rses_capabilities; /*< input type, for example */ + bool rses_autocommit_enabled; + bool rses_transaction_active; struct router_client_session* next; #if defined(SS_DEBUG) skygw_chk_t rses_chk_tail; @@ -155,4 +157,8 @@ typedef struct router_instance { struct router_instance* next; /*< Next router on the list */ } ROUTER_INSTANCE; +#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \ + (SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : \ + (SERVER_IS_JOINED((b)->backend_server) ? BE_JOINED : BE_UNDEFINED))); + #endif /*< _RWSPLITROUTER_H */ diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 5bd20f4f9..5e4c9bb21 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -33,6 +33,7 @@ * If current user is authenticated the new users' table will replace the old one * 28/02/2014 Massimiliano Pinto Added: client IPv4 in dcb->ipv4 and inet_ntop for string representation * 11/03/2014 Massimiliano Pinto Added: Unix socket support + * 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake * */ @@ -225,10 +226,21 @@ MySQLSendHandshake(DCB* dcb) uint8_t mysql_filler_ten[10]; uint8_t mysql_last_byte = 0x00; char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]=""; + char *version_string; + int len_version_string=0; MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); GWBUF *buf; + /* get the version string from service property if available*/ + if (dcb->service->version_string != NULL) { + version_string = dcb->service->version_string; + len_version_string = strlen(version_string); + } else { + version_string = GW_MYSQL_VERSION; + len_version_string = strlen(GW_MYSQL_VERSION); + } + gw_generate_random_str(server_scramble, GW_MYSQL_SCRAMBLE_SIZE); // copy back to the caller @@ -245,7 +257,7 @@ MySQLSendHandshake(DCB* dcb) memcpy(mysql_plugin_data, server_scramble + 8, 12); - mysql_payload_size = sizeof(mysql_protocol_version) + (strlen(GW_MYSQL_VERSION) + 1) + sizeof(mysql_thread_id) + 8 + sizeof(mysql_filler) + sizeof(mysql_server_capabilities_one) + sizeof(mysql_server_language) + sizeof(mysql_server_status) + sizeof(mysql_server_capabilities_two) + sizeof(mysql_scramble_len) + sizeof(mysql_filler_ten) + 12 + sizeof(mysql_last_byte) + strlen("mysql_native_password") + sizeof(mysql_last_byte); + mysql_payload_size = sizeof(mysql_protocol_version) + (len_version_string + 1) + sizeof(mysql_thread_id) + 8 + sizeof(mysql_filler) + sizeof(mysql_server_capabilities_one) + sizeof(mysql_server_language) + sizeof(mysql_server_status) + sizeof(mysql_server_capabilities_two) + sizeof(mysql_scramble_len) + sizeof(mysql_filler_ten) + 12 + sizeof(mysql_last_byte) + strlen("mysql_native_password") + sizeof(mysql_last_byte); // allocate memory for packet header + payload if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL) @@ -271,8 +283,9 @@ MySQLSendHandshake(DCB* dcb) mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_protocol_version); // write server version plus 0 filler - strcpy((char *)mysql_handshake_payload, GW_MYSQL_VERSION); - mysql_handshake_payload = mysql_handshake_payload + strlen(GW_MYSQL_VERSION); + strcpy((char *)mysql_handshake_payload, version_string); + mysql_handshake_payload = mysql_handshake_payload + len_version_string; + *mysql_handshake_payload = 0x00; mysql_handshake_payload++; diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 8bbc45c3b..1737a0f76 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -88,4 +88,20 @@ install: $(MODULES) (cd readwritesplit; make DEST=$(DEST) install) (cd binlog; make DEST=$(DEST) install) +cleantests: + $(MAKE) -C readwritesplit/test cleantests + $(MAKE) -C test cleantests + +buildtests: + $(MAKE) -C readwritesplit/test DEBUG=Y buildtests + $(MAKE) -C test DEBUG=Y buildtests + +runtests: + $(MAKE) -C test runtests + $(MAKE) -C readwritesplit runtests + +testall: + $(MAKE) -C test testall + $(MAKE) -C readwritesplit testall + include depend.mk diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile index 5bef1abe7..2d4aba7f0 100644 --- a/server/modules/routing/readwritesplit/Makefile +++ b/server/modules/routing/readwritesplit/Makefile @@ -20,9 +20,9 @@ include ../../../../build_gateway.inc -LOGPATH := $(ROOT_PATH)/log_manager -UTILSPATH := $(ROOT_PATH)/utils -QCLASSPATH := $(ROOT_PATH)/query_classifier +LOGPATH := $(ROOT_PATH)/log_manager +UTILSPATH := $(ROOT_PATH)/utils +QCLASSPATH := $(ROOT_PATH)/query_classifier CC=cc CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include \ @@ -61,5 +61,18 @@ depend: install: $(MODULES) install -D $(MODULES) $(DEST)/MaxScale/modules + +cleantests: + $(MAKE) -C test cleantest + +testall: + $(MAKE) -C test testall + +buildtests: + $(MAKE) -C test buildtests + +runtests: + $(MAKE) -C runtests + include depend.mk diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 5383c1eb6..dcea42d19 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -74,7 +74,7 @@ static uint8_t getCapabilities (ROUTER* inst, void* router_session); static bool search_backend_servers( BACKEND** p_master, BACKEND** p_slave, - ROUTER_INSTANCE* router); + ROUTER_INSTANCE* router); static ROUTER_OBJECT MyObject = { createInstance, @@ -157,6 +157,13 @@ static void tracelog_routed_query( DCB* dcb, GWBUF* buf); +static bool route_session_write( + ROUTER_CLIENT_SES* router_client_ses, + GWBUF* querybuf, + ROUTER_INSTANCE* inst, + unsigned char packet_type, + skygw_query_type_t qtype); + static SPINLOCK instlock; static ROUTER_INSTANCE* instances; @@ -211,10 +218,10 @@ static ROUTER* createInstance( SERVICE* service, char** options) { - ROUTER_INSTANCE* router; - SERVER* server; + ROUTER_INSTANCE* router; + SERVER* server; int n; - int i; + int i; if ((router = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; @@ -244,7 +251,6 @@ static ROUTER* createInstance( "module but none are supported. The options will be " "ignored."))); } - /** * Create an array of the backend servers in the router structure to * maintain a count of the number of connections to each @@ -266,7 +272,7 @@ static ROUTER* createInstance( router->servers[n]->backend_conn_count = 0; n += 1; server = server->nextdb; - } + } router->servers[n] = NULL; /** @@ -300,7 +306,7 @@ static ROUTER* createInstance( } } } - /** + /** * We have completed the creation of the router data, so now * insert this router into the linked list of routers * that have been created with this module. @@ -329,12 +335,12 @@ static void* newSession( { BACKEND* local_backend[BE_COUNT]; ROUTER_CLIENT_SES* client_rses; - ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_inst; + ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_inst; bool succp; - + client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); - + if (client_rses == NULL) { ss_dassert(false); @@ -345,7 +351,7 @@ static void* newSession( #if defined(SS_DEBUG) client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; -#endif +#endif /** store pointers to sescmd list to both cursors */ client_rses->rses_cursor[BE_MASTER].scmd_cur_rses = client_rses; client_rses->rses_cursor[BE_MASTER].scmd_cur_active = false; @@ -360,8 +366,8 @@ static void* newSession( &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; client_rses->rses_cursor[BE_SLAVE].scmd_cur_cmd = NULL; client_rses->rses_cursor[BE_SLAVE].scmd_cur_be_type = BE_SLAVE; - - /** + + /** * 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. @@ -369,7 +375,7 @@ static void* newSession( succp = search_backend_servers(&local_backend[BE_MASTER], &local_backend[BE_SLAVE], router); - + /** Both Master and Slave must be found */ if (!succp) { free(client_rses); @@ -385,23 +391,23 @@ static void* newSession( if (client_rses->rses_dcb[BE_SLAVE] == NULL) { ss_dassert(session->refcount == 1); - free(client_rses); + free(client_rses); return NULL; - } - /** + } + /** * Open the master connection. - */ + */ client_rses->rses_dcb[BE_MASTER] = dcb_connect( local_backend[BE_MASTER]->backend_server, session, local_backend[BE_MASTER]->backend_server->protocol); if (client_rses->rses_dcb[BE_MASTER] == NULL) - { + { /** Close slave connection first. */ client_rses->rses_dcb[BE_SLAVE]->func.close(client_rses->rses_dcb[BE_SLAVE]); free(client_rses); return NULL; - } + } /** * We now have a master and a slave server with the least connections. * Bump the connection counts for these servers. @@ -411,7 +417,7 @@ static void* newSession( client_rses->rses_backend[BE_SLAVE] = local_backend[BE_SLAVE]; client_rses->rses_backend[BE_MASTER] = local_backend[BE_MASTER]; - router->stats.n_sessions += 1; + router->stats.n_sessions += 1; client_rses->rses_capabilities = RCAP_TYPE_STMT_INPUT; /** @@ -423,15 +429,17 @@ static void* newSession( * Add this session to end of the list of active sessions in router. */ spinlock_acquire(&router->lock); - client_rses->next = router->connections; + client_rses->next = router->connections; router->connections = client_rses; spinlock_release(&router->lock); - CHK_CLIENT_RSES(client_rses); + CHK_CLIENT_RSES(client_rses); return (void *)client_rses; } + + /** * Close a session with the router, this is the mechanism * by which a router may cleanup data structure etc. @@ -446,7 +454,7 @@ static void closeSession( ROUTER_CLIENT_SES* router_cli_ses; DCB* slave_dcb; DCB* master_dcb; - + router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); /** @@ -457,7 +465,7 @@ static void closeSession( /** decrease server current connection counters */ atomic_add(&router_cli_ses->rses_backend[BE_SLAVE]->backend_server->stats.n_current, -1); atomic_add(&router_cli_ses->rses_backend[BE_MASTER]->backend_server->stats.n_current, -1); - + slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; router_cli_ses->rses_dcb[BE_SLAVE] = NULL; master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; @@ -466,14 +474,14 @@ static void closeSession( router_cli_ses->rses_closed = true; /** Unlock */ rses_end_locked_router_action(router_cli_ses); - - /** + + /** * Close the backend server connections */ if (slave_dcb != NULL) { CHK_DCB(slave_dcb); slave_dcb->func.close(slave_dcb); - } + } if (master_dcb != NULL) { master_dcb->func.close(master_dcb); @@ -491,8 +499,8 @@ static void freeSession( int i; router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; - router = (ROUTER_INSTANCE *)router_instance; - + router = (ROUTER_INSTANCE *)router_instance; + atomic_add(&router_cli_ses->rses_backend[BE_SLAVE]->backend_conn_count, -1); atomic_add(&router_cli_ses->rses_backend[BE_MASTER]->backend_conn_count, -1); @@ -560,24 +568,26 @@ static int routeQuery( void* router_session, GWBUF* querybuf) { - skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - GWBUF* plainsqlbuf = NULL; - char* querystr = NULL; + skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; + GWBUF* plainsqlbuf = NULL; + char* querystr = NULL; char* startpos; unsigned char packet_type; uint8_t* packet; int ret = 0; - DCB* master_dcb = NULL; - DCB* slave_dcb = NULL; + DCB* master_dcb = NULL; + DCB* slave_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; bool rses_is_closed; rses_property_t* prop; size_t len; - static bool transaction_active; + /** if false everything goes to master and session commands to slave too */ + static bool autocommit_enabled = true; + /** if true everything goes to master and session commands to slave too */ + static bool transaction_active = false; CHK_CLIENT_RSES(router_cli_ses); - /** Dirty read for quick check if router is closed. */ if (router_cli_ses->rses_closed) @@ -609,16 +619,16 @@ static int routeQuery( "Error: Failed to route %s:%s:\"%s\" to " "backend server. %s.", STRPACKETTYPE(packet_type), - STRQTYPE(qtype), - (querystr == NULL ? "(empty)" : querystr), - (rses_is_closed ? "Router was closed" : - "Router has no backend servers where to " - "route to")))); + STRQTYPE(qtype), + (querystr == NULL ? "(empty)" : querystr), + (rses_is_closed ? "Router was closed" : + "Router has no backend servers where to " + "route to")))); goto return_ret; } inst->stats.n_queries++; startpos = (char *)&packet[5]; - + switch(packet_type) { case COM_QUIT: /**< 1 QUIT will close all sessions */ case COM_INIT_DB: /**< 2 DDL must go to the master */ @@ -647,7 +657,7 @@ static int routeQuery( querystr = master_dcb->func.getquerystr( (void *) gwbuf_clone(querybuf), &querystr_is_copy); - */ + */ qtype = skygw_query_classifier_get_type(querystr, 0); break; @@ -669,207 +679,102 @@ static int routeQuery( LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Packet type\t%s", STRPACKETTYPE(packet_type)))); - - if (QUERY_IS_TYPE(qtype,QUERY_TYPE_COMMIT) && - transaction_active) + /** + * If autocommit is disabled or transaction is explicitly started + * transaction becomes active and master gets all statements until + * transaction is committed and autocommit is enabled again. + */ + if (router_cli_ses->rses_autocommit_enabled && + QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) { - transaction_active = false; + router_cli_ses->rses_autocommit_enabled = false; + + if (!router_cli_ses->rses_transaction_active) + { + router_cli_ses->rses_transaction_active = true; + } + } + else if (!router_cli_ses->rses_transaction_active && + QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX)) + { + router_cli_ses->rses_transaction_active = true; } - - - switch (qtype) { - case QUERY_TYPE_WRITE: - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Query type\t%s, " - "routing to Master.", - pthread_self(), - STRQTYPE(qtype)))); - - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "routeQuery", - master_dcb, - gwbuf_clone(querybuf))); - - ret = master_dcb->func.write(master_dcb, querybuf); - atomic_add(&inst->stats.n_master, 1); - - goto return_ret; - break; - - case QUERY_TYPE_READ: - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Query type\t%s, " - "routing to %s.", - pthread_self(), - STRQTYPE(qtype), - (transaction_active ? "Master" : "Slave")))); - - LOGIF(LT, tracelog_routed_query( + /** + * Explicit COMMIT and ROLLBACK, implicit COMMIT. + */ + if (router_cli_ses->rses_autocommit_enabled && + router_cli_ses->rses_transaction_active && + (QUERY_IS_TYPE(qtype,QUERY_TYPE_COMMIT) || + QUERY_IS_TYPE(qtype,QUERY_TYPE_ROLLBACK))) + { + router_cli_ses->rses_transaction_active = false; + } + else if (!router_cli_ses->rses_autocommit_enabled && + QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) + { + router_cli_ses->rses_autocommit_enabled = true; + router_cli_ses->rses_transaction_active = false; + } + /** + * Session update is always routed in the same way. + */ + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE)) + { + bool succp = route_session_write( router_cli_ses, - "routeQuery", - (transaction_active ? master_dcb : slave_dcb), - gwbuf_clone(querybuf))); - - if (transaction_active) - { - ret = master_dcb->func.write(master_dcb, querybuf); - } - else - { - ret = slave_dcb->func.write(slave_dcb, querybuf); - } - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Routed.", - pthread_self()))); - - - atomic_add(&inst->stats.n_slave, 1); - goto return_ret; - break; + querybuf, + inst, + packet_type, + qtype); - case QUERY_TYPE_SESSION_WRITE: - /** - * Execute in backends used by current router session. - * Save session variable commands to router session property - * struct. Thus, they can be replayed in backends which are - * started and joined later. - * - * Suppress redundant OK packets sent by backends. - * - * DOES THIS ALL APPLY TO COM_QUIT AS WELL?? - * - * The first OK packet is replied to the client. - * - */ - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] DCB M:%p s:%p, " - "Query type\t%s, " - "packet type %s, routing to all servers.", - pthread_self(), - master_dcb, - slave_dcb, - STRQTYPE(qtype), - STRPACKETTYPE(packet_type)))); - /** - * COM_QUIT is one-way message. Server doesn't respond to that. - * Therefore reply processing is unnecessary and session - * command property is not needed. It is just routed to both - * backends. - */ - if (packet_type == COM_QUIT) - { - int rc; - int rc2; - - rc = master_dcb->func.write(master_dcb, gwbuf_clone(querybuf)); - rc2 = slave_dcb->func.write(slave_dcb, querybuf); - - if (rc == 1 && rc == rc2) - { - ret = 1; - } - goto return_ret; - } - prop = rses_property_init(RSES_PROP_TYPE_SESCMD); - /** - * Additional reference is created to querybuf to - * prevent it from being released before properties - * are cleaned up as a part of router sessionclean-up. - */ - mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); - - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - rses_property_done(prop); - goto return_ret; - } - /** Add sescmd property to router client session */ - rses_property_add(router_cli_ses, prop); - - /** Execute session command in master */ - if (execute_sescmd_in_backend(router_cli_ses, BE_MASTER)) - { - ret = 1; - } - else - { - /** Log error */ - } - /** Execute session command in slave */ - if (execute_sescmd_in_backend(router_cli_ses, BE_SLAVE)) + if (succp) { ret = 1; } - else - { - /** Log error */ - } - - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - - atomic_add(&inst->stats.n_all, 1); + ss_dassert(succp); + ss_dassert(ret == 1); goto return_ret; - break; - - case QUERY_TYPE_BEGIN_TRX: - transaction_active = true; - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "routeQuery", - master_dcb, - gwbuf_clone(querybuf))); - ret = master_dcb->func.write(master_dcb, querybuf); - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Routed.", - pthread_self()))); - atomic_add(&inst->stats.n_master, 1); - goto return_ret; - break; + } + else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && + !router_cli_ses->rses_transaction_active) + { + bool succp; - case QUERY_TYPE_COMMIT: - case QUERY_TYPE_ROLLBACK: - transaction_active = false; - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "routeQuery", - master_dcb, - gwbuf_clone(querybuf))); - ret = master_dcb->func.write(master_dcb, querybuf); - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Routed.", - pthread_self()))); - atomic_add(&inst->stats.n_master, 1); - goto return_ret; - break; - - default: LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "%lu [routeQuery:rwsplit] Query type\t%s, " - "routing to Master by default.", - pthread_self(), - STRQTYPE(qtype)))); + LOGFILE_TRACE, + "Read-only query, routing to Slave."))); + ss_dassert(QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)); - /** - * Is this really ok? - * What is not known is routed to master. - */ - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "routeQuery", - master_dcb, - gwbuf_clone(querybuf))); + ret = slave_dcb->func.write(slave_dcb, querybuf); + atomic_add(&inst->stats.n_slave, 1); - ret = master_dcb->func.write(master_dcb, querybuf); - atomic_add(&inst->stats.n_master, 1); goto return_ret; - break; - } /*< switch by query type */ + } + else + { + bool succp = true; + + if (LOG_IS_ENABLED(LOGFILE_TRACE)) + { + if (router_cli_ses->rses_transaction_active) /*< all to master */ + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Transaction is active, routing to Master."))); + } + else + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Begin transaction, write or unspecified type, " + "routing to Master."))); + } + } + ret = master_dcb->func.write(master_dcb, querybuf); + atomic_add(&inst->stats.n_master, 1); + + goto return_ret; + } return_ret: if (plainsqlbuf != NULL) @@ -1036,8 +941,7 @@ static void clientReply( client_dcb = backend_dcb->session->client; /** Unlock */ - rses_end_locked_router_action(router_cli_ses); - + rses_end_locked_router_action(router_cli_ses); /** * 1. Check if backend received reply to sescmd. * 2. Check sescmd's state whether OK_PACKET has been @@ -1057,14 +961,14 @@ static void clientReply( } if (backend_dcb == master_dcb) - { + { be_type = BE_MASTER; - } + } else if (backend_dcb == slave_dcb) - { + { be_type = BE_SLAVE; - } - LOGIF(LT, tracelog_routed_query(router_cli_ses, + } + LOGIF(LT, tracelog_routed_query(router_cli_ses, "reply_by_statement", backend_dcb, gwbuf_clone(writebuf))); @@ -1085,8 +989,7 @@ static void clientReply( { writebuf = sescmd_cursor_process_replies(client_dcb, writebuf, - scur); - + scur); } /** Unlock router session */ rses_end_locked_router_action(router_cli_ses); @@ -1123,7 +1026,7 @@ lock_failed: * * @param inst - in, use * Pointer to router instance - * + * * @return true, if all what what requested found, false if the request * was not satisfied or was partially satisfied. * @@ -1134,12 +1037,12 @@ lock_failed: static bool search_backend_servers( BACKEND** p_master, BACKEND** p_slave, - ROUTER_INSTANCE* router) -{ + ROUTER_INSTANCE* router) +{ BACKEND* local_backend[BE_COUNT] = {NULL,NULL}; - int i; + int i; bool succp = true; - + /* * Loop over all the servers and find any that have fewer connections * than current candidate server. @@ -1154,32 +1057,32 @@ static bool search_backend_servers( * very low load. * * If master is searched for, the first master found is chosen. - */ + */ for (i = 0; router->servers[i] != NULL; i++) { BACKEND* be = router->servers[i]; if (be != NULL) { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, "%lu [search_backend_servers] Examine server " - "%s:%d with %d connections. Status is %d, " - "router->bitvalue is %d", - pthread_self(), + "%s:%d with %d connections. Status is %d, " + "router->bitvalue is %d", + pthread_self(), be->backend_server->name, be->backend_server->port, be->backend_conn_count, be->backend_server->status, - router->bitmask))); + router->bitmask))); } - + if (be != NULL && SERVER_IS_RUNNING(be->backend_server) && (be->backend_server->status & router->bitmask) == router->bitvalue) - { + { if (SERVER_IS_SLAVE(be->backend_server) && p_slave != NULL) - { + { /** * If no candidate set, set first running * server as an initial candidate server. @@ -1187,7 +1090,7 @@ static bool search_backend_servers( if (local_backend[BE_SLAVE] == NULL) { local_backend[BE_SLAVE] = be; - } + } else if (be->backend_conn_count < local_backend[BE_SLAVE]->backend_conn_count) { @@ -1203,74 +1106,74 @@ static bool search_backend_servers( be->backend_server->stats.n_connections < local_backend[BE_SLAVE]->backend_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. - */ + */ local_backend[BE_SLAVE] = be; - } - } + } + } else if (p_master != NULL && local_backend[BE_MASTER] == NULL && SERVER_IS_MASTER(be->backend_server)) - { + { local_backend[BE_MASTER] = be; - } + } else if (p_master != NULL && local_backend[BE_JOINED] == NULL && SERVER_IS_JOINED(be->backend_server)) - { + { local_backend[BE_JOINED] = be; - } - } - } + } + } + } if (router->bitvalue != 0 && p_master != NULL && local_backend[BE_JOINED] == NULL) - { + { succp = false; LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, + LOGFILE_ERROR, "Error : Couldn't find a Joined Galera node from %d " - "candidates.", + "candidates.", i))); goto return_succp; } - + if (p_slave != NULL && local_backend[BE_SLAVE] == NULL) { succp = false; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Couldn't find suitable Slave from %d " - "candidates.", + "candidates.", i))); } - + if (p_master != NULL && local_backend[BE_MASTER] == NULL) { succp = false; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Couldn't find suitable Master from %d " - "candidates.", + "candidates.", i))); - } + } if (local_backend[BE_SLAVE] != NULL) { *p_slave = local_backend[BE_SLAVE]; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, "%lu [readwritesplit:search_backend_servers] Selected " "Slave %s:%d from %d candidates.", pthread_self(), local_backend[BE_SLAVE]->backend_server->name, local_backend[BE_SLAVE]->backend_server->port, i))); - } + } if (local_backend[BE_MASTER] != NULL) { *p_master = local_backend[BE_MASTER]; LOGIF(LT, (skygw_log_write( @@ -1630,7 +1533,7 @@ static bool execute_sescmd_in_backend( sescmd_cursor_t* scur; dcb = rses->rses_dcb[be_type]; - + CHK_DCB(dcb); CHK_CLIENT_RSES(rses); ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); @@ -1665,7 +1568,7 @@ static bool execute_sescmd_in_backend( dcb->session, sescmd_cursor_clone_querybuf(scur)); break; - + case COM_QUIT: case COM_QUERY: case COM_INIT_DB: @@ -1690,6 +1593,7 @@ return_succp: return succp; } + /** * Moves cursor to next property and copied address of its sescmd to cursor. * Current propery must be non-null. @@ -1767,7 +1671,6 @@ static rses_property_t* mysql_sescmd_get_property( return scmd->my_sescmd_prop; } - static void tracelog_routed_query( ROUTER_CLIENT_SES* rses, char* funcname, @@ -1781,7 +1684,7 @@ static void tracelog_routed_query( char* querystr; char* startpos = (char *)&packet[5]; backend_type_t be_type; - + if (rses->rses_dcb[BE_MASTER] == dcb) { be_type = BE_MASTER; @@ -1824,11 +1727,13 @@ static void tracelog_routed_query( -1)), STRBETYPE(be_type), dcb))); + free(querystr); } } gwbuf_free(buf); } + /** * Return rc, rc < 0 if router session is closed. rc == 0 if there are no * capabilities specified, rc > 0 when there are capabilities. @@ -1851,4 +1756,103 @@ static uint8_t getCapabilities ( return_rc: return rc; -} \ No newline at end of file +} + +/** + * Execute in backends used by current router session. + * Save session variable commands to router session property + * struct. Thus, they can be replayed in backends which are + * started and joined later. + * + * Suppress redundant OK packets sent by backends. + * + * The first OK packet is replied to the client. + * Return true if succeed, false is returned if router session was closed or + * if execute_sescmd_in_backend failed. + */ +static bool route_session_write( + ROUTER_CLIENT_SES* router_cli_ses, + GWBUF* querybuf, + ROUTER_INSTANCE* inst, + unsigned char packet_type, + skygw_query_type_t qtype) +{ + bool succp; + DCB* master_dcb; + DCB* slave_dcb; + rses_property_t* prop; + + master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; + slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; + CHK_DCB(master_dcb); + CHK_DCB(slave_dcb); + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Session write, query type\t%s, packet type %s, " + "routing to all servers.", + STRQTYPE(qtype), + STRPACKETTYPE(packet_type)))); + /** + * COM_QUIT is one-way message. Server doesn't respond to that. + * Therefore reply processing is unnecessary and session + * command property is not needed. It is just routed to both + * backends. + */ + if (packet_type == COM_QUIT) + { + int rc; + int rc2; + + rc = master_dcb->func.write(master_dcb, gwbuf_clone(querybuf)); + rc2 = slave_dcb->func.write(slave_dcb, querybuf); + + if (rc == 1 && rc == rc2) + { + succp = true; + } + goto return_succp; + } + prop = rses_property_init(RSES_PROP_TYPE_SESCMD); + /** + * Additional reference is created to querybuf to + * prevent it from being released before properties + * are cleaned up as a part of router sessionclean-up. + */ + mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); + + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + rses_property_done(prop); + succp = false; + goto return_succp; + } + /** Add sescmd property to router client session */ + rses_property_add(router_cli_ses, prop); + + /** Execute session command in master */ + succp = execute_sescmd_in_backend(router_cli_ses, BE_MASTER); + + if (!succp) + { + /** Unlock router session */ + rses_end_locked_router_action(router_cli_ses); + goto return_succp; + } + /** Execute session command in slave */ + succp = execute_sescmd_in_backend(router_cli_ses, BE_SLAVE); + if (!succp) + { + /** Unlock router session */ + rses_end_locked_router_action(router_cli_ses); + goto return_succp; + } + /** Unlock router session */ + rses_end_locked_router_action(router_cli_ses); + + atomic_add(&inst->stats.n_all, 1); + +return_succp: + return succp; +} + diff --git a/server/modules/routing/readwritesplit/test/makefile b/server/modules/routing/readwritesplit/test/makefile index 096e28fe7..87d22363d 100644 --- a/server/modules/routing/readwritesplit/test/makefile +++ b/server/modules/routing/readwritesplit/test/makefile @@ -8,7 +8,7 @@ include $(ROOT_PATH)/makefile.inc include $(ROOT_PATH)/test.inc CC=cc -TESTLOG := $(shell pwd)/testrouters.log +TESTLOG := $(shell pwd)/testrwsplit.log RET := -1 cleantests: @@ -25,17 +25,11 @@ buildtests: runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) - @echo "Test MaxScale R/W Split" >> $(TESTLOG) + @echo "Test Read/Write split router" >> $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) ./rwsplit.sh $(TESTLOG) $(THOST) $(TPORT_RW) $(TMASTER_ID) $(TUSER) $(TPWD) @echo "" >> $(TESTLOG) - - -pesce: - @echo "fine" - - - \ No newline at end of file + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file diff --git a/server/modules/routing/readwritesplit/test/rwsplit.sh b/server/modules/routing/readwritesplit/test/rwsplit.sh index 4755e4526..d64fcf495 100755 --- a/server/modules/routing/readwritesplit/test/rwsplit.sh +++ b/server/modules/routing/readwritesplit/test/rwsplit.sh @@ -30,9 +30,46 @@ else echo "$TINPUT PASSED">>$TLOG ; fi +TINPUT=test_transaction_routing2b.sql +TRETVAL=0 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + TINPUT=test_transaction_routing3.sql TRETVAL=2 a=`$RUNCMD < ./$TINPUT` +if [ "$a" == "$TMASTER_ID" ]; then + echo "$TINPUT FAILED, return value $a when one of the slave IDs was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +TINPUT=test_transaction_routing3b.sql +TRETVAL=2 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" == "$TMASTER_ID" ]; then + echo "$TINPUT FAILED, return value $a when one of the slave IDs was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +# test implicit transaction, that is, not started explicitly, autocommit=0 +TINPUT=test_transaction_routing4.sql +TRETVAL=0 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +TINPUT=test_transaction_routing4b.sql +TRETVAL=0 +a=`$RUNCMD < ./$TINPUT` if [ "$a" != "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; else @@ -113,3 +150,44 @@ if [ "$a" == "$TRETVAL" ]; then else echo "$TINPUT PASSED">>$TLOG ; fi + +TINPUT=test_autocommit_disabled1.sql +TRETVAL=1 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +TINPUT=test_autocommit_disabled1b.sql +TRETVAL=1 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +# Disable autocommit in the first session and then test in new session that +# it is again enabled. +TINPUT=test_autocommit_disabled2.sql +TRETVAL=1 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +TINPUT=set_autocommit_disabled.sql +`$RUNCMD < ./$TINPUT` +TINPUT=test_after_autocommit_disabled.sql +TRETVAL=$TMASTER_ID +a=`$RUNCMD < ./$TINPUT` +if [ "$a" == "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + diff --git a/server/modules/routing/readwritesplit/test/set_autocommit_disabled.sql b/server/modules/routing/readwritesplit/test/set_autocommit_disabled.sql new file mode 100644 index 000000000..a182b4922 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/set_autocommit_disabled.sql @@ -0,0 +1,7 @@ +use test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; -- open transaction +begin; +insert into t1 values(1); -- write to master +commit; diff --git a/server/modules/routing/readwritesplit/test/test_after_autocommit_disabled.sql b/server/modules/routing/readwritesplit/test/test_after_autocommit_disabled.sql new file mode 100644 index 000000000..f10c5eb8c --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_after_autocommit_disabled.sql @@ -0,0 +1,2 @@ +use test; +select @@server_id; \ No newline at end of file diff --git a/server/modules/routing/readwritesplit/test/test_autocommit_disabled1.sql b/server/modules/routing/readwritesplit/test/test_autocommit_disabled1.sql new file mode 100644 index 000000000..1f3eb28fa --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_autocommit_disabled1.sql @@ -0,0 +1,7 @@ +use test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; -- open transaction +insert into t1 values(1); -- write to master +select count(*) from t1; -- read from master +drop table t1; diff --git a/server/modules/routing/readwritesplit/test/test_autocommit_disabled1b.sql b/server/modules/routing/readwritesplit/test/test_autocommit_disabled1b.sql new file mode 100644 index 000000000..fb3ff8d59 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_autocommit_disabled1b.sql @@ -0,0 +1,7 @@ +use test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=OFF; -- open transaction +insert into t1 values(1); -- write to master +select count(*) from t1; -- read from master +drop table t1; diff --git a/server/modules/routing/readwritesplit/test/test_autocommit_disabled2.sql b/server/modules/routing/readwritesplit/test/test_autocommit_disabled2.sql new file mode 100644 index 000000000..04be4024e --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_autocommit_disabled2.sql @@ -0,0 +1,9 @@ +use test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; -- open transaction +begin; +insert into t1 values(1); -- write to master +commit; +select count(*) from t1; -- read from master since autocommit is disabled +drop table t1; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql index 663c32efa..2b10bdcbb 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql @@ -1,5 +1,5 @@ DROP DATABASE If EXISTS FOO; -SET autocommit=0; +SET autocommit=1; BEGIN; CREATE DATABASE FOO; -- implicit commit SELECT (@@server_id) INTO @a; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql index 80f0ebad1..fbca8b34e 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql @@ -1,7 +1,7 @@ USE test; DROP TABLE IF EXISTS T1; DROP EVENT IF EXISTS myevent; -SET autocommit=0; +SET autocommit=1; BEGIN; CREATE TABLE T1 (id integer); CREATE EVENT myevent diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql index 4db36fdd5..c8c433c21 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql @@ -1,6 +1,6 @@ USE test; DROP TABLE IF EXISTS T1; -SET autocommit=0; +SET autocommit=1; BEGIN; CREATE TABLE T1 (id integer); -- implicit commit SELECT (@@server_id) INTO @a; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql index 3656f1af8..50b175b37 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql @@ -1,6 +1,6 @@ USE test; DROP PROCEDURE IF EXISTS simpleproc; -SET autocommit=0; +SET autocommit=1; BEGIN; DELIMITER // CREATE PROCEDURE simpleproc (OUT param1 INT) diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql index ba896966d..18f1c39ef 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql @@ -1,6 +1,6 @@ USE test; DROP FUNCTION IF EXISTS hello; -SET autocommit=0; +SET autocommit=1; BEGIN; CREATE FUNCTION hello (s CHAR(20)) RETURNS CHAR(50) DETERMINISTIC diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql index 9b3c5bac7..f45b8c516 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql @@ -1,7 +1,7 @@ USE test; DROP TABLE IF EXISTS T1; CREATE TABLE T1 (id integer); -- implicit commit -SET autocommit=0; +SET autocommit=1; BEGIN; CREATE INDEX foo_t1 on T1 (id); -- implicit commit SELECT (@@server_id) INTO @a; diff --git a/server/modules/routing/readwritesplit/test/test_transaction_routing1.sql b/server/modules/routing/readwritesplit/test/test_transaction_routing1.sql deleted file mode 100644 index af17eb3d5..000000000 --- a/server/modules/routing/readwritesplit/test/test_transaction_routing1.sql +++ /dev/null @@ -1,19 +0,0 @@ -USE test; -SET autocommit = 0; -SET @a= -1; -SET @b= -2; -START TRANSACTION; -CREATE TABLE IF NOT EXISTS myCity (a int, b char(20)); -INSERT INTO myCity VALUES (1, 'Milan'); -INSERT INTO myCity VALUES (2, 'London'); -COMMIT; -START TRANSACTION; -DELETE FROM myCity; -SET @a = (SELECT COUNT(*) FROM myCity); -ROLLBACK; -START TRANSACTION; -SET @b = (SELECT COUNT(*) FROM myCity); -START TRANSACTION; -DROP TABLE myCity; -SELECT (@a+@b) AS res; -COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_transaction_routing3.sql b/server/modules/routing/readwritesplit/test/test_transaction_routing2b.sql similarity index 80% rename from server/modules/routing/readwritesplit/test/test_transaction_routing3.sql rename to server/modules/routing/readwritesplit/test/test_transaction_routing2b.sql index 895a88d80..4e8c65d35 100644 --- a/server/modules/routing/readwritesplit/test/test_transaction_routing3.sql +++ b/server/modules/routing/readwritesplit/test/test_transaction_routing2b.sql @@ -1,10 +1,11 @@ USE test; -SET autocommit = 0; +SET autocommit = Off; START TRANSACTION; CREATE TABLE IF NOT EXISTS myCity (a int, b char(20)); INSERT INTO myCity VALUES (1, 'Milan'); INSERT INTO myCity VALUES (2, 'London'); -COMMIT; +COMMIT; +START TRANSACTION; DELETE FROM myCity; -SELECT COUNT(*) FROM myCity; -- read transaction's modifications from slave +SELECT COUNT(*) FROM myCity; -- read transaction's modifications from master COMMIT; \ No newline at end of file diff --git a/server/modules/routing/readwritesplit/test/test_transaction_routing3b.sql b/server/modules/routing/readwritesplit/test/test_transaction_routing3b.sql new file mode 100644 index 000000000..aa59f17ef --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_transaction_routing3b.sql @@ -0,0 +1,7 @@ +-- Read from slave after implicit COMMIT +USE test; +START TRANSACTION; +CREATE TABLE IF NOT EXISTS T2 (id integer); +INSERT INTO T2 VALUES (@@server_id); +SET AUTOCOMMIT=oN; +SELECT id from T2; -- read transaction's modifications from slave diff --git a/server/modules/routing/readwritesplit/test/test_transaction_routing4.sql b/server/modules/routing/readwritesplit/test/test_transaction_routing4.sql new file mode 100644 index 000000000..6ba4ccc04 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_transaction_routing4.sql @@ -0,0 +1,9 @@ +USE test; +SET autocommit = 0; +CREATE TABLE IF NOT EXISTS myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; -- implicit transaction started +SELECT COUNT(*) FROM myCity; -- read transaction's modifications from master +COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_transaction_routing4b.sql b/server/modules/routing/readwritesplit/test/test_transaction_routing4b.sql new file mode 100644 index 000000000..6190ff224 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_transaction_routing4b.sql @@ -0,0 +1,9 @@ +USE test; +SET autocommit = oFf; +CREATE TABLE IF NOT EXISTS myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; -- implicit transaction started +SELECT COUNT(*) FROM myCity; -- read transaction's modifications from master +COMMIT; diff --git a/server/test/makefile b/server/test/makefile index e05e1a975..d812494fc 100644 --- a/server/test/makefile +++ b/server/test/makefile @@ -5,6 +5,7 @@ include ../../build_gateway.inc include ../../makefile.inc +include ../../test.inc TEST_ROOT := $(ROOT_PATH)/test PARENT_DIR := $(ROOT_PATH)/server @@ -39,8 +40,15 @@ testall: @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test Server Core" >> $(TESTLOG) - $(MAKE) -C $(ROOT_PATH)/server/core testall - @echo "Query Classifier PASSED" >> $(TESTLOG) + $(MAKE) -C $(ROOT_PATH)/server/core testall + @echo "Core PASSED" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Test Modules" >> $(TESTLOG) + $(MAKE) -C $(ROOT_PATH)/server/modules/routing testall + @killall maxscale + @echo "Routing modules PASSED" >> $(TESTLOG) buildtests: @@ -51,7 +59,7 @@ else endif runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test MaxScale server" >> $(TESTLOG) @@ -63,5 +71,4 @@ runtests: @sleep 5 @echo "MaxScale server PASSED" >> $(TESTLOG) @echo "" >> $(TESTLOG) - @killall maxscale - + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/test.inc b/test.inc index 241772b0b..b020368d8 100644 --- a/test.inc +++ b/test.inc @@ -32,4 +32,10 @@ TPWD := # master's server_id, for example: # TMASTER_ID := 2 # -TMASTER_ID := \ No newline at end of file +TMASTER_ID := + +# +# Global test log where all log is gathered +# TEST_MAXSCALE_LOG := $(ROOT_PATH)/test/test_maxscale.log +# +TEST_MAXSCALE_LOG := diff --git a/test/README b/test/README index cb0fbbda5..c13504058 100644 --- a/test/README +++ b/test/README @@ -57,7 +57,8 @@ MAXSCALE_HOME log_manager, query_classifier, server, - utils + utils, + modules | |- utils cleantests, buildtests, runtests, testall | | diff --git a/test/makefile b/test/makefile index 0919d698a..15e57f8bf 100644 --- a/test/makefile +++ b/test/makefile @@ -5,11 +5,12 @@ include ../build_gateway.inc include ../makefile.inc +include ../test.inc export MAXSCALE_HOME=$(shell pwd)/MaxScale CC=cc -TESTLOG := $(shell pwd)/testmaxscale.log +TESTLOG := $(TEST_MAXSCALE_LOG) testall: $(MAKE) cleantests @@ -39,7 +40,7 @@ testall: @echo "Test utils" >> $(TESTLOG) $(MAKE) -C $(ROOT_PATH)/utils testall @echo "Utils PASSED" >> $(TESTLOG) - + cleantests: $(DEL) *~ $(MAKE) -C $(ROOT_PATH)/log_manager cleantests @@ -55,9 +56,10 @@ buildtests: $(MAKE) -C $(ROOT_PATH)/utils buildtests runtests: - @echo "" >> $(TESTLOG) + @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test MaxScale root" >> $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo "Nothing to run here so far" >> $(TESTLOG) + \ No newline at end of file