diff --git a/Makefile b/Makefile index 328fa9b7a..b8a085468 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ clean: (cd client; touch depend.mk; make clean) depend: + echo '#define MAXSCALE_VERSION "'`cat $(ROOT_PATH)/VERSION`'"' > $(ROOT_PATH)/server/include/version.h (cd log_manager; make depend) (cd query_classifier; make depend) (cd server; make depend) diff --git a/README b/README index 3750fb332..cdc9b35ac 100644 --- a/README +++ b/README @@ -182,7 +182,7 @@ 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 +* a slave on port 3002, 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 diff --git a/build_gateway.inc b/build_gateway.inc index 83ffaf762..0e1d6b75f 100644 --- a/build_gateway.inc +++ b/build_gateway.inc @@ -12,7 +12,7 @@ # # Set debug flags # -DEBUG := +DEBUG := ${MAXSCALE_DEBUG} # # Set build env @@ -22,7 +22,7 @@ UNIX := Y # # Set MaxScale branch directory # -ROOT_PATH := $(HOME)/src/bazaar/tmp/maxscale +ROOT_PATH := $(HOME)/${MAXSCALE_SOURCE} INC_PATH := $(HOME)/usr/include # @@ -38,7 +38,7 @@ MYSQL_HEADERS := -I$(INC_PATH) -I$(MYSQL_ROOT)/ -I$(MYSQL_ROOT)/private/ -I$(MYS # # Set DYNLIB=Y if you want to link MaxScale with dynamic embedded lib # -DYNLIB := +DYNLIB := ${MAXSCALE_DYNLIB} # # Set path to Embedded MySQL Server @@ -51,3 +51,4 @@ endif # Set path to MySQL errors file # ERRMSG := $(HOME)/usr/share/mysql + diff --git a/client/Makefile b/client/Makefile index a57518dc2..19f38785f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -20,6 +20,9 @@ # client program # 18/06/14 Mark Riddoch Addition of conditional for histedit +include ../build_gateway.inc +include ../makefile.inc + ifeq ($(wildcard /usr/include/histedit.h), ) HISTLIB= HISTFLAG= @@ -63,13 +66,14 @@ maxadmin: $(OBJ) clean: - rm -f $(OBJ) maxadmin + $(DEL) $(OBJ) maxadmin + $(DEL) *.so tags: ctags $(SRCS) $(HDRS) depend: - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk install: maxadmin diff --git a/gcov.diff b/gcov.diff deleted file mode 100644 index 9dd0604ca..000000000 --- a/gcov.diff +++ /dev/null @@ -1,169 +0,0 @@ -diff --git a/makefile.inc b/makefile.inc -index f2d93bf..c7dbffa 100644 ---- a/makefile.inc -+++ b/makefile.inc -@@ -24,8 +24,8 @@ endif - - # -O2 -g -pipe -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector --param=ssp-buffer-size=4 -fPIC - --CFLAGS := $(CFLAGS) -Wall --LDLIBS := $(LDLIBS) -pthread -+CFLAGS := $(CFLAGS) -Wall -fprofile-arcs -ftest-coverage -+LDLIBS := $(LDLIBS) -pthread -lgcov - LDMYSQL := -lmysqld - CPP_LDLIBS := -lstdc++ - -diff --git a/server/core/Makefile b/server/core/Makefile -index cb0250d..fe0f579 100644 ---- a/server/core/Makefile -+++ b/server/core/Makefile -@@ -46,7 +46,7 @@ CC=cc - CFLAGS=-c -I/usr/include -I../include -I../modules/include -I../inih \ - $(MYSQL_HEADERS) \ - -I$(LOGPATH) -I$(UTILSPATH) \ -- -Wall -g -+ -Wall -g -fprofile-arcs -ftest-coverage - - include ../../makefile.inc - -@@ -76,7 +76,7 @@ POBJS=maxpasswd.o secrets.o utils.o - LIBS=-L$(EMBEDDED_LIB) \ - -lmysqld \ - -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ -- -L../inih/extra -linih -lssl -lstdc++ -+ -L../inih/extra -linih -lssl -lstdc++ -lgcov - - all: maxscale maxkeys maxpasswd - -diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile -index f8cf910..ca022b8 100644 ---- a/server/modules/filter/Makefile -+++ b/server/modules/filter/Makefile -@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -44,7 +44,7 @@ TEESRCS=tee.c - TEEOBJ=$(TEESRCS:.c=.o) - SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS) - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov - MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so libhintfilter.so - - -diff --git a/server/modules/filter/hint/Makefile b/server/modules/filter/hint/Makefile -index 4f21947..3ad0e3b 100644 ---- a/server/modules/filter/hint/Makefile -+++ b/server/modules/filter/hint/Makefile -@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../../makefile.inc - -@@ -34,7 +34,7 @@ LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \ - - SRCS= hintfilter.c hintparser.c - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov - - libhintfilter.so: $(OBJ) - $(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@ -diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile -index f815efe..308f496 100644 ---- a/server/modules/monitor/Makefile -+++ b/server/modules/monitor/Makefile -@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I. -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) $(MYSQL_HEADERS) -Wall -g -+ -I$(UTILSPATH) $(MYSQL_HEADERS) -Wall -g -fprofile-arcs -ftest-coverage - - LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \ - -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \ -@@ -42,7 +42,7 @@ NDBCLUSTEROBJ=$(NDBCLUSTERSRCS:.c=.o) - SRCS=$(MYSQLSRCS) $(GALERASRCS) $(NDBCLUSTERSRCS) - OBJ=$(SRCS:.c=.o) - LIBS=$(UTILSPATH)/skygw_utils.o -llog_manager \ -- -L$(EMBEDDED_LIB) -lmysqld -+ -L$(EMBEDDED_LIB) -lmysqld -lgcov - MODULES=libmysqlmon.so libgaleramon.so libndbclustermon.so - - -diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile -index 54a8f8c..c8913ab 100644 ---- a/server/modules/protocol/Makefile -+++ b/server/modules/protocol/Makefile -@@ -31,7 +31,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -51,7 +51,7 @@ MAXSCALEDOBJ=$(MAXSCALEDSRCS:.c=.o) - SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) \ - $(MAXSCALEDSRCS) - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -+LIBS=$(UTILSPATH)/skygw_utils.o -lgcov - MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so \ - libmaxscaled.so - -diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile -index dbb0503..8304766 100644 ---- a/server/modules/routing/Makefile -+++ b/server/modules/routing/Makefile -@@ -29,7 +29,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -48,7 +48,7 @@ WEBSRCS=webserver.o - WEBOBJ=$(WEBSRCS:.c=.o) - SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov - MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so \ - libwebserver.so - -diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile -index c60f2ff..a3a643e 100644 ---- a/server/modules/routing/readwritesplit/Makefile -+++ b/server/modules/routing/readwritesplit/Makefile -@@ -27,7 +27,7 @@ QCLASSPATH := $(ROOT_PATH)/query_classifier - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include \ - -I$(LOGPATH) -I$(UTILSPATH) -I$(QCLASSPATH) \ -- $(MYSQL_HEADERS) -Wall -g -+ $(MYSQL_HEADERS) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../../makefile.inc - -@@ -38,7 +38,7 @@ LDFLAGS=-shared -L$(LOGPATH) -L$(QCLASSPATH) -L$(EMBEDDED_LIB) \ - - SRCS=readwritesplit.c - OBJ=$(SRCS:.c=.o) --LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld -+LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld -lgcov - MODULES=libreadwritesplit.so - - all: $(MODULES) diff --git a/log_manager/log_manager.cc b/log_manager/log_manager.cc index c8f96266e..733e9fffe 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -255,11 +255,7 @@ static int logmanager_write_log( 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, -#else blockbuf_t** p_bb, -#endif logfile_id_t id, size_t str_len, bool flush); @@ -653,7 +649,16 @@ static int logmanager_write_log( int safe_str_len; timestamp_len = get_timestamp_len(); - safe_str_len = MIN(timestamp_len-1+str_len, lf->lf_buf_size); + + /** Findout how much can be safely written with current block size */ + if (timestamp_len-1+str_len > lf->lf_buf_size) + { + safe_str_len = lf->lf_buf_size; + } + else + { + safe_str_len = timestamp_len-1+str_len; + } /** * Seek write position and register to block buffer. * Then print formatted string to write position. @@ -673,9 +678,9 @@ static int logmanager_write_log( * of the timestamp string. */ if (use_valist) { - vsnprintf(wp+timestamp_len, safe_str_len, str, valist); + vsnprintf(wp+timestamp_len, safe_str_len-timestamp_len, str, valist); } else { - snprintf(wp+timestamp_len, safe_str_len, "%s", str); + snprintf(wp+timestamp_len, safe_str_len-timestamp_len, "%s", str); } /** write to syslog */ @@ -694,12 +699,7 @@ static int logmanager_write_log( break; } } - - /** remove double line feed */ - if (wp[timestamp_len+str_len-2] == '\n') { - wp[timestamp_len+str_len-2]=' '; - } - wp[timestamp_len+str_len-1]='\n'; + wp[safe_str_len-1] = '\n'; blockbuf_unregister(bb); /** diff --git a/log_manager/makefile b/log_manager/makefile index b1386bbcc..a020c7932 100644 --- a/log_manager/makefile +++ b/log_manager/makefile @@ -44,7 +44,7 @@ install: liblink install liblog_manager.so.1.0.1 liblog_manager.so $(DEST)/lib depend: - @rm -f depend + @$(DEL) depend $(CPP) -M $(CFLAGS) \ $(MYSQL_HEADERS) \ -I$(UTILS_PATH) -I./ \ diff --git a/log_manager/test/makefile b/log_manager/test/makefile index 75df90c60..71fd074f1 100644 --- a/log_manager/test/makefile +++ b/log_manager/test/makefile @@ -39,7 +39,7 @@ buildtests: -o testlog \ -I$(MARIADB_SRC_PATH)/include \ -I$(LOG_MANAGER_PATH) -I$(UTILS_PATH) testlog.c \ - -llog_manager $(LDLIBS) \ + -lstdc++ -llog_manager $(LDLIBS) \ $(UTILS_PATH)/skygw_utils.o diff --git a/query_classifier/makefile b/query_classifier/makefile index efa456a01..4f8cf34c8 100644 --- a/query_classifier/makefile +++ b/query_classifier/makefile @@ -8,6 +8,8 @@ SRCS := query_classifier.cc UTILS_PATH := $(ROOT_PATH)/utils QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager +SERVER_INC_PATH := $(ROOT_PATH)/server/include +MODULE_INC_PATH := $(ROOT_PATH)/server/modules/include makeall: clean all @@ -32,8 +34,7 @@ runtests: testall: $(MAKE) -C test testall - - + utils: $(MAKE) -C $(UTILS_PATH) clean all @@ -43,6 +44,9 @@ libcomp: $(CPP) -c $(CFLAGS) \ $(MYSQL_HEADERS) \ -I$(LOG_MANAGER_PATH) \ + -I$(SERVER_INC_PATH) \ + -I$(MODULE_INC_PATH) \ + -I$(UTILS_PATH) \ -I./ \ -fPIC ./query_classifier.cc -o query_classifier.o @@ -62,10 +66,13 @@ install: liblink install ./libquery_classifier.so.1.0.1 ./libquery_classifier.so $(DEST)/lib depend: - @rm -f depend + @$(DEL) depend $(CPP) -M $(CFLAGS) \ $(MYSQL_HEADERS) \ -I$(LOG_MANAGER_PATH) \ + -I$(SERVER_INC_PATH) \ + -I$(MODULE_INC_PATH) \ + -I$(UTILS_PATH) \ -I./ \ $(SRCS) > depend diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 889940e00..1d9e9f3e1 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -34,6 +34,7 @@ #include "../utils/skygw_types.h" #include "../utils/skygw_debug.h" #include +#include #include #include @@ -83,122 +84,158 @@ static bool skygw_stmt_causes_implicit_commit( static int is_autocommit_stmt( LEX* lex); -/** - * @node (write brief function description here) - * - * Parameters: - * @param query_str - - * - * - * @param client_flag - - * - * - * @return - * +static void parsing_info_set_plain_str(void* ptr, + char* str); + +/** + * Calls parser for the query includede in the buffer. Creates and adds parsing + * information to buffer if it doesn't exist already. Resolves the query type. * - * @details (write detailed description here) - * + * @param querybuf buffer including the query and possibly the parsing information + * + * @return query type */ -skygw_query_type_t skygw_query_classifier_get_type( - const char* query, - unsigned long client_flags, - MYSQL** p_mysql) +skygw_query_type_t query_classifier_get_type( + GWBUF* querybuf) { - MYSQL* mysql; - char* query_str; - const char* user = "skygw"; - const char* db = "skygw"; - THD* thd; + MYSQL* mysql; skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - bool failp = FALSE; - - ss_info_dassert(query != NULL, ("query_str is NULL")); + bool succp; - query_str = const_cast(query); - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Query : \"%s\"", query_str))); + ss_info_dassert(querybuf != NULL, ("querybuf is NULL")); - /** Get server handle */ - mysql = mysql_init(NULL); + /** Create parsing info for the query and store it to buffer */ + succp = query_is_parsed(querybuf); - if (mysql == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : call to mysql_real_connect failed due %d, %s.", - mysql_errno(mysql), - mysql_error(mysql)))); + if (!succp) + { + succp = parse_query(querybuf); + } + /** Read thd pointer and resolve the query type with it. */ + if (succp) + { + parsing_info_t* pi; - mysql_library_end(); - goto return_qtype; - } + pi = (parsing_info_t*)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + + if (pi != NULL) + { + mysql = (MYSQL *)pi->pi_handle; - if (p_mysql != NULL) - { - *p_mysql = mysql; + /** Find out the query type */ + if (mysql != NULL) + { + qtype = resolve_query_type((THD *)mysql->thd); + } + } } - /** Set methods and authentication to mysql */ - mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw"); - mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); - mysql->methods = &embedded_methods; - mysql->user = my_strdup(user, MYF(0)); - mysql->db = my_strdup(db, MYF(0)); - mysql->passwd = NULL; - - /** Get one or create new THD object to be use in parsing */ - thd = get_or_create_thd_for_parsing(mysql, query_str); - - if (thd == NULL) - { - skygw_query_classifier_free(mysql); - *p_mysql = NULL; - goto return_qtype; - } - /** - * Create parse_tree inside thd. - * thd and even lex are readable even if parser failed so let it - * continue despite failure. - */ - failp = create_parse_tree(thd); - qtype = resolve_query_type(thd); - - if (p_mysql == NULL) - { - skygw_query_classifier_free(mysql); - } -return_qtype: return qtype; } - -void skygw_query_classifier_free( - MYSQL* mysql) +/** + * Create parsing info and try to parse the query included in the query buffer. + * Store pointer to created parse_tree_t object to buffer. + * + * @param querybuf buffer including the query and possibly the parsing information + * + * @return true if succeed, false otherwise + */ +bool parse_query ( + GWBUF* querybuf) { - if (mysql->thd != NULL) + bool succp; + THD* thd; + uint8_t* data; + size_t len; + char* query_str; + parsing_info_t* pi; + + CHK_GWBUF(querybuf); + /** Do not parse without releasing previous parse info first */ + ss_dassert(!query_is_parsed(querybuf)); + + if (query_is_parsed(querybuf)) { - (*mysql->methods->free_embedded_thd)(mysql); - mysql->thd = NULL; + return false; } - mysql_close(mysql); - mysql_thread_end(); -} + /** Create parsing info */ + pi = parsing_info_init(parsing_info_done); + + if (pi == NULL) + { + succp = false; + goto retblock; + } + /** Extract query and copy it to different buffer */ + data = (uint8_t*)GWBUF_DATA(querybuf); + len = MYSQL_GET_PACKET_LEN(data)-1; /*< distract 1 for packet type byte */ + query_str = (char *)malloc(len+1); + + if (query_str == NULL) + { + /** Free parsing info data */ + parsing_info_done(pi); + succp = false; + goto retblock; + } + memcpy(query_str, &data[5], len); + memset(&query_str[len], 0, 1); + parsing_info_set_plain_str(pi, query_str); + + /** Get one or create new THD object to be use in parsing */ + thd = get_or_create_thd_for_parsing((MYSQL *)pi->pi_handle, query_str); + + if (thd == NULL) + { + /** Free parsing info data */ + parsing_info_done(pi); + succp = false; + goto retblock; + } + /** + * Create parse_tree inside thd. + * thd and lex are readable even if creating parse tree fails. + */ + create_parse_tree(thd); + /** Add complete parsing info struct to the query buffer */ + gwbuf_add_buffer_object(querybuf, + GWBUF_PARSING_INFO, + (void *)pi, + parsing_info_done); + + succp = true; +retblock: + return succp; +} +/** + * If buffer has non-NULL gwbuf_parsing_info it is parsed and it has parsing + * information included. + * + * @param buf buffer being examined + * + * @return true or false + */ +bool query_is_parsed( + GWBUF* buf) +{ + CHK_GWBUF(buf); + return GWBUF_IS_PARSED(buf); +} -/** - * @node (write brief function description here) + +/** + * Create a thread context, thd, init embedded server, connect to it, and allocate + * query to thd. * * Parameters: - * @param mysql - - * - * - * @param query_str - - * - * - * @return - * + * @param mysql Database handle * - * @details (write detailed description here) + * @param query_str Query in plain txt string + * + * @return Thread context pointer * */ static THD* get_or_create_thd_for_parsing( @@ -412,7 +449,7 @@ static skygw_query_type_t resolve_query_type( ss_info_dassert(thd != NULL, ("thd is NULL\n")); - force_data_modify_op_replication = FALSE; + force_data_modify_op_replication = FALSE; lex = thd->lex; /** SELECT ..INTO variable|OUTFILE|DUMPFILE */ @@ -475,7 +512,15 @@ static skygw_query_type_t resolve_query_type( } else if (lex->option_type == OPT_SESSION) { - type |= QUERY_TYPE_SESSION_WRITE; + /** SHOW commands are all reads to one backend */ + if (lex->sql_command == SQLCOM_SHOW_VARIABLES) + { + type |= QUERY_TYPE_SESSION_READ; + } + else + { + type |= QUERY_TYPE_SESSION_WRITE; + } goto return_qtype; } /** @@ -494,10 +539,18 @@ static skygw_query_type_t resolve_query_type( force_data_modify_op_replication) { type |= QUERY_TYPE_SESSION_WRITE; - } else { + } + else + { type |= QUERY_TYPE_WRITE; + + if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE && + lex->sql_command == SQLCOM_CREATE_TABLE) + { + type |= QUERY_TYPE_CREATE_TMP_TABLE; + } + } - goto return_qtype; } @@ -635,7 +688,6 @@ static skygw_query_type_t resolve_query_type( "%lu [resolve_query_type] " "functype FUNC_SP, stored proc " "or unknown function.", - "%s:%s", pthread_self()))); break; case Item_func::UDF_FUNC: @@ -648,7 +700,6 @@ static skygw_query_type_t resolve_query_type( pthread_self()))); break; case Item_func::NOW_FUNC: - case Item_func::GSYSVAR_FUNC: func_qtype |= QUERY_TYPE_LOCAL_READ; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -657,8 +708,30 @@ static skygw_query_type_t resolve_query_type( "executed in MaxScale.", pthread_self()))); break; + /** System session variable */ + case Item_func::GSYSVAR_FUNC: + /** User-defined variable read */ + case Item_func::GUSERVAR_FUNC: + /** User-defined variable modification */ + case Item_func::SUSERVAR_FUNC: + func_qtype |= QUERY_TYPE_SESSION_READ; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [resolve_query_type] " + "functype SUSERVAR_FUNC, could be " + "executed in MaxScale.", + pthread_self()))); + break; case Item_func::UNKNOWN_FUNC: - func_qtype |= QUERY_TYPE_READ; + if (item->name != NULL && + strcmp(item->name, "last_insert_id()") == 0) + { + func_qtype |= QUERY_TYPE_SESSION_READ; + } + else + { + func_qtype |= QUERY_TYPE_READ; + } /** * Many built-in functions are of this * type, for example, rand(), soundex(), @@ -816,3 +889,379 @@ char* skygw_query_classifier_get_stmtname( return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str; } + +/** + * Finds the head of the list of tables affected by the current select statement. + * @param thd Pointer to a valid THD + * @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error + */ +void* skygw_get_affected_tables(void* thdp) +{ + THD* thd = (THD*)thdp; + + if(thd == NULL || + thd->lex == NULL || + thd->lex->current_select == NULL) + { + ss_dassert(thd != NULL && + thd->lex != NULL && + thd->lex->current_select != NULL); + return NULL; + } + + return (void*)thd->lex->current_select->table_list.first; +} + + +/** + * Reads the parsetree and lists all the affected tables and views in the query. + * In the case of an error, the size of the table is set to zero and no memory is allocated. + * The caller must free the allocated memory. + * + * @param querybuf GWBUF where the table names are extracted from + * @param tblsize Pointer where the number of tables is written + * @return Array of null-terminated strings with the table names + */ +char** skygw_get_table_names(GWBUF* querybuf,int* tblsize) +{ + parsing_info_t* pi; + MYSQL* mysql; + THD* thd; + TABLE_LIST* tbl; + int i = 0, currtblsz = 0; + char**tables,**tmp; + + if (!GWBUF_IS_PARSED(querybuf)) + { + tables = NULL; + goto retblock; + } + pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + + if (pi == NULL) + { + tables = NULL; + goto retblock; + } + + if (pi->pi_query_plain_str == NULL || + (mysql = (MYSQL *)pi->pi_handle) == NULL || + (thd = (THD *)mysql->thd) == NULL) + { + ss_dassert(pi->pi_query_plain_str != NULL && + mysql != NULL && + thd != NULL); + tables = NULL; + goto retblock; + } + + thd->lex->current_select = thd->lex->all_selects_list; + + while(thd->lex->current_select){ + + tbl = (TABLE_LIST*)skygw_get_affected_tables(thd); + + while (tbl) + { + if(i >= currtblsz){ + + tmp = (char**)malloc(sizeof(char*)*(currtblsz*2+1)); + + if(tmp){ + if(currtblsz > 0){ + int x; + for(x = 0;xalias); + tbl=tbl->next_local; + } + thd->lex->current_select = thd->lex->current_select->next_select_in_list(); + } + + retblock: + *tblsize = i; + return tables; +} +/** + * Extract the name of the created table. + * @param querybuf Buffer to use. + * @return A pointer to the name if a table was created, otherwise NULL + */ +char* skygw_get_created_table_name(GWBUF* querybuf) +{ + parsing_info_t* pi; + MYSQL* mysql; + THD* thd; + if (!GWBUF_IS_PARSED(querybuf)) + { + return NULL; + } + pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + + if (pi == NULL) + { + return NULL; + } + + if ((mysql = (MYSQL *)pi->pi_handle) == NULL || + (thd = (THD *)mysql->thd) == NULL) + { + ss_dassert(mysql != NULL && + thd != NULL); + return NULL; + } + + if(thd->lex->create_last_non_select_table && + thd->lex->create_last_non_select_table->table_name){ + char* name = strdup(thd->lex->create_last_non_select_table->table_name); + return name; + }else{ + return NULL; + } + +} +/** + * Checks whether the buffer contains a DROP TABLE... query. + * @param querybuf Buffer to inspect + * @return true if it contains the query otherwise false + */ +bool is_drop_table_query(GWBUF* querybuf) +{ + parsing_info_t* pi; + MYSQL* mysql; + THD* thd; + + if (!GWBUF_IS_PARSED(querybuf)) + { + return false; + } + pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + + if (pi == NULL) + { + return false; + } + + if ((mysql = (MYSQL *)pi->pi_handle) == NULL || + (thd = (THD *)mysql->thd) == NULL) + { + ss_dassert(mysql != NULL && + thd != NULL); + return false; + } + + return thd->lex->sql_command == SQLCOM_DROP_TABLE; +} + +/* + * Replace user-provided literals with question marks. Return a copy of the + * querystr with replacements. + * + * @param querybuf GWBUF buffer including necessary parsing info + * + * @return Copy of querystr where literals are replaces with question marks or + * NULL if querystr is NULL, thread context or lex are NULL or if replacement + * function fails. + * + * Replaced literal types are STRING_ITEM,INT_ITEM,DECIMAL_ITEM,REAL_ITEM, + * VARBIN_ITEM,NULL_ITEM + */ +char* skygw_get_canonical( + GWBUF* querybuf) +{ + parsing_info_t* pi; + MYSQL* mysql; + THD* thd; + LEX* lex; + Item* item; + char* querystr; + + if (!GWBUF_IS_PARSED(querybuf)) + { + querystr = NULL; + goto retblock; + } + pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + CHK_PARSING_INFO(pi); + + if (pi == NULL) + { + querystr = NULL; + goto retblock; + } + + if (pi->pi_query_plain_str == NULL || + (mysql = (MYSQL *)pi->pi_handle) == NULL || + (thd = (THD *)mysql->thd) == NULL || + (lex = thd->lex) == NULL) + { + ss_dassert(pi->pi_query_plain_str != NULL && + mysql != NULL && + thd != NULL && + lex != NULL); + querystr = NULL; + goto retblock; + } + querystr = strdup(pi->pi_query_plain_str); + + for (item=thd->free_list; item != NULL; item=item->next) + { + Item::Type itype; + + if (item->name == NULL) + { + continue; + } + itype = item->type(); + + if (itype == Item::STRING_ITEM) + { + String tokenstr; + String* res = item->val_str_ascii(&tokenstr); + + if (res->is_empty()) /*< empty string */ + { + querystr = replace_literal(querystr, "\"\"", "\"?\""); + } + else + { + querystr = replace_literal(querystr, res->ptr(), "?"); + } + } + else if (itype == Item::INT_ITEM || + itype == Item::DECIMAL_ITEM || + itype == Item::REAL_ITEM || + itype == Item::VARBIN_ITEM || + itype == Item::NULL_ITEM) + { + querystr = replace_literal(querystr, item->name, "?"); + } + } /*< for */ +retblock: + return querystr; +} + + +/** + * Create parsing information; initialize mysql handle, allocate parsing info + * struct and set handle and free function pointer to it. + * + * @param donefun pointer to free function + * + * @return pointer to parsing information + */ +parsing_info_t* parsing_info_init( + void (*donefun)(void *)) +{ + parsing_info_t* pi = NULL; + MYSQL* mysql; + const char* user = "skygw"; + const char* db = "skygw"; + + ss_dassert(donefun != NULL); + + /** Get server handle */ + mysql = mysql_init(NULL); + ss_dassert(mysql != NULL); + + if (mysql == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : call to mysql_real_connect failed due %d, %s.", + mysql_errno(mysql), + mysql_error(mysql)))); + + goto retblock; + } + /** Set methods and authentication to mysql */ + mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw"); + mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); + mysql->methods = &embedded_methods; + mysql->user = my_strdup(user, MYF(0)); + mysql->db = my_strdup(db, MYF(0)); + mysql->passwd = NULL; + + pi = (parsing_info_t*)calloc(1, sizeof(parsing_info_t)); + + if (pi == NULL) + { + mysql_close(mysql); + goto retblock; + } +#if defined(SS_DEBUG) + pi->pi_chk_top = CHK_NUM_PINFO; + pi->pi_chk_tail = CHK_NUM_PINFO; +#endif + /** Set handle and free function to parsing info struct */ + pi->pi_handle = mysql; + pi->pi_done_fp = donefun; + +retblock: + return pi; +} + +/** + * Free function for parsing info. Called by gwbuf_free or in case initialization + * of parsing information fails. + * + * @param ptr Pointer to parsing information, cast required + * + * @return void + * + */ +void parsing_info_done( + void* ptr) +{ + parsing_info_t* pi = (parsing_info_t *)ptr; + + if (pi->pi_handle != NULL) + { + MYSQL* mysql = (MYSQL *)pi->pi_handle; + + if (mysql->thd != NULL) + { + (*mysql->methods->free_embedded_thd)(mysql); + mysql->thd = NULL; + } + mysql_close(mysql); + } + /** Free plain text query string */ + if (pi->pi_query_plain_str != NULL) + { + free(pi->pi_query_plain_str); + } + free(pi); +} + +/** + * Add plain text query string to parsing info. + * + * @param ptr Pointer to parsing info struct, cast required + * @param str String to be added + * + * @return void + */ +static void parsing_info_set_plain_str( + void* ptr, + char* str) +{ + parsing_info_t* pi = (parsing_info_t *)ptr; + CHK_PARSING_INFO(pi); + + pi->pi_query_plain_str = str; +} \ No newline at end of file diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 31f9cf44e..ccf08a6ea 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -20,7 +20,8 @@ Copyright SkySQL Ab /** getpid */ #include #include -#include "../utils/skygw_utils.h" +#include +#include EXTERN_C_BLOCK_BEGIN @@ -43,23 +44,46 @@ typedef enum { QUERY_TYPE_COMMIT = 0x0200, /*< COMMIT */ QUERY_TYPE_PREPARE_NAMED_STMT = 0x0400, /*< Prepared stmt with name from user */ QUERY_TYPE_PREPARE_STMT = 0x0800, /*< Prepared stmt with id provided by server */ - QUERY_TYPE_EXEC_STMT = 0x1000 /*< Execute prepared statement */ + QUERY_TYPE_EXEC_STMT = 0x1000, /*< Execute prepared statement */ + QUERY_TYPE_SESSION_READ = 0x2000, /*< Read session data (from master 31.8.14) */ + QUERY_TYPE_CREATE_TMP_TABLE = 0x4000, /*< Create temporary table */ + QUERY_TYPE_READ_TMP_TABLE = 0x8000 /*< Read temporary table */ } skygw_query_type_t; + +typedef struct parsing_info_st { +#if defined(SS_DEBUG) + skygw_chk_t pi_chk_top; +#endif + void* pi_handle; /*< parsing info object pointer */ + char* pi_query_plain_str; /*< query as plain string */ + void (*pi_done_fp)(void *); /*< clean-up function for parsing info */ +#if defined(SS_DEBUG) + skygw_chk_t pi_chk_tail; +#endif +} parsing_info_t; + + #define QUERY_IS_TYPE(mask,type) ((mask & type) == type) /** * Create THD and use it for creating parse tree. Examine parse tree and * classify the query. */ -skygw_query_type_t skygw_query_classifier_get_type( - const char* query_str, - unsigned long client_flags, - MYSQL** mysql); +skygw_query_type_t query_classifier_get_type(GWBUF* querybuf); /** Free THD context and close MYSQL */ -void skygw_query_classifier_free(MYSQL* mysql); -char* skygw_query_classifier_get_stmtname(MYSQL* mysql); +char* skygw_query_classifier_get_stmtname(MYSQL* mysql); +char* skygw_get_created_table_name(GWBUF* querybuf); +bool is_drop_table_query(GWBUF* querybuf); +void* skygw_get_affected_tables(void* thdp); +char** skygw_get_table_names(GWBUF* querybuf,int* tblsize); +char* skygw_get_canonical(GWBUF* querybuf); +bool parse_query (GWBUF* querybuf); +parsing_info_t* parsing_info_init(void (*donefun)(void *)); +void parsing_info_done(void* ptr); +bool query_is_parsed(GWBUF* buf); + EXTERN_C_BLOCK_END diff --git a/query_classifier/test/canonical_tests/Makefile b/query_classifier/test/canonical_tests/Makefile new file mode 100644 index 000000000..3d69507ec --- /dev/null +++ b/query_classifier/test/canonical_tests/Makefile @@ -0,0 +1,64 @@ +# cleantests - clean local and subdirectories' tests +# buildtests - build all local and subdirectories' tests +# runtests - run all local tests +# testall - clean, build and run local and subdirectories' tests + +include ../../../build_gateway.inc +include ../../../makefile.inc +include ../../../test.inc + +CC = gcc +CPP = g++ + +TESTPATH := $(shell pwd) +TESTLOG := $(TESTPATH)/testqclass.log +QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier +LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager +UTILS_PATH := $(ROOT_PATH)/utils +CORE_PATH := $(ROOT_PATH)/server/core +TESTAPP = $(TESTPATH)/canonizer + +LDFLAGS=-L$(QUERY_CLASSIFIER_PATH) \ + -L$(LOG_MANAGER_PATH) \ + -L$(EMBEDDED_LIB) \ + -Wl,-rpath,$(DEST)/lib \ + -Wl,-rpath,$(EMBEDDED_LIB) \ + -Wl,-rpath,$(LOG_MANAGER_PATH) \ + -Wl,-rpath,$(QUERY_CLASSIFIER_PATH) + +LIBS=-lstdc++ -lpthread -lquery_classifier -lz -ldl -lssl -laio -lcrypt -lcrypto -lrt \ + -llog_manager $(UTILS_PATH)/skygw_utils.o $(CORE_PATH)/buffer.o $(CORE_PATH)/atomic.o $(CORE_PATH)/spinlock.o + +CFLAGS=-g $(MYSQL_HEADERS) \ + -I$(QUERY_CLASSIFIER_PATH) \ + $(MYSQL_HEADERS) \ + -I$(ROOT_PATH)/server/include \ + -I$(UTILS_PATH) + +EMBFLAGS=$(shell mysql_config --cflags --libmysqld-libs) + + +testall: + $(MAKE) cleantests + $(MAKE) buildtests + $(MAKE) runtests + +cleantests: + - $(DEL) *.o + - $(DEL) *~ + - $(DEL) canonizer + - $(DEL) aria_log* + - $(DEL) ib* + +buildtests: $(OBJS) + cp $(ERRMSG)/errmsg.sys . + $(CC) $(CFLAGS) $(LDFLAGS) $(EMBFLAGS) $(LIBS) canonizer.c -o $(TESTAPP) + +runtests: + @echo "" > $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Canonical Query Tests" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + ./canontest.sh $(TESTLOG) input.sql output.sql expected.sql diff --git a/query_classifier/test/canonical_tests/canonizer.c b/query_classifier/test/canonical_tests/canonizer.c new file mode 100644 index 000000000..a4c9df90f --- /dev/null +++ b/query_classifier/test/canonical_tests/canonizer.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include + +static char* server_options[] = { + "SkySQL Gateway", + "--datadir=./", + "--language=./", + "--skip-innodb", + "--default-storage-engine=myisam", + NULL +}; + +const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1; + +static char* server_groups[] = { + "embedded", + "server", + "server", + NULL +}; + +int main(int argc, char** argv) +{ + + int fdin,fdout,i=0,fnamelen,fsz,lines = 0; + unsigned int psize; + GWBUF** qbuff; + char *qin, *outnm, *buffer, *tok; + + if(argc != 3){ + printf("Usage: canonizer \n"); + return 1; + } + + + + bool failed = mysql_library_init(num_elements, server_options, server_groups); + + if(failed){ + printf("Embedded server init failed.\n"); + return 1; + } + + fnamelen = strlen(argv[1]) + 16; + fdin = open(argv[1],O_RDONLY); + fsz = lseek(fdin,0,SEEK_END); + lseek(fdin,0,SEEK_SET); + + if(!(buffer = malloc(sizeof(char)*fsz))){ + printf("Error: Failed to allocate memory."); + return 1; + } + + read(fdin,buffer,fsz); + tok = strpbrk(buffer,"\n"); + lines = 1; + + while((tok = strpbrk(tok + 1,"\n"))){ + lines++; + } + + qbuff = malloc(sizeof(GWBUF*)*lines); + + i = 0; + tok = strtok(buffer,"\n"); + + while(tok){ + qin = strdup(tok); + psize = strlen(qin); + qbuff[i] = gwbuf_alloc(psize + 6); + *(qbuff[i]->sbuf->data + 0) = (unsigned char)psize; + *(qbuff[i]->sbuf->data + 1) = (unsigned char)(psize>>8); + *(qbuff[i]->sbuf->data + 2) = (unsigned char)(psize>>16); + *(qbuff[i]->sbuf->data + 4) = 0x03; + memcpy(qbuff[i]->sbuf->data + 5,qin,psize); + *(qbuff[i]->sbuf->data + 5 + psize) = 0x00; + tok = strtok(NULL,"\n"); + free(qin); + i++; + } + + fdout = open(argv[2],O_TRUNC|O_CREAT|O_WRONLY,S_IRWXU|S_IXGRP|S_IXOTH); + + for(i = 0;i " + exit 0 +fi +TESTLOG=$1 +INPUT=$2 +OUTPUT=$3 +EXPECTED=$4 +DIFFLOG=diff.out +$PWD/canonizer $INPUT $OUTPUT +diff $OUTPUT $EXPECTED > $DIFFLOG +if [ $? -eq 0 ] +then + echo "PASSED" >> $TESTLOG +else + echo "FAILED" >> $TESTLOG + echo "Diff output: " >> $TESTLOG + cat $DIFFLOG >> $TESTLOG +fi diff --git a/query_classifier/test/canonical_tests/expected.sql b/query_classifier/test/canonical_tests/expected.sql new file mode 100755 index 000000000..fabb27dc7 --- /dev/null +++ b/query_classifier/test/canonical_tests/expected.sql @@ -0,0 +1,17 @@ +select md5(?) =?, sleep(?), rand(?); +select * from my1 where md5(?) =?; +select md5(?) =?; +select * from my1 where md5(?) =?; +select sleep(?) +select * from tst where lname='?' +select ?,?,?,?,?,? from tst +select * from tst where fname like '?' +select * from tst where lname like '?' order by fname +insert into tst values ("?","?"),("?",?),("?","?") +drop table if exists tst +create table tst(fname varchar(30), lname varchar(30)) +update tst set lname="?" where fname like '?' or lname like '?' +delete from tst where lname like '?' and fname like '?' +select ? from tst where fname='?' or lname like '?' +select ?,?,?,? from tst where name='?' or name='?' or name='?' +select count(?),count(?),count(?),count(?),count (?),count(?) from tst diff --git a/query_classifier/test/canonical_tests/input.sql b/query_classifier/test/canonical_tests/input.sql new file mode 100755 index 000000000..59e9a2496 --- /dev/null +++ b/query_classifier/test/canonical_tests/input.sql @@ -0,0 +1,17 @@ +select md5("200000foo") =10, sleep(2), rand(100); +select * from my1 where md5("110") =10; +select md5("100foo") =10; +select * from my1 where md5("100") =10; +select sleep(2); +select * from tst where lname='Doe'; +select 1,2,3,4,5,6 from tst; +select * from tst where fname like '%a%'; +select * from tst where lname like '%e%' order by fname; +insert into tst values ("John","Doe"),("Plato",null),("Nietzsche",""); +drop table if exists tst; +create table tst(fname varchar(30), lname varchar(30)); +update tst set lname="Human" where fname like '%a%' or lname like '%a%'; +delete from tst where lname like '%man%' and fname like '%ard%'; +select 100 from tst where fname='10' or lname like '%100%'; +select 1,20,300,4000 from tst where name='1000' or name='200' or name='30' or name='4'; +select count(1),count(10),count(100),count(2),count (20),count(200) from tst; diff --git a/query_classifier/test/makefile b/query_classifier/test/makefile index d6a5cce52..c42a795e3 100644 --- a/query_classifier/test/makefile +++ b/query_classifier/test/makefile @@ -18,7 +18,7 @@ UTILS_PATH := $(ROOT_PATH)/utils TESTAPP = $(TESTPATH)/testmain testall:buildtests - + $(MAKE) -C canonical_tests testall testalllaters: $(MAKE) cleantests $(MAKE) DEBUG=Y DYNLIB=Y buildtests @@ -80,4 +80,4 @@ ifeq ($?, 0) else @echo "Query Classifier FAILED" >> $(TESTLOG) endif - @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/Makefile b/server/Makefile index 09120da07..fda9d14c1 100644 --- a/server/Makefile +++ b/server/Makefile @@ -24,6 +24,9 @@ # 08/07/13 Mark Riddoch Addition of monitor modules # 16/07/13 Mark Riddoch Renamed things to match the new naming +include ../build_gateway.inc +include ../makefile.inc + DEST=$(HOME)/usr/local/skysql all: @@ -45,7 +48,7 @@ testall: $(MAKE) -C test HAVE_SRV=$(HAVE_SRV) testall clean: - (cd Documentation; rm -rf html) + (cd Documentation; $(DEL) html) (cd core; touch depend.mk ; make clean) (cd modules/routing; touch depend.mk ; make clean) (cd modules/protocol; touch depend.mk ; make clean) diff --git a/server/core/Makefile b/server/core/Makefile index cb0250d21..21ce90ee5 100644 --- a/server/core/Makefile +++ b/server/core/Makefile @@ -109,14 +109,14 @@ maxpasswd: $(POBJS) echo '#define MAXSCALE_VERSION "'`cat ../../VERSION`'"' > ../include/version.h clean: - rm -f $(OBJ) maxscale - - rm *.so + $(DEL) $(OBJ) maxscale + $(DEL) *.so tags: ctags $(SRCS) $(HDRS) depend: ../include/version.h - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk install: maxscale maxkeys maxpasswd diff --git a/server/core/adminusers.c b/server/core/adminusers.c index 6ed70ed3c..61cd7c077 100644 --- a/server/core/adminusers.c +++ b/server/core/adminusers.c @@ -298,6 +298,7 @@ char* admin_remove_user( fname, err))); fclose(fp); + fclose(fp_tmp); unlink(fname_tmp); return ADMIN_ERR_PWDFILEACCESS; } @@ -325,6 +326,7 @@ char* admin_remove_user( fname, err))); fclose(fp); + fclose(fp_tmp); unlink(fname_tmp); return ADMIN_ERR_PWDFILEACCESS; } diff --git a/server/core/buffer.c b/server/core/buffer.c index 3c78ed222..1e827f29f 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -41,6 +41,11 @@ #include #include +static buffer_object_t* gwbuf_remove_buffer_object( + GWBUF* buf, + buffer_object_t* bufobj); + + /** * Allocate a new gateway buffer structure of size bytes. * @@ -78,7 +83,7 @@ SHARED_BUF *sbuf; free(sbuf); return NULL; } - spinlock_init(&rval->lock); + spinlock_init(&rval->gwbuf_lock); rval->start = sbuf->data; rval->end = rval->start + size; sbuf->refcount = 1; @@ -87,7 +92,8 @@ SHARED_BUF *sbuf; rval->hint = NULL; rval->properties = NULL; rval->gwbuf_type = GWBUF_TYPE_UNDEFINED; - rval->command = 0; + rval->gwbuf_info = GWBUF_INFO_NONE; + rval->gwbuf_bufobj = NULL; CHK_GWBUF(rval); return rval; } @@ -102,11 +108,20 @@ gwbuf_free(GWBUF *buf) { BUF_PROPERTY *prop; + buffer_object_t* bo; + CHK_GWBUF(buf); if (atomic_add(&buf->sbuf->refcount, -1) == 1) { free(buf->sbuf->data); free(buf->sbuf); + bo = buf->gwbuf_bufobj; + + while (bo != NULL) + { + bo = gwbuf_remove_buffer_object(buf, bo); + } + } while (buf->properties) { @@ -153,6 +168,8 @@ GWBUF *rval; rval->gwbuf_type = buf->gwbuf_type; rval->properties = NULL; rval->hint = NULL; + rval->gwbuf_info = buf->gwbuf_info; + rval->gwbuf_bufobj = buf->gwbuf_bufobj; rval->next = NULL; CHK_GWBUF(rval); return rval; @@ -181,6 +198,8 @@ GWBUF *gwbuf_clone_portion( clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone the type for now */ clonebuf->properties = NULL; clonebuf->hint = NULL; + clonebuf->gwbuf_info = buf->gwbuf_info; + clonebuf->gwbuf_bufobj = buf->gwbuf_bufobj; clonebuf->next = NULL; CHK_GWBUF(clonebuf); return clonebuf; @@ -323,11 +342,13 @@ int rval = 0; } /** - * Trim bytes form the end of a GWBUF structure + * Trim bytes form the end of a GWBUF structure. If the + * buffer has n_bytes or less then it will be freed and + * NULL will be returned. * * @param buf The buffer to trim - * @param nbytes The number of bytes to trim off - * @return The buffer chain + * @param n_bytes The number of bytes to trim off + * @return The buffer chain or NULL if buffer has <= n_bytes */ GWBUF * gwbuf_trim(GWBUF *buf, unsigned int n_bytes) @@ -361,6 +382,90 @@ void gwbuf_set_type( } } +/** + * Add a buffer object to GWBUF buffer. + * + * @param buf GWBUF where object is added + * @param id Type identifier for object + * @param data Object data + * @param donefun_dp Clean-up function to be executed before buffer is freed. + */ +void gwbuf_add_buffer_object( + GWBUF* buf, + bufobj_id_t id, + void* data, + void (*donefun_fp)(void *)) +{ + buffer_object_t** p_b; + buffer_object_t* newb; + + CHK_GWBUF(buf); + newb = (buffer_object_t *)malloc(sizeof(buffer_object_t)); + newb->bo_id = id; + newb->bo_data = data; + newb->bo_donefun_fp = donefun_fp; + newb->bo_next = NULL; + /** Lock */ + spinlock_acquire(&buf->gwbuf_lock); + p_b = &buf->gwbuf_bufobj; + /** Search the end of the list and add there */ + while (*p_b != NULL) + { + p_b = &(*p_b)->bo_next; + } + *p_b = newb; + /** Set flag */ + buf->gwbuf_info |= GWBUF_INFO_PARSED; + /** Unlock */ + spinlock_release(&buf->gwbuf_lock); +} + +/** + * Search buffer object which matches with the id. + * + * @param buf GWBUF to be searched + * @param id Identifier for the object + * + * @return Searched buffer object or NULL if not found + */ +void* gwbuf_get_buffer_object_data( + GWBUF* buf, + bufobj_id_t id) +{ + buffer_object_t* bo; + + CHK_GWBUF(buf); + /** Lock */ + spinlock_acquire(&buf->gwbuf_lock); + bo = buf->gwbuf_bufobj; + + while (bo != NULL && bo->bo_id != id) + { + bo = bo->bo_next; + } + /** Unlock */ + spinlock_release(&buf->gwbuf_lock); + + return bo->bo_data; +} + +/** + * @return pointer to next buffer object or NULL + */ +static buffer_object_t* gwbuf_remove_buffer_object( + GWBUF* buf, + buffer_object_t* bufobj) +{ + buffer_object_t* next; + + next = bufobj->bo_next; + /** Call corresponding clean-up function to clean buffer object's data */ + bufobj->bo_donefun_fp(bufobj->bo_data); + free(bufobj); + return next; +} + + /** * Add a property to a buffer. @@ -380,10 +485,10 @@ BUF_PROPERTY *prop; prop->name = strdup(name); prop->value = strdup(value); - spinlock_acquire(&buf->lock); + spinlock_acquire(&buf->gwbuf_lock); prop->next = buf->properties; buf->properties = prop; - spinlock_release(&buf->lock); + spinlock_release(&buf->gwbuf_lock); return 1; } @@ -398,11 +503,11 @@ gwbuf_get_property(GWBUF *buf, char *name) { BUF_PROPERTY *prop; - spinlock_acquire(&buf->lock); + spinlock_acquire(&buf->gwbuf_lock); prop = buf->properties; while (prop && strcmp(prop->name, name) != 0) prop = prop->next; - spinlock_release(&buf->lock); + spinlock_release(&buf->gwbuf_lock); if (prop) return prop->value; return NULL; @@ -451,7 +556,7 @@ gwbuf_add_hint(GWBUF *buf, HINT *hint) { HINT *ptr; - spinlock_acquire(&buf->lock); + spinlock_acquire(&buf->gwbuf_lock); if (buf->hint) { ptr = buf->hint; @@ -463,7 +568,6 @@ HINT *ptr; { buf->hint = hint; } - spinlock_release(&buf->lock); + spinlock_release(&buf->gwbuf_lock); return 1; } - diff --git a/server/core/config.c b/server/core/config.c index f2c9185dc..1a8689bfb 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -65,6 +65,29 @@ static char *config_file = NULL; static GATEWAY_CONF gateway; char *version_string = NULL; + +/** + * Trim whitespace from the front and rear of a string + * + * @param str String to trim + * @return Trimmed string, changes are done in situ + */ +static char * +trim(char *str) +{ +char *ptr; + + while (isspace(*str)) + str++; + + /* Point to last character of the string */ + ptr = str + strlen(str) - 1; + while (ptr > str && isspace(*ptr)) + *ptr-- = 0; + + return str; +} + /** * Config item handler for the ini file reader * @@ -508,7 +531,7 @@ int error_count = 0; CONFIG_CONTEXT *obj1 = context; while (obj1) { - if (strcmp(s, obj1->object) == 0 && + if (strcmp(trim(s), obj1->object) == 0 && obj->element && obj1->element) { serviceAddBackend( diff --git a/server/core/dbusers.c b/server/core/dbusers.c index dd36d683c..d318fd5f2 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -187,14 +187,6 @@ getUsers(SERVICE *service, struct users *users) if (service_user == NULL || service_passwd == NULL) return -1; - /** multi-thread environment requires that thread init succeeds. */ - if (mysql_thread_init()) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : mysql_thread_init failed."))); - return -1; - } - con = mysql_init(NULL); if (con == NULL) { @@ -388,10 +380,9 @@ getUsers(SERVICE *service, struct users *users) memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); free(users_data); - + free(key.user); mysql_free_result(result); mysql_close(con); - mysql_thread_end(); return total_users; } diff --git a/server/core/dcb.c b/server/core/dcb.c index 1dfde0b58..577a89210 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -814,7 +814,7 @@ int below_water; } spinlock_acquire(&dcb->writeqlock); - + if (dcb->writeq != NULL) { /* @@ -978,9 +978,10 @@ int below_water; } if (dolog) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Writing to %s socket failed due %d, %s.", + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Writing to %s socket failed due %d, %s.", + pthread_self(), dcb_isclient(dcb) ? "client" : "backend server", saved_errno, strerror(saved_errno)))); @@ -1703,14 +1704,15 @@ int gw_write( /** * 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. + * 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 callback The callback function to call * @param userdata User data to send in the call * @return Non-zero (true) if the callback was added */ @@ -1766,7 +1768,7 @@ int rval = 1; * * @param dcb The DCB to add the callback to * @param reason The callback reason - * @param cb The callback function to call + * @param callback The callback function to call * @param userdata User data to send in the call * @return Non-zero (true) if the callback was removed */ @@ -1839,7 +1841,7 @@ DCB_CALLBACK *cb, *nextcb; /** * Check the passed DCB to ensure it is in the list of allDCBS * - * @param DCB The DCB to check + * @param dcb The DCB to check * @return 1 if the DCB is in the list, otherwise 0 */ int @@ -1936,8 +1938,8 @@ void dcb_call_foreach ( * Null protocol write routine used for cloned dcb's. It merely consumes * buffers written on the cloned DCB. * - * @params dcb The descriptor control block - * @params buf The buffer beign written + * @param dcb The descriptor control block + * @param buf The buffer being written * @return Always returns a good write operation result */ static int diff --git a/server/core/filter.c b/server/core/filter.c index baeea21d7..3b3ef4dd6 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -39,8 +39,8 @@ extern int lm_enabled_logfiles_bitmask; -static SPINLOCK filter_spin = SPINLOCK_INIT; -static FILTER_DEF *allFilters = NULL; +static SPINLOCK filter_spin = SPINLOCK_INIT; /**< Protects the list of all filters */ +static FILTER_DEF *allFilters = NULL; /**< The list of all filters */ /** * Allocate a new filter within MaxScale @@ -79,7 +79,7 @@ FILTER_DEF *filter; /** * Deallocate the specified filter * - * @param server The service to deallocate + * @param filter The filter to deallocate * @return Returns true if the server was freed */ void @@ -243,8 +243,8 @@ int i; /** * Add a router option to a service * - * @param service The service to add the router option to - * @param option The option string + * @param filter The filter to add the option to + * @param option The option string */ void filterAddOption(FILTER_DEF *filter, char *option) @@ -273,7 +273,7 @@ int i; /** * Add a router parameter to a service * - * @param service The service to add the router option to + * @param filter The filter to add the parameter to * @param name The parameter name * @param value The parameter value */ @@ -318,6 +318,9 @@ filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream) { DOWNSTREAM *me; + if (filter == NULL) + return NULL; + if (filter->obj == NULL) { /* Filter not yet loaded */ @@ -359,7 +362,7 @@ DOWNSTREAM *me; UPSTREAM * filterUpstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream) { -UPSTREAM *me; +UPSTREAM *me = NULL; /* * The the filter has no setUpstream entry point then is does diff --git a/server/core/gateway.c b/server/core/gateway.c index fda19fff8..7c3fe96aa 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -1339,11 +1339,16 @@ int main(int argc, char **argv) /* Init MaxScale poll system */ poll_init(); - /*< - * Start the services that were created above - */ + /** + * Init mysql thread context for main thread as well. Needed when users + * are queried from backends. + */ + mysql_thread_init(); + + /** Start the services that were created above */ n_services = serviceStartAll(); - if (n_services == 0) + + if (n_services == 0) { char* logerr = "Failed to start any MaxScale services. Exiting."; print_log_n_stderr(true, !daemon_mode, logerr, logerr, 0); @@ -1396,9 +1401,13 @@ int main(int argc, char **argv) /*< Stop all the monitors */ monitorStopAll(); + LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "MaxScale is shutting down."))); + /** Release mysql thread context*/ + mysql_thread_end(); + datadir_cleanup(); LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, diff --git a/server/core/hashtable.c b/server/core/hashtable.c index 50857bfec..81646a864 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -63,6 +63,10 @@ static void hashtable_read_lock(HASHTABLE *table); static void hashtable_read_unlock(HASHTABLE *table); static void hashtable_write_lock(HASHTABLE *table); static void hashtable_write_unlock(HASHTABLE *table); +static HASHTABLE *hashtable_alloc_real(HASHTABLE* target, + int size, + int (*hashfn)(), + int (*cmpfn)()); /** * Special null function used as default memory allfunctions in the hashtable @@ -89,14 +93,42 @@ nullfn(void *data) HASHTABLE * hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)()) { -HASHTABLE *rval; + return hashtable_alloc_real(NULL, size, hashfn, cmpfn); +} - if ((rval = malloc(sizeof(HASHTABLE))) == NULL) - return NULL; +HASHTABLE* hashtable_alloc_flat( + HASHTABLE* target, + int size, + int (*hashfn)(), + int (*cmpfn)()) +{ + return hashtable_alloc_real(target, size, hashfn, cmpfn); +} +static HASHTABLE * +hashtable_alloc_real( + HASHTABLE* target, + int size, + int (*hashfn)(), + int (*cmpfn)()) +{ + HASHTABLE *rval; + + if (target == NULL) + { + if ((rval = malloc(sizeof(HASHTABLE))) == NULL) + return NULL; + rval->ht_isflat = false; + } + else + { + rval = target; + rval->ht_isflat = true; + } + #if defined(SS_DEBUG) - rval->ht_chk_top = CHK_NUM_HASHTABLE; - rval->ht_chk_tail = CHK_NUM_HASHTABLE; + rval->ht_chk_top = CHK_NUM_HASHTABLE; + rval->ht_chk_tail = CHK_NUM_HASHTABLE; #endif rval->hashsize = size; rval->hashfn = hashfn; @@ -114,7 +146,7 @@ HASHTABLE *rval; return NULL; } memset(rval->entries, 0, size * sizeof(HASHENTRIES *)); - + return rval; } @@ -143,7 +175,11 @@ HASHENTRIES *entry, *ptr; } } free(table->entries); - free(table); + + if (!table->ht_isflat) + { + free(table); + } } /** diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 93cdf95f6..cba0c6533 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -281,6 +281,7 @@ MODULES *mod = registered; * @param dlhandle The handle returned by dlopen * @param version The version string returned by the module * @param modobj The module object + * @param mod_info The module information */ static void register_module(const char *module, const char *type, void *dlhandle, char *version, void *modobj, MODULE_INFO *mod_info) diff --git a/server/core/maxpasswd.c b/server/core/maxpasswd.c index ac8b0a042..d11980ba3 100644 --- a/server/core/maxpasswd.c +++ b/server/core/maxpasswd.c @@ -34,7 +34,7 @@ * Encrypt a password for storing in the MaxScale.cnf file * * @param argc Argument count - * @param arv Argument vector + * @param argv Argument vector */ int main(int argc, char **argv) diff --git a/server/core/modutil.c b/server/core/modutil.c index f4dc224d9..e2800b849 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -29,6 +29,7 @@ */ #include #include +#include /** * Check if a GWBUF structure is a MySQL COM_QUERY packet @@ -171,3 +172,57 @@ GWBUF *addition; return orig; } + +/** + * Copy query string from GWBUF buffer to separate memory area. + * + * @param buf GWBUF buffer including the query + * + * @return Plaint text query if the packet type is COM_QUERY. Otherwise return + * a string including the packet type. + */ +char* modutil_get_query( + GWBUF* buf) +{ + uint8_t* packet; + mysql_server_cmd_t packet_type; + size_t len; + char* query_str; + + packet = GWBUF_DATA(buf); + packet_type = packet[4]; + + switch (packet_type) { + case MYSQL_COM_QUIT: + len = strlen("[Quit msg]")+1; + if ((query_str = (char *)malloc(len+1)) == NULL) + { + goto retblock; + } + memcpy(query_str, "[Quit msg]", len); + memset(&query_str[len], 0, 1); + break; + + case MYSQL_COM_QUERY: + len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */ + if ((query_str = (char *)malloc(len+1)) == NULL) + { + goto retblock; + } + memcpy(query_str, &packet[5], len); + memset(&query_str[len], 0, 1); + break; + + default: + len = strlen(STRPACKETTYPE(packet_type))+1; + if ((query_str = (char *)malloc(len+1)) == NULL) + { + goto retblock; + } + memcpy(query_str, STRPACKETTYPE(packet_type), len); + memset(&query_str[len], 0, 1); + break; + } /*< switch */ +retblock: + return query_str; +} \ No newline at end of file diff --git a/server/core/monitor.c b/server/core/monitor.c index 8bb62bc2d..85ff878d7 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -207,7 +207,8 @@ MONITOR *ptr; /** * Show a single monitor * - * @param dcb DCB for printing output + * @param dcb DCB for printing output + * @param monitor The monitor to print information regarding */ void monitorShow(DCB *dcb, MONITOR *monitor) @@ -303,7 +304,7 @@ monitorSetInterval (MONITOR *mon, unsigned long interval) * Enable Replication Heartbeat support in monitor. * * @param mon The monitor instance - * @param interval The sampling interval in milliseconds + * @param replication_heartbeat The replication heartbeat */ void monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat) @@ -312,28 +313,3 @@ monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat) mon->module->replicationHeartbeat(mon->handle, replication_heartbeat); } } - -/** - * Iterate over the monitors, calling a function per call - * - * @param fcn The function to call - * @param data The data to pass to each call - */ -void -monitorIterate(void (*fcn)(MONITOR *, void *), void *data) -{ -MONITOR *monitor, *next; - - spinlock_acquire(&monLock); - monitor = allMonitors; - while (monitor) - { - next = monitor->next; - spinlock_release(&monLock); - (*fcn)(monitor, data); - spinlock_acquire(&monLock); - monitor = next; - } - spinlock_release(&monLock); - -} diff --git a/server/core/poll.c b/server/core/poll.c index 87d3640f0..5e37c1cf5 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -252,9 +252,11 @@ poll_waitevents(void *arg) static bool process_zombies_only = false; /*< flag for all threads */ DCB *zombies = NULL; - /* Add this thread to the bitmask of running polling threads */ + /** Add this thread to the bitmask of running polling threads */ bitmask_set(&poll_mask, thread_id); - + /** Init mysql thread context for use with a mysql handle and a parser */ + mysql_thread_init(); + while (1) { #if BLOCKINGPOLL @@ -495,6 +497,8 @@ poll_waitevents(void *arg) return; } } /*< while(1) */ + /** Release mysql thread context */ + mysql_thread_end(); } /** diff --git a/server/core/server.c b/server/core/server.c index a911390d2..e0e2a41d3 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -148,8 +148,7 @@ server_set_unique_name(SERVER *server, char *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 + * @param name The Server name defined in the header file * @return The server or NULL if not found */ SERVER * @@ -563,28 +562,3 @@ SERVER_PARAM *param = server->parameters; } return NULL; } - -/** - * Iterate over the servers, calling a function per call - * - * @param fcn The function to call - * @param data The data to pass to each call - */ -void -serverIterate(void (*fcn)(SERVER *, void *), void *data) -{ -SERVER *server, *next; - - spinlock_acquire(&server_spin); - server = allServers; - while (server) - { - next = server->next; - spinlock_release(&server_spin); - (*fcn)(server, data); - spinlock_acquire(&server_spin); - server = next; - } - spinlock_release(&server_spin); - -} diff --git a/server/core/service.c b/server/core/service.c index 592d90321..2068c4757 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -675,6 +675,7 @@ int n = 0; "Unable to find filter '%s' for service '%s'\n", trim(ptr), service->name ))); + n--; } flist[n] = NULL; ptr = strtok_r(NULL, "|", &brkt); @@ -1008,7 +1009,7 @@ bool service_set_param_value ( { char* p; int valint; - bool succp; + bool succp = true; /** * Find out whether the value is numeric and ends with '%' or '\0' @@ -1171,28 +1172,3 @@ serviceGetWeightingParameter(SERVICE *service) { return service->weightby; } - -/** - * Iterate over the services, calling a function per call - * - * @param fcn The function to call - * @param data The data to pass to each call - */ -void -serviceIterate(void (*fcn)(SERVICE *, void *), void *data) -{ -SERVICE *service, *next; - - spinlock_acquire(&service_spin); - service = allServices; - while (service) - { - next = service->next; - spinlock_release(&service_spin); - (*fcn)(service, data); - spinlock_acquire(&service_spin); - service = next; - } - spinlock_release(&service_spin); - -} diff --git a/server/core/session.c b/server/core/session.c index 405a8dfc0..db59f9e6e 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -333,13 +333,15 @@ bool session_free( { for (i = 0; i < session->n_filters; i++) { - session->filters[i].filter->obj->closeSession( + if (session->filters[i].filter) + session->filters[i].filter->obj->closeSession( session->filters[i].instance, session->filters[i].session); } for (i = 0; i < session->n_filters; i++) { - session->filters[i].filter->obj->freeSession( + if (session->filters[i].filter) + session->filters[i].filter->obj->freeSession( session->filters[i].instance, session->filters[i].session); } @@ -653,6 +655,14 @@ int i; session->n_filters = service->n_filters; for (i = service->n_filters - 1; i >= 0; i--) { + if (service->filters[i] == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Service '%s' contians an unresolved filter.\n", + service->name))); + return 0; + } if ((head = filterApply(service->filters[i], session, &session->head)) == NULL) { @@ -757,28 +767,3 @@ session_getUser(SESSION *session) { return (session && session->client) ? session->client->user : NULL; } - -/** - * Iterate over the sessions, calling a function per call - * - * @param fcn The function to call - * @param data The data to pass to each call - */ -void -sessionIterate(void (*fcn)(SESSION *, void *), void *data) -{ -SESSION *session, *next; - - spinlock_acquire(&session_spin); - session = allSessions; - while (session) - { - next = session->next; - spinlock_release(&session_spin); - (*fcn)(session, data); - spinlock_acquire(&session_spin); - session = next; - } - spinlock_release(&session_spin); - -} diff --git a/server/core/test/makefile b/server/core/test/makefile index 0cccd0f64..7f6831082 100644 --- a/server/core/test/makefile +++ b/server/core/test/makefile @@ -8,7 +8,20 @@ include ../../../makefile.inc include ../../../test.inc CC=cc -TESTLOG := $(shell pwd)/testhash.log +TESTLOG := $(shell pwd)/testcore.log + +LOGPATH := $(ROOT_PATH)/log_manager +UTILSPATH := $(ROOT_PATH)/utils + +LDFLAGS=-rdynamic -L$(LOGPATH) \ + -Wl,-rpath,$(DEST)/lib \ + -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \ + -Wl,-rpath,$(EMBEDDED_LIB) + +LIBS= -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ + -L../../inih/extra -linih -lssl -lstdc++ + +TESTS=testhash testspinlock testfilter testadminusers cleantests: - $(DEL) *.o @@ -20,19 +33,41 @@ testall: $(MAKE) DEBUG=Y buildtests $(MAKE) runtests -buildtests : +buildtests : $(TESTS) + +testhash: testhash.c $(CC) $(CFLAGS) \ -I$(ROOT_PATH)/server/include \ -I$(ROOT_PATH)/utils \ testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash -runtests: +testspinlock: testspinlock.c + $(CC) $(CFLAGS) \ + -I$(ROOT_PATH)/server/include \ + -I$(ROOT_PATH)/utils \ + testspinlock.c ../spinlock.o ../atomic.o ../thread.o -o testspinlock + +testfilter: testfilter.c libcore.a + $(CC) $(CFLAGS) $(LDFLAGS) \ + -I$(ROOT_PATH)/server/include \ + -I$(ROOT_PATH)/utils \ + testfilter.c libcore.a $(UTILSPATH)/skygw_utils.o $(LIBS) -o testfilter + +testadminusers: testadminusers.c libcore.a + $(CC) $(CFLAGS) $(LDFLAGS) \ + -I$(ROOT_PATH)/server/include \ + -I$(ROOT_PATH)/utils \ + testadminusers.c libcore.a $(UTILSPATH)/skygw_utils.o $(LIBS) -o testadminusers + +libcore.a: ../*.o + ar rv libcore.a ../*.o + +runtests: $(TESTS) @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) @echo "Test MaxScale core" >> $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) - @-./testhash 2>> $(TESTLOG) - if [ "$$?" == "0" ];then echo "MaxScale core PASSED">> $(TESTLOG);else echo "MaxScale core FAILED">> $(TESTLOG);fi + $(foreach var,$(TESTS),./runtest.sh $(var) $(TESTLOG);) @echo "" >> $(TESTLOG) @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/core/test/runtest.sh b/server/core/test/runtest.sh new file mode 100755 index 000000000..434d45701 --- /dev/null +++ b/server/core/test/runtest.sh @@ -0,0 +1,10 @@ +#!/bin/bash +test=$1 +log=$2 +echo Running test $test >> $log +./$test 2>> $log +if [ $? -ne 0 ]; then + echo $test " " FAILED >> $log +else + echo $test " " PASSED >> $log +fi diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c new file mode 100644 index 000000000..00ec3a452 --- /dev/null +++ b/server/core/test/testadminusers.c @@ -0,0 +1,278 @@ +/* + * This file is distributed as part of MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright SkySQL Ab 2014 + */ + +/** + * + * @verbatim + * Revision History + * + * Date Who Description + * 20-08-2014 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include +#include +#include + +#include + + +/** + * test1 default user + * + * Test that the username password admin/skysql is accepted if no users + * have been created and that no other users are accepted + * + * WARNING: $MAXSCALE_HOME/etc/passwd must be removed before this test is run + */ +static int +test1() +{ + if (admin_verify("admin", "skysql") == 0) + { + fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n"); + return 1; + } + if (admin_verify("bad", "user")) + { + fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n"); + return 1; + } + + return 0; +} + +/** + * test2 creating users + * + * Create a user + * Try to create a duplicate user - expects a failure + * Remove that user - expected to fail as one user must always remain + */ +static int +test2() +{ +char *err; + + if ((err = admin_add_user("user0", "passwd0")) != NULL) + { + fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err); + + return 1; + } + if (admin_add_user("user0", "passwd0") == NULL) + { + fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, du;plicate.\n"); + + return 1; + } + + /* Deleting the last user is forbidden so we expect this to fail */ + if ((err = admin_remove_user("user0", "passwd0")) == NULL) + { + fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err); + + return 1; + } + return 0; +} + +/** + * test3 search/verify users + * + * Create a user + * Search for that user + * Search for a non-existant user + * Remove the user + * Search for the user that was removed + */ +static int +test3() +{ +char *err; + + if ((err = admin_add_user("user1", "passwd1")) != NULL) + { + fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err); + + return 1; + } + + if (admin_search_user("user1") == 0) + { + fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n"); + + return 1; + } + if (admin_search_user("user2") != 0) + { + fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n"); + + return 1; + } + + if ((err = admin_remove_user("user1", "passwd1")) != NULL) + { + fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err); + + return 1; + } + + if (admin_search_user("user1")) + { + fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n"); + + return 1; + } + return 0; +} + +/** + * test4 verify users + * + * Create a numebr of users + * search for each user in turn + * verify each user in turn (password verification) + * Verify each user in turn with incorrect password + * Randomly verify each user + * Remove each user + */ +static int +test4() +{ +char *err, user[40], passwd[40]; +int i, n_users = 50; + + for (i = 1; i < n_users; i++) + { + sprintf(user, "user%d", i); + sprintf(passwd, "passwd%d", i); + if ((err = admin_add_user(user, passwd)) != NULL) + { + fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err); + + return 1; + } + } + + for (i = 1; i < n_users; i++) + { + sprintf(user, "user%d", i); + if (admin_search_user(user) == 0) + { + fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n"); + + return 1; + } + } + for (i = 1; i < n_users; i++) + { + sprintf(user, "user%d", i); + sprintf(passwd, "passwd%d", i); + if (admin_verify(user, passwd) == 0) + { + fprintf(stderr, "admin_verify: test 4.3 (search user) failed.\n"); + + return 1; + } + } + + for (i = 1; i < n_users; i++) + { + sprintf(user, "user%d", i); + sprintf(passwd, "badpasswd%d", i); + if (admin_verify(user, passwd) != 0) + { + fprintf(stderr, "admin_verify: test 4.4 (search user) failed.\n"); + + return 1; + } + } + srand(time(0)); + for (i = 1; i < 1000; i++) + { + int j; + j = rand() % n_users; + if (j == 0) + j = 1; + sprintf(user, "user%d", j); + sprintf(passwd, "passwd%d", j); + if (admin_verify(user, passwd) == 0) + { + fprintf(stderr, "admin_verify: test 4.5 (random) failed.\n"); + + return 1; + } + } + + for (i = 1; i < n_users; i++) + { + sprintf(user, "user%d", i); + sprintf(passwd, "passwd%d", i); + if ((err = admin_remove_user(user, passwd)) != NULL) + { + fprintf(stderr, "admin_remove_user: test 4.6 (add user) failed, %s.\n", err); + + return 1; + } + } + return 0; +} + +/** + * test5 remove first user + * + * Create a user so that user0 may be removed + * Remove the first user created (user0) + */ +static int +test5() +{ +char *err; + + if ((err = admin_add_user("user", "passwd")) != NULL) + { + fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err); + + return 1; + } + if ((err = admin_remove_user("user0", "passwd0")) != NULL) + { + fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err); + + return 1; + } + return 0; +} + +int +main(int argc, char **argv) +{ +int result = 0; + + result += test1(); + result += test2(); + result += test3(); + result += test4(); + result += test5(); + + exit(result); +} + diff --git a/server/core/test/testfilter.c b/server/core/test/testfilter.c new file mode 100644 index 000000000..55f7fadf3 --- /dev/null +++ b/server/core/test/testfilter.c @@ -0,0 +1,155 @@ +/* + * This file is distributed as part of MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright SkySQL Ab 2014 + */ + +/** + * + * @verbatim + * Revision History + * + * Date Who Description + * 19-08-2014 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include +#include +#include + +#include + + +/** + * test1 Filter creation, finding and deletion + * + */ +static int +test1() +{ +FILTER_DEF *f1, *f2; + + if ((f1 = filter_alloc("test1", "module")) == NULL) + { + fprintf(stderr, "filter_alloc: test 1 failed.\n"); + return 1; + } + if ((f2 = filter_find("test1")) == NULL) + { + fprintf(stderr, "filter_find: test 2 failed.\n"); + return 1; + } + filter_free(f1); + if ((f2 = filter_find("test1")) != NULL) + { + fprintf(stderr, "filter_find: test 3 failed delete.\n"); + return 1; + } + + return 0; +} + + +/** + * Passive tests for filter_add_option and filter_add_parameter + * + * These tests add options and parameters to a filter, the only failure + * is related hard crashes, such as SIGSEGV etc. as there are no good hooks + * to check the creation of parameters and options currently. + */ +static int +test2() +{ +FILTER_DEF *f1; + + if ((f1 = filter_alloc("test1", "module")) == NULL) + { + fprintf(stderr, "filter_alloc: test 1 failed.\n"); + return 1; + } + filterAddOption(f1, "option1"); + filterAddOption(f1, "option2"); + filterAddOption(f1, "option3"); + filterAddParameter(f1, "name1", "value1"); + filterAddParameter(f1, "name2", "value2"); + filterAddParameter(f1, "name3", "value3"); + return 0; +} + + +/** + * test3 Filter creation, finding and deletion soak test + * + */ +static int +test3() +{ +FILTER_DEF *f1; +char name[40]; +int i, n_filters = 1000; + + for (i = 0; i < n_filters; i++) + { + sprintf(name, "filter%d", i); + if ((f1 = filter_alloc(name, "module")) == NULL) + { + fprintf(stderr, + "filter_alloc: test 3 failed with %s.\n", name); + return 1; + } + } + for (i = 0; i < n_filters; i++) + { + sprintf(name, "filter%d", i); + if ((f1 = filter_find(name)) == NULL) + { + fprintf(stderr, "filter_find: test 3 failed.\n"); + return 1; + } + } + for (i = 0; i < n_filters; i++) + { + sprintf(name, "filter%d", i); + if ((f1 = filter_find(name)) == NULL) + { + fprintf(stderr, "filter_find: test 3 failed.\n"); + return 1; + } + filter_free(f1); + if ((f1 = filter_find(name)) != NULL) + { + fprintf(stderr, + "filter_find: test 3 failed - found deleted filter.\n"); + return 1; + } + } + + return 0; +} + +int +main(int argc, char **argv) +{ +int result = 0; + + result += test1(); + result += test2(); + result += test3(); + + exit(result); +} + diff --git a/server/core/test/testspinlock.c b/server/core/test/testspinlock.c new file mode 100644 index 000000000..bcbfa2a3f --- /dev/null +++ b/server/core/test/testspinlock.c @@ -0,0 +1,132 @@ +/* + * This file is distributed as part of MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright SkySQL Ab 2014 + */ + +/** + * + * @verbatim + * Revision History + * + * Date Who Description + * 18/08-2014 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include +#include +#include + +#include +#include + + +/** + * test1 spinlock_acquire_nowait tests + * + * Test that spinlock_acquire_nowait returns false if the spinlock + * is already taken. + * + * Test that spinlock_acquire_nowait returns true if the spinlock + * is not taken. + * + * Test that spinlock_acquire_nowait does hold the spinlock. + */ +static int +test1() +{ +SPINLOCK lck; + + spinlock_init(&lck); + spinlock_acquire(&lck); + if (spinlock_acquire_nowait(&lck)) + { + fprintf(stderr, "spinlock_acquire_nowait: test 1 failed.\n"); + return 1; + } + spinlock_release(&lck); + if (!spinlock_acquire_nowait(&lck)) + { + fprintf(stderr, "spinlock_acquire_nowait: test 2 failed.\n"); + return 1; + } + if (spinlock_acquire_nowait(&lck)) + { + fprintf(stderr, "spinlock_acquire_nowait: test 3 failed.\n"); + return 1; + } + spinlock_release(&lck); + + return 0; +} + +static int acquire_time; + +static void +test2_helper(void *data) +{ +SPINLOCK *lck = (SPINLOCK *)data; +unsigned long t1 = time(0); + + spinlock_acquire(lck); + acquire_time = time(0) - t1; + spinlock_release(lck); + return; +} + +/** + * Check that spinlock correctly blocks another thread whilst the spinlock + * is held. + * + * Take out a lock. + * Start a second thread to take the same lock + * sleep for 10 seconds + * release lock + * verify that second thread took at least 8 seconds to obtain the lock + */ +static int +test2() +{ +SPINLOCK lck; +void *handle; + + acquire_time = 0; + spinlock_init(&lck); + spinlock_acquire(&lck); + handle = thread_start(test2_helper, (void *)&lck); + sleep(10); + spinlock_release(&lck); + thread_wait(handle); + + if (acquire_time < 8) + { + fprintf(stderr, "spinlock: test 1 failed.\n"); + return 1; + } + return 0; +} + +main(int argc, char **argv) +{ +int result = 0; + + result += test1(); + result += test2(); + + exit(result); +} + diff --git a/server/core/utils.c b/server/core/utils.c index c7e42dda1..1b8c6b5fe 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -204,7 +204,7 @@ void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_le /** - * @node Gets errno corresponding to latest socket error + * node Gets errno corresponding to latest socket error * * Parameters: * @param fd - in, use diff --git a/server/include/adminusers.h b/server/include/adminusers.h index d46570e38..07afa5390 100644 --- a/server/include/adminusers.h +++ b/server/include/adminusers.h @@ -29,6 +29,8 @@ * * @endverbatim */ +#include + #define ADMIN_SALT "MS" extern int admin_verify(char *, char *); diff --git a/server/include/buffer.h b/server/include/buffer.h index 5ef3b3210..c248a770d 100644 --- a/server/include/buffer.h +++ b/server/include/buffer.h @@ -43,11 +43,14 @@ * * @endverbatim */ -#include +#include #include #include +#include +EXTERN_C_BLOCK_BEGIN + /** * Buffer properties - used to store properties related to the buffer * contents. This may be added at any point during the processing of the @@ -89,6 +92,35 @@ typedef struct { int refcount; /*< Reference count on the buffer */ } SHARED_BUF; +typedef enum +{ + GWBUF_INFO_NONE = 0x0, + GWBUF_INFO_PARSED = 0x1 +} gwbuf_info_t; + +#define GWBUF_IS_PARSED(b) (b->gwbuf_info & GWBUF_INFO_PARSED) + +/** + * A structure for cleaning up memory allocations of structures which are + * referred to by GWBUF and deallocated in gwbuf_free but GWBUF doesn't + * know what they are. + * All functions on the list are executed before freeing memory of GWBUF struct. + */ +typedef enum +{ + GWBUF_PARSING_INFO +} bufobj_id_t; + +typedef struct buffer_object_st buffer_object_t; + +struct buffer_object_st { + bufobj_id_t bo_id; + void* bo_data; + void (*bo_donefun_fp)(void *); + buffer_object_t* bo_next; +}; + + /** * The buffer structure used by the descriptor control blocks. * @@ -98,14 +130,15 @@ typedef struct { * be copied within the gateway. */ typedef struct gwbuf { + SPINLOCK gwbuf_lock; struct gwbuf *next; /*< Next buffer in a linked chain of buffers */ void *start; /*< Start of the valid data */ void *end; /*< First byte after the valid data */ SHARED_BUF *sbuf; /*< The shared buffer with the real data */ - int command;/*< The command type for the queue */ + buffer_object_t *gwbuf_bufobj; /*< List of objects referred to by GWBUF */ + gwbuf_info_t gwbuf_info; /*< Info bits */ gwbuf_type_t gwbuf_type; /*< buffer's data type information */ HINT *hint; /*< Hint data for this buffer */ - SPINLOCK lock; BUF_PROPERTY *properties; /*< Buffer properties */ } GWBUF; @@ -144,4 +177,14 @@ extern int gwbuf_add_property(GWBUF *buf, char *name, char *value); extern char *gwbuf_get_property(GWBUF *buf, char *name); extern GWBUF *gwbuf_make_contiguous(GWBUF *); extern int gwbuf_add_hint(GWBUF *, HINT *); + +void gwbuf_add_buffer_object(GWBUF* buf, + bufobj_id_t id, + void* data, + void (*donefun_fp)(void *)); +void* gwbuf_get_buffer_object_data(GWBUF* buf, bufobj_id_t id); + +EXTERN_C_BLOCK_END + + #endif diff --git a/server/include/dcb.h b/server/include/dcb.h index 88964fc31..4f8be99d4 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -145,9 +145,9 @@ typedef enum { DCB_STATE_POLLING, /*< Waiting in the poll loop */ DCB_STATE_LISTENING, /*< The DCB is for a listening socket */ DCB_STATE_DISCONNECTED, /*< The socket is now closed */ - DCB_STATE_FREED, /*< Memory freed */ DCB_STATE_NOPOLLING, /*< Removed from poll mask */ - DCB_STATE_ZOMBIE /*< DCB is no longer active, waiting to free it */ + DCB_STATE_ZOMBIE, /*< DCB is no longer active, waiting to free it */ + DCB_STATE_FREED /*< Memory freed */ } dcb_state_t; typedef enum { diff --git a/server/include/filter.h b/server/include/filter.h index 568df291a..3076587e6 100644 --- a/server/include/filter.h +++ b/server/include/filter.h @@ -61,7 +61,7 @@ typedef struct { * filter pipline * routeQuery Called on each query that requires * routing - * clientReply + * clientReply Called for each reply packet * diagnostics Called to force the filter to print * diagnostic output * @@ -88,21 +88,21 @@ typedef struct filter_object { */ #define FILTER_VERSION {1, 1, 0} /** - * The definition of a filter form the configuration file. + * The definition of a filter from the configuration file. * This is basically the link between a plugin to load and the * optons to pass to that plugin. */ typedef struct filter_def { - char *name; /*< The Filter name */ - char *module; /*< The module to load */ - char **options; /*< The options set for this filter */ + char *name; /**< The Filter name */ + char *module; /**< The module to load */ + char **options; /**< The options set for this filter */ FILTER_PARAMETER - **parameters; /*< The filter parameters */ - FILTER filter; - FILTER_OBJECT *obj; - SPINLOCK spin; + **parameters; /**< The filter parameters */ + FILTER filter; /**< The runtime filter */ + FILTER_OBJECT *obj; /**< The "MODULE_OBJECT" for the filter */ + SPINLOCK spin; /**< Spinlock to protect the filter definition */ struct filter_def - *next; /*< Next filter in the chain of all filters */ + *next; /**< Next filter in the chain of all filters */ } FILTER_DEF; FILTER_DEF *filter_alloc(char *, char *); diff --git a/server/include/hashtable.h b/server/include/hashtable.h index 92bc71b8e..175bd5d32 100644 --- a/server/include/hashtable.h +++ b/server/include/hashtable.h @@ -84,12 +84,17 @@ typedef struct hashtable { SPINLOCK spin; /**< Internal spinlock for the hashtable */ int n_readers; /**< Number of clients reading the table */ int writelock; /**< The table is locked by a writer */ + bool ht_isflat; /**< Indicates whether hashtable is in stack or heap */ #if defined(SS_DEBUG) skygw_chk_t ht_chk_tail; #endif } HASHTABLE; extern HASHTABLE *hashtable_alloc(int, int (*hashfn)(), int (*cmpfn)()); +HASHTABLE *hashtable_alloc_flat(HASHTABLE* target, + int size, + int (*hashfn)(), + int (*cmpfn)()); /**< Allocate a hashtable */ extern void hashtable_memory_fns(HASHTABLE *, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN); /**< Provide an interface to control key/value memory diff --git a/server/include/modutil.h b/server/include/modutil.h index 00336f937..a0624752a 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -36,4 +36,6 @@ extern int modutil_is_SQL(GWBUF *); extern int modutil_extract_SQL(GWBUF *, char **, int *); extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *); extern GWBUF *modutil_replace_SQL(GWBUF *, char *); +char* modutil_get_query(GWBUF* buf); + #endif diff --git a/server/include/monitor.h b/server/include/monitor.h index 4dee5f49c..d65fd075f 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -110,5 +110,4 @@ extern void monitorList(DCB *); extern void monitorSetId(MONITOR *, unsigned long); extern void monitorSetInterval (MONITOR *, unsigned long); extern void monitorSetReplicationHeartbeat(MONITOR *, int); -extern void monitorIterate(void (*fcn)(MONITOR *, void *), void *data); #endif diff --git a/server/include/server.h b/server/include/server.h index 5271cec17..c558418bc 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -169,5 +169,4 @@ extern void serverAddParameter(SERVER *, char *, char *); extern char *serverGetParameter(SERVER *, char *); extern void server_update(SERVER *, char *, char *, char *); extern void server_set_unique_name(SERVER *, char *); -extern void serverIterate(void (*fcn)(SERVER *, void *), void *data); #endif diff --git a/server/include/service.h b/server/include/service.h index 0d8d32cda..cd13d411b 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -161,7 +161,6 @@ extern void serviceSetFilters(SERVICE *, char *); extern int serviceEnableRootUser(SERVICE *, int ); extern void serviceWeightBy(SERVICE *, char *); extern char *serviceGetWeightingParameter(SERVICE *); -extern void serviceIterate(void (*fcn)(SERVICE *, void *), void *data); extern void service_update(SERVICE *, char *, char *, char *); extern int service_refresh_users(SERVICE *); extern void printService(SERVICE *); diff --git a/server/include/session.h b/server/include/session.h index 0386a5eb6..cbd43fe40 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -157,7 +157,6 @@ void dprintAllSessions(struct dcb *); void dprintSession(struct dcb *, SESSION *); void dListSessions(struct dcb *); char *session_state(int); -void sessionIterate(void (*fcn)(SESSION *, void *), void *data); bool session_link_dcb(SESSION *, struct dcb *); SESSION* get_session_by_router_ses(void* rses); #endif diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile index f8cf9105f..31b41ae93 100644 --- a/server/modules/filter/Makefile +++ b/server/modules/filter/Makefile @@ -81,7 +81,7 @@ tags: (cd hint; touch depend.mk; make tags) depend: - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk (cd hint; touch depend.mk; make depend) diff --git a/server/modules/filter/qlafilter.c b/server/modules/filter/qlafilter.c index da78713d2..b3accfbf8 100644 --- a/server/modules/filter/qlafilter.c +++ b/server/modules/filter/qlafilter.c @@ -17,6 +17,9 @@ */ /** + * @file qlafilter.c - Quary Log All Filter + * @verbatim + * * QLA Filter - Query Log All. A primitive query logging filter, simply * used to verify the filter mechanism for downstream filters. All queries * that are passed through the filter will be written to file. @@ -33,6 +36,7 @@ * 11/06/2014 Mark Riddoch Addition of source and match parameters * 19/06/2014 Mark Riddoch Addition of user parameter * + * @endverbatim */ #include #include @@ -154,6 +158,7 @@ GetModuleObject() * within MaxScale. * * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ diff --git a/server/modules/filter/regexfilter.c b/server/modules/filter/regexfilter.c index 5c4f1e7df..79cf70c09 100644 --- a/server/modules/filter/regexfilter.c +++ b/server/modules/filter/regexfilter.c @@ -27,7 +27,8 @@ extern int lm_enabled_logfiles_bitmask; /** - * regexfilter.c - a very simple regular expression rewrite filter. + * @file regexfilter.c - a very simple regular expression rewrite filter. + * @verbatim * * A simple regular expression query rewrite filter. * Two parameters should be defined in the filter configuration @@ -39,6 +40,7 @@ extern int lm_enabled_logfiles_bitmask; * * Date Who Description * 19/06/2014 Mark Riddoch Addition of source and user parameters + * @endverbatim */ MODULE_INFO info = { @@ -132,6 +134,7 @@ GetModuleObject() * within MaxScale. * * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ diff --git a/server/modules/filter/tee.c b/server/modules/filter/tee.c index 34efbb65b..f8e22c7eb 100644 --- a/server/modules/filter/tee.c +++ b/server/modules/filter/tee.c @@ -18,6 +18,7 @@ /** * @file tee.c A filter that splits the processing pipeline in two + * @verbatim * * Conditionally duplicate requests and send the duplicates to another service * within MaxScale. @@ -41,6 +42,7 @@ * 20/06/2014 Mark Riddoch Initial implementation * 24/06/2014 Mark Riddoch Addition of support for multi-packet queries * + * @endverbatim */ #include #include @@ -162,6 +164,7 @@ GetModuleObject() * within MaxScale. * * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ diff --git a/server/modules/filter/test/harness.c b/server/modules/filter/test/harness.c index ddcad822a..a5a929f56 100755 --- a/server/modules/filter/test/harness.c +++ b/server/modules/filter/test/harness.c @@ -396,8 +396,9 @@ void print_help() { printf("\nFilter Test Harness\n\n" - "List of commands:\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n" - "%-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n" + "List of commands:\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n " + "%-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n " + "%-32s%s\n %-32s%s\n" ,"help","Prints this help message." ,"run","Feeds the contents of the buffer to the filter chain." ,"add ","Loads a filter and appeds it to the end of the chain." @@ -407,6 +408,8 @@ void print_help() ,"config ","Loads filter configurations from a file." ,"in ","Source file for the SQL statements." ,"out ","Destination file for the SQL statements. Defaults to stdout if no parameters were passed." + ,"threads ","Sets the amount of threads to use" + ,"sessions ","How many sessions to create for each filter. This clears all loaded filters." ,"quiet","Print only error messages." ,"verbose","Print everything." ,"exit","Exit the program" @@ -490,9 +493,14 @@ FILTER_PARAMETER** read_params(int* paramc) int routeQuery(void* ins, void* session, GWBUF* queue) { - int buffsz = (int)(queue->end - (queue->start + 5)); + unsigned int buffsz = 0; + unsigned char* ptr = (void*)queue->start; char *qstr; + buffsz += *ptr++; + buffsz += *ptr++ << 8; + buffsz += *ptr++ << 16; + if(queue->hint){ buffsz += 40; if(queue->hint->data){ @@ -506,7 +514,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue) qstr = calloc(buffsz,sizeof(char)); if(qstr){ - memcpy(qstr,queue->start + 5,(int)(queue->end - 1 - (queue->start + 5))); + memcpy(qstr,queue->start + 5,buffsz - 1); if(queue->hint){ char *ptr = qstr + (int)(queue->end - 1 - (queue->start + 5)); @@ -621,9 +629,9 @@ void manual_query() gwbuf_set_type(instance.buffer[0],GWBUF_TYPE_MYSQL); memcpy(instance.buffer[0]->sbuf->data + 5,query,qlen); - instance.buffer[0]->sbuf->data[0] = (qlen>>0&1)|(qlen>>1&1) << 1; - instance.buffer[0]->sbuf->data[1] = (qlen>>2&1)|(qlen>>3&1) << 1; - instance.buffer[0]->sbuf->data[2] = (qlen>>4&1)|(qlen>>5&1) << 1; + instance.buffer[0]->sbuf->data[0] = (qlen); + instance.buffer[0]->sbuf->data[1] = (qlen << 8); + instance.buffer[0]->sbuf->data[2] = (qlen << 16); instance.buffer[0]->sbuf->data[3] = 0x00; instance.buffer[0]->sbuf->data[4] = 0x03; @@ -706,9 +714,9 @@ int load_query() memcpy(tmpbff[i]->sbuf->data + 5,query_list[i],strnlen(query_list[i],buff_sz)); qlen = strnlen(query_list[i],buff_sz); - tmpbff[i]->sbuf->data[0] = (qlen>>0&1)|(qlen>>1&1) << 1; - tmpbff[i]->sbuf->data[1] = (qlen>>2&1)|(qlen>>3&1) << 1; - tmpbff[i]->sbuf->data[2] = (qlen>>4&1)|(qlen>>5&1) << 1; + tmpbff[i]->sbuf->data[0] = qlen; + tmpbff[i]->sbuf->data[1] = (qlen << 8); + tmpbff[i]->sbuf->data[2] = (qlen << 16); tmpbff[i]->sbuf->data[3] = 0x00; tmpbff[i]->sbuf->data[4] = 0x03; diff --git a/server/modules/filter/testfilter.c b/server/modules/filter/testfilter.c index f72471a36..289ccf4a2 100644 --- a/server/modules/filter/testfilter.c +++ b/server/modules/filter/testfilter.c @@ -21,13 +21,15 @@ #include /** - * testfilter.c - a very simple test filter. + * @file testfilter.c - a very simple test filter. + * @verbatim * * This filter is a very simple example used to test the filter API, * it merely counts the number of statements that flow through the * filter pipeline. * * Reporting is done via the diagnostics print routine. + * @endverbatim */ MODULE_INFO info = { @@ -114,6 +116,7 @@ GetModuleObject() * within MaxScale. * * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ diff --git a/server/modules/filter/topfilter.c b/server/modules/filter/topfilter.c index d4d594e6d..9ca281ef1 100644 --- a/server/modules/filter/topfilter.c +++ b/server/modules/filter/topfilter.c @@ -17,6 +17,9 @@ */ /** + * @file topfilter.c - Top N Longest Running Queries + * @verbatim + * * TOPN Filter - Query Log All. A primitive query logging filter, simply * used to verify the filter mechanism for downstream filters. All queries * that are passed through the filter will be written to file. @@ -30,6 +33,8 @@ * * Date Who Description * 18/06/2014 Mark Riddoch Addition of source and user filters + * + * @endverbatim */ #include #include @@ -172,6 +177,7 @@ GetModuleObject() * within MaxScale. * * @param options The options for this filter + * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ diff --git a/server/modules/include/httpd.h b/server/modules/include/httpd.h index 920e65b2d..0d5e65604 100644 --- a/server/modules/include/httpd.h +++ b/server/modules/include/httpd.h @@ -48,20 +48,18 @@ #define HTTPD_FIELD_MAXLEN 8192 #define HTTPD_REQUESTLINE_MAXLEN 8192 -typedef enum { - METHOD_UNKNOWN = 0, - METHOD_POST, - METHOD_PUT, - METHOD_GET, - METHOD_HEAD -} HTTP_METHOD; /** * HTTPD session specific data * */ typedef struct httpd_session { - HTTP_METHOD method; - GWBUF *saved; - int request_len; - char *url; + char user[HTTPD_USER_MAXLEN]; /*< username for authentication*/ + char *cookies; /*< all input cookies */ + char hostname[HTTPD_HOSTNAME_MAXLEN]; /*< The hostname */ + char useragent[HTTPD_USERAGENT_MAXLEN]; /*< The useragent */ + char method[HTTPD_METHOD_MAXLEN]; /*< The HTTPD Method */ + char *url; /*< the URL in the request */ + char *path_info; /*< the Pathinfo, starts with /, is the extra path segments after the document name */ + char *query_string; /*< the Query string, starts with ?, after path_info and document name */ + int headers_received; /*< All the headers has been received, if 1 */ } HTTPD_session; diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 9e2147d05..c875a1232 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -102,6 +102,12 @@ typedef enum { MYSQL_IDLE } mysql_auth_state_t; +typedef enum { + MYSQL_PROTOCOL_ALLOC, + MYSQL_PROTOCOL_ACTIVE, + MYSQL_PROTOCOL_DONE +} mysql_protocol_state_t; + /* * MySQL session specific data @@ -270,6 +276,7 @@ typedef struct { server_command_t protocol_command; /*< session command list */ server_command_t* protocol_cmd_history; /*< session command history */ mysql_auth_state_t protocol_auth_state; /*< Authentication status */ + mysql_protocol_state_t protocol_state; /*< Protocol struct status */ uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, * created or received */ uint32_t server_capabilities; /*< server capabilities, diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 1a8936c74..07254f37f 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -92,7 +92,8 @@ typedef enum rses_property_type_t { RSES_PROP_TYPE_UNDEFINED=-1, RSES_PROP_TYPE_SESCMD=0, RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD, - RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_SESCMD, + RSES_PROP_TYPE_TMPTABLES, + RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_TMPTABLES, RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1 } rses_property_type_t; @@ -157,7 +158,7 @@ struct rses_property_st { rses_property_type_t rses_prop_type; union rses_prop_data { mysql_sescmd_t sescmd; - void* placeholder; /*< to be removed due new type */ + HASHTABLE* temp_tables; } rses_prop_data; rses_property_t* rses_prop_next; /*< next property of same type */ #if defined(SS_DEBUG) diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile index f815efe49..0f3bc5867 100644 --- a/server/modules/monitor/Makefile +++ b/server/modules/monitor/Makefile @@ -20,6 +20,8 @@ # 28/07/14 Massimiliano Pinto new monitor ndbcluster added include ../../../build_gateway.inc +include ../../../makefile.inc + LOGPATH := $(ROOT_PATH)/log_manager UTILSPATH := $(ROOT_PATH)/utils @@ -61,13 +63,13 @@ libndbclustermon.so: $(NDBCLUSTEROBJ) $(CC) $(CFLAGS) $< -o $@ clean: - rm -f $(OBJ) $(MODULES) + $(DEL) $(OBJ) $(MODULES) tags: ctags $(SRCS) $(HDRS) depend: - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk install: $(MODULES) diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile index 54a8f8c38..0b41d329e 100644 --- a/server/modules/protocol/Makefile +++ b/server/modules/protocol/Makefile @@ -76,7 +76,7 @@ libmaxscaled.so: $(MAXSCALEDOBJ) $(CC) $(CFLAGS) $< -o $@ clean: - rm -f $(OBJ) $(MODULES) + $(DEL) $(OBJ) $(MODULES) tags: ctags $(SRCS) $(HDRS) @@ -85,7 +85,7 @@ install: $(MODULES) install -D $(MODULES) $(DEST)/modules depend: - rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk include depend.mk diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index bdb06e560..7db1366ad 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -33,8 +33,6 @@ * Date Who Description * 08/07/2013 Massimiliano Pinto Initial version * 09/07/2013 Massimiliano Pinto Added /show?dcb|session for all dcbs|sessions - * 11/07/2014 Mark Riddoch Recoded as more generic protocol module - * removing hardcoded example * * @endverbatim */ @@ -42,10 +40,6 @@ #include #include #include -#include -#include - -extern int lm_enabled_logfiles_bitmask; MODULE_INFO info = { MODULE_API_PROTOCOL, @@ -54,6 +48,7 @@ MODULE_INFO info = { "An experimental HTTPD implementation for use in admnistration" }; +#define ISspace(x) isspace((int)(x)) #define HTTP_SERVER_STRING "Gateway(c) v.1.0.0" static char *version_str = "V1.0.1"; @@ -65,8 +60,8 @@ static int httpd_hangup(DCB *dcb); static int httpd_accept(DCB *dcb); static int httpd_close(DCB *dcb); static int httpd_listen(DCB *dcb, char *config); -static char *httpd_nextline(GWBUF *buf, char *ptr); -static void httpd_process_header(GWBUF *buf, char *sol, HTTPD_session *client_data); +static int httpd_get_line(int sock, char *buf, int size); +static void httpd_send_headers(DCB *dcb, int final); /** * The "module object" for the httpd protocol module. @@ -126,99 +121,132 @@ GetModuleObject() * @return */ static int -httpd_read_event(DCB *dcb) +httpd_read_event(DCB* dcb) { -SESSION *session = dcb->session; -GWBUF *buf = NULL; -char *ptr, *sol; -HTTPD_session *client_data = NULL; -int n; +//SESSION *session = dcb->session; +//ROUTER_OBJECT *router = session->service->router; +//ROUTER *router_instance = session->service->router_instance; +//void *rsession = session->router_session; - // Read all the available data - if ((n = dcb_read(dcb, &buf)) != -1) - { - client_data = dcb->data; +int numchars = 1; +char buf[HTTPD_REQUESTLINE_MAXLEN-1] = ""; +char *query_string = NULL; +char method[HTTPD_METHOD_MAXLEN-1] = ""; +char url[HTTPD_SMALL_BUFFER] = ""; +int cgi = 0; +size_t i, j; +int headers_read = 0; +HTTPD_session *client_data = NULL; - if (client_data->saved) - { - buf = gwbuf_append(client_data->saved, buf); - client_data->saved = NULL; - } - buf = gwbuf_make_contiguous(buf); - - ptr = GWBUF_DATA(buf); - - if (strncasecmp(ptr, "POST", 4)) - { - client_data->method = METHOD_POST; - gwbuf_add_property(buf, "Method", "POST"); - ptr = ptr + 4; - } - else if (strncasecmp(ptr, "PUT", 3)) - { - client_data->method = METHOD_PUT; - gwbuf_add_property(buf, "Method", "PUT"); - ptr = ptr + 3; - } - else if (strncasecmp(ptr, "GET", 3)) - { - client_data->method = METHOD_GET; - gwbuf_add_property(buf, "Method", "GET"); - ptr = ptr + 3; - } - else if (strncasecmp(ptr, "HEAD", 4)) - { - client_data->method = METHOD_HEAD; - gwbuf_add_property(buf, "Method", "HEAD"); - ptr = ptr + 4; - } - while (ptr < (char *)(buf->end) && isspace(*ptr)) - ptr++; - sol = ptr; - while (ptr < (char *)(buf->end) && isspace(*ptr) == 0) - ptr++; - client_data->url = strndup(sol, ptr - sol); - gwbuf_add_property(buf, "URL", client_data->url); - while ((sol = httpd_nextline(buf, ptr)) != NULL && - *sol != '\n' && *sol != '\r') - { - httpd_process_header(buf, sol, client_data); - ptr = sol; - } - - /* - * We have read all the headers, or run out of data to - * examine. - */ - if (sol == NULL) - { - client_data->saved = buf; - return 0; - } - else - { - if (((char *)(buf->end) - sol) - < client_data->request_len) - { - client_data->saved = buf; - } - else - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "HTTPD: request %s.\n", client_data->url))); - SESSION_ROUTE_QUERY(session, buf); - if (client_data->url) - { - free(client_data->url); - client_data->url = NULL; - } - } - } + client_data = dcb->data; + /** + * get the request line + * METHOD URL HTTP_VER\r\n + */ + numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); + i = 0; j = 0; + while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { + method[i] = buf[j]; + i++; j++; } + method[i] = '\0'; + + strcpy(client_data->method, method); + + /* check allowed http methods */ + if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { + //httpd_unimplemented(dcb->fd); + return 0; + } + + if (strcasecmp(method, "POST") == 0) + cgi = 1; + + i = 0; + + while (ISspace(buf[j]) && (j < sizeof(buf))) { + j++; + } + + while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { + url[i] = buf[j]; + i++; j++; + } + + url[i] = '\0'; + + /** + * Get the query string if availble + */ + + if (strcasecmp(method, "GET") == 0) { + query_string = url; + while ((*query_string != '?') && (*query_string != '\0')) + query_string++; + if (*query_string == '?') { + cgi = 1; + *query_string = '\0'; + query_string++; + } + } + + /** + * Get the request headers + */ + + while ((numchars > 0) && strcmp("\n", buf)) { + char *value = NULL; + char *end = NULL; + numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); + if ( (value = strchr(buf, ':'))) { + *value = '\0'; + value++; + end = &value[strlen(value) -1]; + *end = '\0'; + + if (strncasecmp(buf, "Hostname", 6) == 0) { + strcpy(client_data->hostname, value); + } + if (strncasecmp(buf, "useragent", 9) == 0) { + strcpy(client_data->useragent, value); + } + } + } + + if (numchars) { + headers_read = 1; + memcpy(&client_data->headers_received, &headers_read, sizeof(int)); + } + + /** + * Now begins the server reply + */ + + /* send all the basic headers and close with \r\n */ + httpd_send_headers(dcb, 1); + + /** + * ToDO: launch proper content handling based on the requested URI, later REST interface + * + */ + + dcb_printf(dcb, "Welcome to HTTPD Gateway (c) %s\n\n", version_str); + + if (strcmp(url, "/show") == 0) { + if (strlen(query_string)) { + if (strcmp(query_string, "dcb") == 0) + dprintAllDCBs(dcb); + if (strcmp(query_string, "session") == 0) + dprintAllSessions(dcb); + } + } + + /* force the client connecton close */ + dcb_close(dcb); + return 0; } @@ -259,18 +287,6 @@ httpd_write(DCB *dcb, GWBUF *queue) static int httpd_error(DCB *dcb) { -HTTPD_session *client_data = NULL; - if (dcb->data) - { - client_data = dcb->data; - if (client_data->url) - { - free(client_data->url); - client_data->url = NULL; - } - free(dcb->data); - dcb->data = NULL; - } dcb_close(dcb); return 0; } @@ -302,7 +318,7 @@ int n_connect = 0; { int so = -1; struct sockaddr_in addr; - socklen_t addrlen = 0; + socklen_t addrlen; DCB *client = NULL; HTTPD_session *client_data = NULL; @@ -317,11 +333,10 @@ int n_connect = 0; memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL)); /* we don't need the session */ - client->session = session_alloc(dcb->session->service, client); + client->session = NULL; /* create the session data for HTTPD */ client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session)); - memset(client_data, 0, sizeof(HTTPD_session)); client->data = client_data; if (poll_add_dcb(client) == -1) @@ -410,84 +425,51 @@ int rc; } /** - * Return the start of the next line int the buffer. - * - * @param buf The GWBUF chain - * @param ptr Start point within the buffer - * - * @return the start of the next line or NULL if there are no more lines + * HTTPD get line from client */ -static char * -httpd_nextline(GWBUF *buf, char *ptr) -{ - while (ptr < (char *)(buf->end) && *ptr != '\n' && *ptr != '\r') - ptr++; - if (ptr >= (char *)(buf->end)) - return NULL; +static int httpd_get_line(int sock, char *buf, int size) { + int i = 0; + char c = '\0'; + int n; - /* Skip prcisely one CR/LF */ - if (*ptr == '\r') - ptr++; - if (*ptr == '\n') - ptr++; - return ptr; -} - -/** - * The headers to extract from the HTTP request and add as properties to the - * GWBUF structure. - */ -static char *headers[] = { - "Content-Type", - "User-Agent", - "From", - "Date", - NULL -}; - -/** - * Process a single header line - * - * @param buf The GWBUF that contains the request - * @param sol The current start of line - * @param client_data The client data structure for this request - */ -static void -httpd_process_header(GWBUF *buf, char *sol, HTTPD_session *client_data) -{ -char *ptr = sol; -char cbuf[300]; -int len, i; - - /* Find the end of the line */ - while (ptr < (char *)(buf->end) && *ptr != '\n' && *ptr != '\r') - ptr++; - - if (strncmp(sol, "Content-Length:", strlen("Content-Length:")) == 0) - { - char *p1 = sol + strlen("Content-Length:"); - while (isspace(*p1)) - p1++; - len = ptr - p1; - strncpy(cbuf, p1, len); - cbuf[len] = 0; - client_data->request_len = atoi(cbuf); - gwbuf_add_property(buf, "Content-Length", cbuf); - } - else - { - for (i = 0; headers[i]; i++) - { - if (strncmp(sol, headers[i], strlen(headers[i])) == 0) - { - char *p1 = sol + strlen(headers[i]) + 1; - while (isspace(*p1)) - p1++; - len = ptr - p1; - strncpy(cbuf, p1, len); - cbuf[len] = 0; - gwbuf_add_property(buf, headers[i], cbuf); + while ((i < size - 1) && (c != '\n')) { + n = recv(sock, &c, 1, 0); + /* DEBUG printf("%02X\n", c); */ + if (n > 0) { + if (c == '\r') { + n = recv(sock, &c, 1, MSG_PEEK); + /* DEBUG printf("%02X\n", c); */ + if ((n > 0) && (c == '\n')) + recv(sock, &c, 1, 0); + else + c = '\n'; } - } + buf[i] = c; + i++; + } else + c = '\n'; + } + + buf[i] = '\0'; + + return i; +} + +/** + * HTTPD send basic headers with 200 OK + */ +static void httpd_send_headers(DCB *dcb, int final) +{ + char date[64] = ""; + const char *fmt = "%a, %d %b %Y %H:%M:%S GMT"; + time_t httpd_current_time = time(NULL); + + strftime(date, sizeof(date), fmt, localtime(&httpd_current_time)); + + dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: text/plain\r\n", date, HTTP_SERVER_STRING); + + /* close the headers */ + if (final) { + dcb_printf(dcb, "\r\n"); } } diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index fee8d984b..d79d7a044 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -761,12 +761,13 @@ return_rc: */ static int gw_error_backend_event(DCB *dcb) { - SESSION* session; - void* rsession; - ROUTER_OBJECT* router; - ROUTER* router_instance; - GWBUF* errbuf; - bool succp; + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + GWBUF* errbuf; + bool succp; + session_state_t ses_state; CHK_DCB(dcb); session = dcb->session; @@ -775,11 +776,6 @@ static int gw_error_backend_event(DCB *dcb) router = session->service->router; router_instance = session->service->router_instance; -#if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend error event handling."))); -#endif /** * Avoid running redundant error handling procedure. * dcb_close is already called for the DCB. Thus, either connection is @@ -795,6 +791,34 @@ static int gw_error_backend_event(DCB *dcb) 0, "Lost connection to backend server."); + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + + /** + * Session might be initialized when DCB already is in the poll set. + * Thus hangup can occur in the middle of session initialization. + * Only complete and successfully initialized sessions allow for + * calling error handler. + */ + while (ses_state == SESSION_STATE_READY) + { + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + } + + if (ses_state != SESSION_STATE_ROUTER_READY) + { + gwbuf_free(errbuf); + goto retblock; + } + +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend error event handling."))); +#endif router->handleError(router_instance, rsession, errbuf, @@ -810,6 +834,7 @@ static int gw_error_backend_event(DCB *dcb) } dcb_close(dcb); +retblock: return 1; } @@ -923,12 +948,13 @@ return_fd: static int gw_backend_hangup(DCB *dcb) { - SESSION* session; - void* rsession; - ROUTER_OBJECT* router; - ROUTER* router_instance; - bool succp; - GWBUF* errbuf; + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + bool succp; + GWBUF* errbuf; + session_state_t ses_state; CHK_DCB(dcb); session = dcb->session; @@ -936,20 +962,41 @@ gw_backend_hangup(DCB *dcb) rsession = session->router_session; router = session->service->router; - router_instance = session->service->router_instance; - + router_instance = session->service->router_instance; + + errbuf = mysql_create_custom_error( + 1, + 0, + "Lost connection to backend server."); + + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + + /** + * Session might be initialized when DCB already is in the poll set. + * Thus hangup can occur in the middle of session initialization. + * Only complete and successfully initialized sessions allow for + * calling error handler. + */ + while (ses_state == SESSION_STATE_READY) + { + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); + } + + if (ses_state != SESSION_STATE_ROUTER_READY) + { + gwbuf_free(errbuf); + goto retblock; + } #if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Backend hangup error handling."))); #endif - - errbuf = mysql_create_custom_error( - 1, - 0, - "Lost connection to backend server."); - router->handleError(router_instance, rsession, errbuf, @@ -958,7 +1005,8 @@ gw_backend_hangup(DCB *dcb) &succp); /** There are not required backends available, close session. */ - if (!succp) { + if (!succp) + { #if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -971,7 +1019,8 @@ gw_backend_hangup(DCB *dcb) } dcb_close(dcb); - return 1; +retblock: + return 1; } /** diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index b9d6e32e9..8454516e2 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -65,7 +65,7 @@ static int gw_client_hangup_event(DCB *dcb); int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); int MySQLSendHandshake(DCB* dcb); static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); -static int route_by_statement(SESSION *, GWBUF *); +static int route_by_statement(SESSION *, GWBUF **); /* * The "module object" for the mysqld client protocol module. @@ -534,7 +534,7 @@ int gw_read_client_event( if (nbytes_read == 0) { goto return_rc; - } + } /** * if read queue existed appent read to it. * if length of read buffer is less than 3 or less than mysql packet @@ -783,7 +783,7 @@ int gw_read_client_event( * Feed each statement completely and separately * to router. */ - rc = route_by_statement(session, read_buffer); + rc = route_by_statement(session, &read_buffer); if (read_buffer != NULL) { @@ -1300,12 +1300,19 @@ static int gw_error_client_event( STRDCBSTATE(dcb->state), (session != NULL ? session : NULL)))); + if (session != NULL && session->state == SESSION_STATE_STOPPING) + { + goto retblock; + } + #if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Client error event handling."))); #endif dcb_close(dcb); + +retblock: return 1; } @@ -1380,12 +1387,19 @@ gw_client_hangup_event(DCB *dcb) { CHK_SESSION(session); } + + if (session != NULL && session->state == SESSION_STATE_STOPPING) + { + goto retblock; + } #if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Client hangup error handling."))); + "Client hangup error handling."))); #endif dcb_close(dcb); + +retblock: return 1; } @@ -1399,14 +1413,16 @@ gw_client_hangup_event(DCB *dcb) * Return 1 in success. If the last packet is incomplete return success but * leave incomplete packet to readbuf. */ -static int route_by_statement(SESSION *session, GWBUF *readbuf) +static int route_by_statement( + SESSION* session, + GWBUF** p_readbuf) { int rc = -1; GWBUF* packetbuf; #if defined(SS_DEBUG) GWBUF* tmpbuf; - tmpbuf = readbuf; + tmpbuf = *p_readbuf; while (tmpbuf != NULL) { ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf)); @@ -1415,15 +1431,14 @@ static int route_by_statement(SESSION *session, GWBUF *readbuf) #endif do { - ss_dassert(GWBUF_IS_TYPE_MYSQL(readbuf)); + ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf))); - packetbuf = gw_MySQL_get_next_packet(&readbuf); - - ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf)); + packetbuf = gw_MySQL_get_next_packet(p_readbuf); if (packetbuf != NULL) { CHK_GWBUF(packetbuf); + ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf)); /** * This means that buffer includes exactly one MySQL * statement. @@ -1446,7 +1461,7 @@ static int route_by_statement(SESSION *session, GWBUF *readbuf) goto return_rc; } } - while (readbuf != NULL); + while (*p_readbuf != NULL); return_rc: return rc; diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 70050a13c..f9c0ebdea 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -79,6 +79,7 @@ MySQLProtocol* mysql_protocol_init( strerror(eno)))); goto return_p; } + p->protocol_state = MYSQL_PROTOCOL_ALLOC; p->protocol_auth_state = MYSQL_ALLOC; p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; p->protocol_command.scom_nresponse_packets = 0; @@ -90,6 +91,7 @@ MySQLProtocol* mysql_protocol_init( /*< Assign fd with protocol */ p->fd = fd; p->owner_dcb = dcb; + p->protocol_state = MYSQL_PROTOCOL_ACTIVE; CHK_PROTOCOL(p); return_p: return p; @@ -107,15 +109,30 @@ return_p: void mysql_protocol_done ( DCB* dcb) { - server_command_t* scmd = ((MySQLProtocol *)dcb->protocol)->protocol_cmd_history; + MySQLProtocol* p; + server_command_t* scmd; server_command_t* scmd2; + p = (MySQLProtocol *)dcb->protocol; + + spinlock_acquire(&p->protocol_lock); + + if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE) + { + goto retblock; + } + scmd = p->protocol_cmd_history; + while (scmd != NULL) { scmd2 = scmd->scom_next; free(scmd); scmd = scmd2; } + p->protocol_state = MYSQL_PROTOCOL_DONE; + +retblock: + spinlock_release(&p->protocol_lock); } @@ -886,7 +903,7 @@ int mysql_send_com_quit( if (buf == NULL) { return 0; - } + } nbytes = dcb->func.write(dcb, buf); return nbytes; @@ -1497,6 +1514,9 @@ GWBUF* gw_MySQL_get_next_packet( size_t packetlen; size_t totalbuflen; uint8_t* data; + size_t nbytes_copied = 0; + uint8_t* target; + readbuf = *p_readbuf; if (readbuf == NULL) @@ -1523,42 +1543,27 @@ GWBUF* gw_MySQL_get_next_packet( packetbuf = NULL; goto return_packetbuf; } - /** there is one complete packet in the buffer */ - if (packetlen == buflen) - { - packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); - *p_readbuf = gwbuf_consume(readbuf, packetlen); - goto return_packetbuf; - } + + packetbuf = gwbuf_alloc(packetlen); + target = GWBUF_DATA(packetbuf); + packetbuf->gwbuf_type = readbuf->gwbuf_type; /*< Copy the type too */ /** - * Packet spans multiple buffers. - * Allocate buffer for complete packet - * copy packet parts into it and consume copied bytes - */ - else if (packetlen > buflen) + * Copy first MySQL packet to packetbuf and leave posible other + * packets to read buffer. + */ + while (nbytes_copied < packetlen && totalbuflen > 0) { - size_t nbytes_copied = 0; - uint8_t* target; + uint8_t* src = GWBUF_DATA((*p_readbuf)); + size_t bytestocopy; - packetbuf = gwbuf_alloc(packetlen); - target = GWBUF_DATA(packetbuf); - - while (nbytes_copied < packetlen) - { - uint8_t* src = GWBUF_DATA(readbuf); - size_t buflen = GWBUF_LENGTH(readbuf); - - memcpy(target+nbytes_copied, src, buflen); - *p_readbuf = gwbuf_consume(readbuf, buflen); - nbytes_copied += buflen; - } - ss_dassert(nbytes_copied == packetlen); - } - else - { - packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); - *p_readbuf = gwbuf_consume(readbuf, packetlen); + bytestocopy = MIN(buflen,packetlen-nbytes_copied); + + memcpy(target+nbytes_copied, src, bytestocopy); + *p_readbuf = gwbuf_consume((*p_readbuf), bytestocopy); + totalbuflen = gwbuf_length((*p_readbuf)); + nbytes_copied += bytestocopy; } + ss_dassert(buflen == 0 || nbytes_copied == packetlen); return_packetbuf: return packetbuf; @@ -1625,11 +1630,16 @@ void protocol_archive_srv_command( MySQLProtocol* p) { server_command_t* s1; - server_command_t** s2; + server_command_t* h1; int len = 0; spinlock_acquire(&p->protocol_lock); + if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE) + { + goto retblock; + } + s1 = &p->protocol_command; LOGIF(LT, (skygw_log_write( @@ -1639,18 +1649,19 @@ void protocol_archive_srv_command( p->owner_dcb->fd))); /** Copy to history list */ - s2 = &p->protocol_cmd_history; - - if (*s2 != NULL) - { - while ((*s2)->scom_next != NULL) - { - *s2 = (*s2)->scom_next; - len += 1; - } + if ((h1 = p->protocol_cmd_history) == NULL) + { + p->protocol_cmd_history = server_command_copy(s1); } - *s2 = server_command_copy(s1); - + else + { + while (h1->scom_next != NULL) + { + h1 = h1->scom_next; + } + h1->scom_next = server_command_copy(s1); + } + /** Keep history limits, remove oldest */ if (len > MAX_CMD_HISTORY) { @@ -1669,6 +1680,8 @@ void protocol_archive_srv_command( p->protocol_command = *(s1->scom_next); free(s1->scom_next); } + +retblock: spinlock_release(&p->protocol_lock); } @@ -1685,6 +1698,10 @@ void protocol_add_srv_command( spinlock_acquire(&p->protocol_lock); + if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE) + { + goto retblock; + } /** this is the only server command in protocol */ if (p->protocol_command.scom_cmd == MYSQL_COM_UNDEFINED) { @@ -1717,6 +1734,7 @@ void protocol_add_srv_command( c = c->scom_next; } #endif +retblock: spinlock_release(&p->protocol_lock); } diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index dbb050375..f3d772a36 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -44,13 +44,10 @@ DEBUGCLISRCS=debugcli.c debugcmd.c DEBUGCLIOBJ=$(DEBUGCLISRCS:.c=.o) CLISRCS=cli.c debugcmd.c CLIOBJ=$(CLISRCS:.c=.o) -WEBSRCS=webserver.o -WEBOBJ=$(WEBSRCS:.c=.o) SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so \ - libwebserver.so +MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so all: $(MODULES) @@ -67,9 +64,6 @@ libdebugcli.so: $(DEBUGCLIOBJ) libcli.so: $(CLIOBJ) $(CC) $(LDFLAGS) $(CLIOBJ) $(LIBS) -o $@ -libwebserver.so: $(WEBOBJ) - $(CC) $(LDFLAGS) $(WEBOBJ) $(LIBS) -o $@ - libreadwritesplit.so: # (cd readwritesplit; touch depend.mk ; make; cp $@ ..) @@ -77,7 +71,7 @@ libreadwritesplit.so: $(CC) $(CFLAGS) $< -o $@ clean: - rm -f $(OBJ) $(MODULES) + $(DEL) $(OBJ) $(MODULES) (cd readwritesplit; touch depend.mk; make clean) tags: @@ -85,7 +79,7 @@ tags: (cd readwritesplit; make tags) depend: - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk (cd readwritesplit; touch depend.mk ; make depend) diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile index c60f2ffd1..80f2ec572 100644 --- a/server/modules/routing/readwritesplit/Makefile +++ b/server/modules/routing/readwritesplit/Makefile @@ -50,13 +50,13 @@ libreadwritesplit.so: $(OBJ) $(CC) $(CFLAGS) $< -o $@ clean: - rm -f $(OBJ) $(MODULES) + $(DEL) $(OBJ) $(MODULES) tags: ctags $(SRCS) $(HDRS) depend: - @rm -f depend.mk + @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk install: $(MODULES) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index ffd6bf62c..00bcf0b59 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -31,6 +31,7 @@ #include #include #include +#include #include MODULE_INFO info = { @@ -280,6 +281,47 @@ static bool have_enough_servers( static SPINLOCK instlock; static ROUTER_INSTANCE* instances; +static int hashkeyfun(void* key); +static int hashcmpfun (void *, void *); + +static int hashkeyfun( + void* key) +{ + if(key == NULL){ + return 0; + } + unsigned int hash = 0,c = 0; + char* ptr = (char*)key; + while((c = *ptr++)){ + hash = c + (hash << 6) + (hash << 16) - hash; + } + return *(int *)key; +} + +static int hashcmpfun( + void* v1, + void* v2) +{ + char* i1 = (char*) v1; + char* i2 = (char*) v2; + + return strcmp(i1,i2); +} + +static void* hstrdup(void* fval) +{ + char* str = (char*)fval; + return strdup(str); +} + + +static void* hfree(void* fval) +{ + free (fval); + return NULL; +} + + /** * Implementation of the mandatory version entry point * @@ -711,7 +753,7 @@ static void* newSession( backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; - } + } max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); max_slave_rlag = rses_get_max_replication_lag(client_rses); @@ -956,76 +998,74 @@ static bool get_dcb( /** get root master from available servers */ master_host = get_root_master(backend_ref, rses->rses_nbackends); + if (name != NULL) /*< Choose backend by name from a hint */ + { + ss_dassert(btype != BE_MASTER); /*< Master dominates and no name should be passed with it */ + + for (i=0; irses_nbackends; i++) + { + BACKEND* b = backend_ref[i].bref_backend; + + /** + * To become chosen: + * backend must be in use, name must match, + * root master node must be found, + * backend's role must be either slave, relay + * server, or master. + */ + if (BREF_IS_IN_USE((&backend_ref[i])) && + (strncasecmp( + name, + b->backend_server->unique_name, + PATH_MAX) == 0) && + master_host != NULL && + (SERVER_IS_SLAVE(b->backend_server) || + SERVER_IS_RELAY_SERVER(b->backend_server) || + SERVER_IS_MASTER(b->backend_server))) + { + *p_dcb = backend_ref[i].bref_dcb; + succp = true; + ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); + break; + } + } + if (succp) + { + goto return_succp; + } + } + if (btype == BE_SLAVE) { - if (name != NULL) /*< Choose backend by name (hint) */ - { - for (i=0; irses_nbackends; i++) - { - BACKEND* b = backend_ref[i].bref_backend; - - /** - * To become chosen: - * backend must be in use, name must match, - * root master node must be found, - * backend's role must be either slave, relay - * server, or master. - */ - if (BREF_IS_IN_USE((&backend_ref[i])) && - (strncasecmp( - name, - b->backend_server->unique_name, - MIN(strlen(b->backend_server->unique_name), PATH_MAX)) == 0) && - master_host != NULL && -#if 0 - (max_rlag == MAX_RLAG_UNDEFINED || - (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag)) && -#endif - (SERVER_IS_SLAVE(b->backend_server) || - SERVER_IS_RELAY_SERVER(b->backend_server) || - SERVER_IS_MASTER(b->backend_server))) - { - *p_dcb = backend_ref[i].bref_dcb; - succp = true; - ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); - break; - } - } - } - - if (!succp) /*< No hints or finding named backend failed */ - { - for (i=0; irses_nbackends; i++) - { - BACKEND* b = backend_ref[i].bref_backend; - /** - * To become chosen: - * backend must be in use, - * root master node must be found, - * backend is not allowed to be the master, - * backend's role can be either slave or relay - * server and it must have least connections - * at the moment. - */ - if (BREF_IS_IN_USE((&backend_ref[i])) && - master_host != NULL && - b->backend_server != master_host->backend_server && - (max_rlag == MAX_RLAG_UNDEFINED || - (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag)) && - (SERVER_IS_SLAVE(b->backend_server) || - SERVER_IS_RELAY_SERVER(b->backend_server)) && - (smallest_nconn == -1 || - b->backend_conn_count < smallest_nconn)) - { - *p_dcb = backend_ref[i].bref_dcb; - smallest_nconn = b->backend_conn_count; - succp = true; - ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); - } - } - } + for (i=0; irses_nbackends; i++) + { + BACKEND* b = backend_ref[i].bref_backend; + /** + * To become chosen: + * backend must be in use, + * root master node must be found, + * backend is not allowed to be the master, + * backend's role can be either slave or relay + * server and it must have least connections + * at the moment. + */ + if (BREF_IS_IN_USE((&backend_ref[i])) && + master_host != NULL && + b->backend_server != master_host->backend_server && + (max_rlag == MAX_RLAG_UNDEFINED || + (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->backend_server->rlag <= max_rlag)) && + (SERVER_IS_SLAVE(b->backend_server) || + SERVER_IS_RELAY_SERVER(b->backend_server)) && + (smallest_nconn == -1 || + b->backend_conn_count < smallest_nconn)) + { + *p_dcb = backend_ref[i].bref_dcb; + smallest_nconn = b->backend_conn_count; + succp = true; + ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); + } + } if (!succp) /*< No valid slave was found, search master next */ { @@ -1065,7 +1105,7 @@ return_succp: * Examine the query type, transaction state and routing hints. Find out the * target for query routing. * - * @param qtype Type of query + * @param qtype Type of query * @param trx_active Is transacation active or not * @param hint Pointer to list of hints attached to the query buffer * @@ -1086,10 +1126,22 @@ static route_target_t get_route_target ( /** hints don't affect on routing */ target = TARGET_ALL; } - else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && !trx_active) + /** + * Read-only statements to slave or to master can be re-routed after + * the hints + */ + else if ((QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_READ)) && + !trx_active) { - target = TARGET_SLAVE; - + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)) + { + target = TARGET_SLAVE; + } + else + { + target = TARGET_MASTER; + } /** process routing hints */ while (hint != NULL) { @@ -1103,8 +1155,15 @@ static route_target_t get_route_target ( } else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) { - target |= TARGET_NAMED_SERVER; /*< add */ - } + /** + * Searching for a named server. If it can't be + * found, the oroginal target is chosen. + */ + target |= TARGET_NAMED_SERVER; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: route to named server : "))); + } else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER) { /** not implemented */ @@ -1140,6 +1199,7 @@ static route_target_t get_route_target ( } else if (hint->type == HINT_ROUTE_TO_SLAVE) { + target = TARGET_SLAVE; LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "Hint: route to slave."))); @@ -1191,15 +1251,27 @@ static int routeQuery( char* startpos; mysql_server_cmd_t packet_type; uint8_t* packet; - int ret = 0; + int ret = 0; + int tsize = 0; + int klen = 0; + int i = 0; DCB* master_dcb = NULL; DCB* target_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + rses_property_t* rses_prop_tmp; bool rses_is_closed = false; + bool target_tmp_table = false; + char* dbname; + char* hkey; + char** tbl; + HASHTABLE* h; + MYSQL_session* data; size_t len; - MYSQL* mysql = NULL; route_target_t route_target; + bool succp = false; + int rlag_max = MAX_RLAG_UNDEFINED; + backend_type_t btype; /*< target backend type */ CHK_CLIENT_RSES(router_cli_ses); @@ -1213,6 +1285,7 @@ static int routeQuery( packet = GWBUF_DATA(querybuf); packet_type = packet[4]; + rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; if (rses_is_closed) { @@ -1222,6 +1295,8 @@ static int routeQuery( */ if (packet_type != MYSQL_COM_QUIT) { + char* query_str = modutil_get_query(querybuf); + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -1229,18 +1304,21 @@ static int routeQuery( "backend server. %s.", STRPACKETTYPE(packet_type), STRQTYPE(qtype), - (querystr == NULL ? "(empty)" : querystr), + (query_str == NULL ? "(empty)" : query_str), (rses_is_closed ? "Router was closed" : "Router has no backend servers where to " "route to")))); + free(querybuf); } - goto return_ret; + goto retblock; } inst->stats.n_queries++; - startpos = (char *)&packet[5]; master_dcb = router_cli_ses->rses_master_ref->bref_dcb; CHK_DCB(master_dcb); + + data = (MYSQL_session*)master_dcb->session->data; + dbname = data->db; switch(packet_type) { case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ @@ -1261,44 +1339,16 @@ static int routeQuery( break; case MYSQL_COM_QUERY: - plainsqlbuf = gwbuf_clone_transform(querybuf, - GWBUF_TYPE_PLAINSQL); - len = GWBUF_LENGTH(plainsqlbuf); - /** unnecessary if buffer includes additional terminating null */ - querystr = (char *)malloc(len+1); - memcpy(querystr, startpos, len); - memset(&querystr[len], 0, 1); - /** - * Use mysql handle to query information from parse tree. - * call skygw_query_classifier_free before exit! - */ - qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); + qtype = query_classifier_get_type(querybuf); break; case MYSQL_COM_STMT_PREPARE: - plainsqlbuf = gwbuf_clone_transform(querybuf, - GWBUF_TYPE_PLAINSQL); - len = GWBUF_LENGTH(plainsqlbuf); - /** unnecessary if buffer includes additional terminating null */ - querystr = (char *)malloc(len+1); - memcpy(querystr, startpos, len); - memset(&querystr[len], 0, 1); - qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); + qtype = query_classifier_get_type(querybuf); qtype |= QUERY_TYPE_PREPARE_STMT; break; case MYSQL_COM_STMT_EXECUTE: /** Parsing is not needed for this type of packet */ -#if defined(NOT_USED) - plainsqlbuf = gwbuf_clone_transform(querybuf, - GWBUF_TYPE_PLAINSQL); - len = GWBUF_LENGTH(plainsqlbuf); - /** unnecessary if buffer includes additional terminating null */ - querystr = (char *)malloc(len+1); - memcpy(querystr, startpos, len); - memset(&querystr[len], 0, 1); - qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); -#endif qtype = QUERY_TYPE_EXEC_STMT; break; @@ -1314,6 +1364,48 @@ static int routeQuery( break; } /**< switch by packet type */ + /** + * Check if the query targets a temporary table + */ + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)) + { + tbl = skygw_get_table_names(querybuf,&tsize); + + if (tsize > 0) + { + /** Query targets at least one table */ + for(i = 0; irses_prop_data.temp_tables) + { + + if( (target_tmp_table = + (bool)hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables,(void *)hkey))) + { + /**Query target is a temporary table*/ + qtype = QUERY_TYPE_READ_TMP_TABLE; + LOGIF(LT, + (skygw_log_write(LOGFILE_TRACE, + "Query targets a temporary table: %s",hkey))); + } + } + free(hkey); + } + + for (i = 0; irses_autocommit_enabled = true; router_cli_ses->rses_transaction_active = false; } + + /** + * If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out + * the database and table name, create a hashvalue and + * add it to the router client session's property. If property + * doesn't exist then create it first. If the query is DROP TABLE... + * then see if it targets a temporary table and remove it from the hashtable + * if it does. + */ + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_CREATE_TMP_TABLE)) + { + bool is_temp = true; + char* tblname = NULL; + + tblname = skygw_get_created_table_name(querybuf); + + if (tblname && strlen(tblname) > 0) + { + klen = strlen(dbname) + strlen(tblname) + 2; + hkey = calloc(klen,sizeof(char)); + strcpy(hkey,dbname); + strcat(hkey,"."); + strcat(hkey,tblname); + } + else + { + hkey = NULL; + } + + if(rses_prop_tmp == NULL) + { + if((rses_prop_tmp = + (rses_property_t*)calloc(1,sizeof(rses_property_t)))) + { + #if defined(SS_DEBUG) + rses_prop_tmp->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; + rses_prop_tmp->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; + #endif + rses_prop_tmp->rses_prop_rsession = router_cli_ses; + rses_prop_tmp->rses_prop_refcount = 1; + rses_prop_tmp->rses_prop_next = NULL; + rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES; + router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp; + } + } + + if (rses_prop_tmp->rses_prop_data.temp_tables == NULL) + { + h = hashtable_alloc(7, hashkeyfun, hashcmpfun); + hashtable_memory_fns(h,hstrdup,NULL,hfree,NULL); + if (h != NULL) + { + rses_prop_tmp->rses_prop_data.temp_tables = h; + } + } + + if (hkey && + hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables, + (void *)hkey, + (void *)is_temp) == 0) /*< Conflict in hash table */ + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Temporary table conflict in hashtable: %s", + hkey))); + } +#if defined(SS_DEBUG) + { + bool retkey = + hashtable_fetch( + rses_prop_tmp->rses_prop_data.temp_tables, + hkey); + if (retkey) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Temporary table added: %s", + hkey))); + } + } +#endif + free(hkey); + } + + /** Check if DROP TABLE... targets a temporary table */ + if (is_drop_table_query(querybuf)) + { + tbl = skygw_get_table_names(querybuf,&tsize); + + for(i = 0; irses_prop_data.temp_tables) + { + if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, + (void *)hkey)) + { + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, + "Temporary table dropped: %s",hkey))); + } + } + free(tbl[i]); + free(hkey); + } + free(tbl); + } /** * Find out where to route the query. Result may not be clear; it is * possible to have a hint for routing to a named server which can @@ -1357,161 +1561,193 @@ static int routeQuery( * If query would otherwise be routed to slave then the hint determines * actual target server if it exists. * - * route_target is a bitfield and may include multiple values. + * route_target is a bitfield and may include : + * TARGET_ALL + * - route to all connected backend servers + * TARGET_SLAVE[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] + * - route primarily according to hints, then to slave and if those + * failed, eventually to master + * TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] + * - route primarily according to the hints and if they failed, + * eventually to master */ route_target = get_route_target(qtype, router_cli_ses->rses_transaction_active, querybuf->hint); - - if (TARGET_IS_ALL(route_target)) - { - /** - * It is not sure if the session command in question requires - * response. Statement is examined in route_session_write. - */ - bool succp = route_session_write( - router_cli_ses, - querybuf, - inst, - packet_type, - qtype); - if (succp) - { - ret = 1; - } - goto return_ret; - } - /** - * Handle routing to master and to slave - */ - else + if (TARGET_IS_ALL(route_target)) + { + /** + * It is not sure if the session command in question requires + * response. Statement is examined in route_session_write. + * Router locking is done inside the function. + */ + succp = route_session_write(router_cli_ses, + gwbuf_clone(querybuf), + inst, + packet_type, + qtype); + + if (succp) + { + ret = 1; + } + goto retblock; + } + + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + goto retblock; + } + /** + * There is a hint which either names the target backend or + * hint which sets maximum allowed replication lag for the + * backend. + */ + if (TARGET_IS_NAMED_SERVER(route_target) || + TARGET_IS_RLAG_MAX(route_target)) + { + HINT* hint; + char* named_server = NULL; + + hint = querybuf->hint; + + while (hint != NULL) + { + if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) + { + /** + * Set the name of searched + * backend server. + */ + named_server = hint->data; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: route to server " + "'%s'", + named_server))); + } + else if (hint->type == HINT_PARAMETER && + (strncasecmp((char *)hint->data, + "max_slave_replication_lag", + strlen("max_slave_replication_lag")) == 0)) + { + int val = (int) strtol((char *)hint->value, + (char **)NULL, 10); + + if (val != 0 || errno == 0) + { + /** + * Set max. acceptable + * replication lag + * value for backend srv + */ + rlag_max = val; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: " + "max_slave_replication_lag=%d", + rlag_max))); + } + } + hint = hint->next; + } /*< while */ + + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ + { + rlag_max = rses_get_max_replication_lag(router_cli_ses); + } + btype = BE_UNDEFINED; /*< target may be master or slave */ + /** + * Search backend server by name or replication lag. + * If it fails, then try to find valid slave or master. + */ + succp = get_dcb(&target_dcb, + router_cli_ses, + btype, + named_server, + rlag_max); + } + + if (!succp && TARGET_IS_SLAVE(route_target)) + { + btype = BE_SLAVE; + + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ + { + rlag_max = rses_get_max_replication_lag(router_cli_ses); + } + /** + * Search suitable backend server, get DCB in target_dcb + */ + succp = get_dcb(&target_dcb, + router_cli_ses, + BE_SLAVE, + NULL, + rlag_max); + } + + if (!succp && TARGET_IS_MASTER(route_target)) + { + if (master_dcb == NULL) + { + succp = get_dcb(&master_dcb, + router_cli_ses, + BE_MASTER, + NULL, + MAX_RLAG_UNDEFINED); + } + else + { + succp = true; + } + target_dcb = master_dcb; + } + ss_dassert(succp); + + + if (succp) /*< Have DCB of the target backend */ + { + if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(querybuf))) == 1) + { + backend_ref_t* bref; + + atomic_add(&inst->stats.n_slave, 1); + /** + * Add one query response waiter to backend reference + */ + bref = get_bref_from_dcb(router_cli_ses, target_dcb); + bref_set_state(bref, BREF_QUERY_ACTIVE); + bref_set_state(bref, BREF_WAITING_RESULT); + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing query \"%s\" failed.", + querystr))); + } + } + rses_end_locked_router_action(router_cli_ses); +retblock: +#if defined(SS_DEBUG) { - bool succp = true; - HINT* hint; - char* named_server = NULL; - int rlag_max = MAX_RLAG_UNDEFINED; + char* canonical_query_str; - if (router_cli_ses->rses_transaction_active) /*< all to master */ + canonical_query_str = skygw_get_canonical(querybuf); + + if (canonical_query_str != NULL) { - route_target = TARGET_MASTER; /*< override old value */ - LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, - "Transaction is active, routing to Master."))); + "Canonical version: %s", + canonical_query_str))); + free(canonical_query_str); } - LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s", STRQTYPE(qtype)))); - - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - goto return_ret; - } - - if (TARGET_IS_SLAVE(route_target)) - { - if (TARGET_IS_NAMED_SERVER(route_target) || - TARGET_IS_RLAG_MAX(route_target)) - { - hint = querybuf->hint; - - while (hint != NULL) - { - if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) - { - named_server = hint->data; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Hint: route to server " - "'%s'", - named_server))); - - } - else if (hint->type == HINT_PARAMETER && - (strncasecmp( - (char *)hint->data, - "max_slave_replication_lag", - strlen("max_slave_replication_lag")) == 0)) - { - int val = (int) strtol((char *)hint->value, - (char **)NULL, 10); - - if (val != 0 || errno == 0) - { - rlag_max = val; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Hint: " - "max_slave_replication_lag=%d", - rlag_max))); - } - } - hint = hint->next; - } - } - - if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ - { - rlag_max = rses_get_max_replication_lag(router_cli_ses); - } - - succp = get_dcb(&target_dcb, - router_cli_ses, - BE_SLAVE, - named_server, - rlag_max); - } - else if (TARGET_IS_MASTER(route_target)) - { - if (master_dcb == NULL) - { - succp = get_dcb(&master_dcb, - router_cli_ses, - BE_MASTER, - NULL, - MAX_RLAG_UNDEFINED); - } - target_dcb = master_dcb; - } - - if (succp) /*< Have DCB of the target backend */ - { - if ((ret = target_dcb->func.write(target_dcb, querybuf)) == 1) - { - backend_ref_t* bref; - - atomic_add(&inst->stats.n_slave, 1); - /** - * Add one query response waiter to backend reference - */ - bref = get_bref_from_dcb(router_cli_ses, target_dcb); - bref_set_state(bref, BREF_QUERY_ACTIVE); - bref_set_state(bref, BREF_WAITING_RESULT); - } - else - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Routing query \"%s\" failed.", - querystr))); - } - } - rses_end_locked_router_action(router_cli_ses); - } - -return_ret: - if (plainsqlbuf != NULL) - { - gwbuf_free(plainsqlbuf); - } - if (querystr != NULL) - { - free(querystr); - } - if (mysql != NULL) - { - skygw_query_classifier_free(mysql); } +#endif + gwbuf_free(querybuf); return ret; } @@ -1725,8 +1961,10 @@ static void clientReply ( (uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf)); size_t len = MYSQL_GET_PACKET_LEN(buf); char* cmdstr = (char *)malloc(len+1); + /** data+termination character == len */ + snprintf(cmdstr, len, "%s", &buf[5]); - snprintf(cmdstr, len+1, "%s", &buf[5]); + ss_dassert(len+4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf)); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -2457,6 +2695,11 @@ static void rses_property_done( case RSES_PROP_TYPE_SESCMD: mysql_sescmd_done(&prop->rses_prop_data.sescmd); break; + + case RSES_PROP_TYPE_TMPTABLES: + hashtable_free(prop->rses_prop_data.temp_tables); + break; + default: LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -2856,8 +3099,23 @@ static bool execute_sescmd_in_backend( sescmd_cursor_clone_querybuf(scur)); break; - case MYSQL_COM_QUERY: - case MYSQL_COM_INIT_DB: + case MYSQL_COM_INIT_DB: + { + /** + * Record database name and store to session. + */ + GWBUF* tmpbuf; + MYSQL_session* data; + unsigned int qlen; + + data = dcb->session->data; + tmpbuf = scur->scmd_cur_cmd->my_sescmd_buf; + qlen = MYSQL_GET_PACKET_LEN((unsigned char*)tmpbuf->start); + memset(data->db,0,MYSQL_DATABASE_MAXLEN+1); + strncpy(data->db,tmpbuf->start+5,qlen - 1); + } + /** Fallthrough */ + case MYSQL_COM_QUERY: default: /** * Mark session command buffer, it triggers writing @@ -3142,7 +3400,7 @@ static bool route_session_write( { succp = false; goto return_succp; - } + } /** * Additional reference is created to querybuf to * prevent it from being released before properties @@ -3831,4 +4089,3 @@ static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers) { } return master_host; } - diff --git a/server/modules/routing/readwritesplit/test/rwsplit.sh b/server/modules/routing/readwritesplit/test/rwsplit.sh index 36199e384..f55960009 100755 --- a/server/modules/routing/readwritesplit/test/rwsplit.sh +++ b/server/modules/routing/readwritesplit/test/rwsplit.sh @@ -230,3 +230,65 @@ if [ "$a" != "$TRETVAL" ]; then else echo "$TINPUT PASSED">>$TLOG ; fi + +TINPUT=test_temporary_table.sql +a=`$RUNCMD < ./$TINPUT` +TRETVAL=1 +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi + +echo "-----------------------------------" >> $TLOG +echo "Session variables: Stress Test 1" >> $TLOG +echo "-----------------------------------" >> $TLOG + +RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --disable-reconnect\ -q\ -r +TINPUT=test_sescmd2.sql +for ((i = 0;i<1000;i++)) +do + if [[ $(( i % 50 )) -eq 0 ]] + then + printf "." + fi + a=`$RUNCMD < $TINPUT 2>&1` + if [[ "`echo "$a"|grep -i 'error'`" != "" ]] + then + err=`echo "$a" | grep -i error` + break + fi +done +if [[ "$err" == "" ]] +then + echo "TEST PASSED" >> $TLOG +else + echo "$err" >> $TLOG + echo "Test FAILED at iteration $((i+1))" >> $TLOG +fi +echo "-----------------------------------" >> $TLOG +echo "Session variables: Stress Test 2" >> $TLOG +echo "-----------------------------------" >> $TLOG +echo "" +err="" +TINPUT=test_sescmd3.sql +for ((j = 0;j<1000;j++)) +do + if [[ $(( j % 50 )) -eq 0 ]] + then + printf "." + fi + b=`$RUNCMD < $TINPUT 2>&1` + if [[ "`echo "$b"|grep -i 'null|error'`" != "" ]] + then + err=`echo "$b" | grep -i null|error` + break + fi +done +if [[ "$err" == "" ]] +then + echo "TEST PASSED" >> $TLOG +else + echo "Test FAILED at iteration $((j+1))" >> $TLOG +fi +echo "" >> $TLOG diff --git a/server/modules/routing/readwritesplit/test/test_hints/testrwsplit_hints.log b/server/modules/routing/readwritesplit/test/test_hints/testrwsplit_hints.log deleted file mode 100644 index 021b52b72..000000000 --- a/server/modules/routing/readwritesplit/test/test_hints/testrwsplit_hints.log +++ /dev/null @@ -1,15 +0,0 @@ - -------------------------------- -Tue Aug 5 13:31:41 EEST 2014 -Test Read/Write split router - hint routing -------------------------------- --- maxscale route to master FAILED, return value 3003 when 3000 was expected --- maxscale route to server server1 FAILED, return value 3003 when 3000 was expected --- maxscale route to server server2 FAILED, return value 3003 when 3001 was expected --- maxscale route to server server3 FAILED, return value 3003 when 3002 was expected --- maxscale route to server server4 PASSED --- maxscale max_slave_replication_lag = 100 FAILED, return value 3003 when was expected --- maxscale max_slave_replication_lag= 100 FAILED, return value 3003 when was expected --- maxscale max_slave_replication_lag =100 FAILED, return value 3003 when was expected --- maxscale max_slave_replication_lag=100 FAILED, return value 3003 when was expected - diff --git a/server/modules/routing/readwritesplit/test/test_sescmd2.sql b/server/modules/routing/readwritesplit/test/test_sescmd2.sql new file mode 100644 index 000000000..5cd179dfc --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_sescmd2.sql @@ -0,0 +1,20 @@ +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/server/modules/routing/readwritesplit/test/test_sescmd3.sql b/server/modules/routing/readwritesplit/test/test_sescmd3.sql new file mode 100644 index 000000000..ef5e9ed72 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_sescmd3.sql @@ -0,0 +1,16 @@ +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +select @OLD_SQL_NOTES; +select @OLD_CHARACTER_SET_CLIENT; +select @OLD_CHARACTER_SET_RESULTS; +select @OLD_COLLATION_CONNECTION; +select @OLD_TIME_ZONE; +select @OLD_UNIQUE_CHECKS; +select @OLD_FOREIGN_KEY_CHECKS; +select @OLD_SQL_MODE; diff --git a/server/modules/routing/readwritesplit/test/test_temporary_table.sql b/server/modules/routing/readwritesplit/test/test_temporary_table.sql new file mode 100644 index 000000000..374510389 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_temporary_table.sql @@ -0,0 +1,5 @@ +use test; +drop table if exists t1; +create temporary table t1 (id integer); +insert into t1 values(1); +select id from t1; diff --git a/utils/makefile b/utils/makefile index a8df09a7f..0faa27144 100644 --- a/utils/makefile +++ b/utils/makefile @@ -3,6 +3,7 @@ include ../makefile.inc CC = gcc CPP = g++ +UTILS_PATH := $(ROOT_PATH)/utils makeall: clean all @@ -13,7 +14,7 @@ clean: - $(DEL) *~ all: - $(CPP) -c $(CFLAGS) \ + $(CPP) -c $(CFLAGS) -I$(UTILS_PATH) \ -fPIC skygw_utils.cc -o skygw_utils.o cleantests: diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 5746922f3..352d736f9 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -123,7 +123,8 @@ typedef enum skygw_chk_t { CHK_NUM_SESCMD_CUR, CHK_NUM_BACKEND, CHK_NUM_BACKEND_REF, - CHK_NUM_PREP_STMT + CHK_NUM_PREP_STMT, + CHK_NUM_PINFO } skygw_chk_t; # define STRBOOL(b) ((b) ? "true" : "false") @@ -489,6 +490,13 @@ typedef enum skygw_chk_t { "Prepared statement struct has invalid check fields"); \ } +#define CHK_PARSING_INFO(p) { \ + ss_info_dassert((p)->pi_chk_top == CHK_NUM_PINFO && \ + (p)->pi_chk_tail == CHK_NUM_PINFO, \ + "Parsing info struct has invalid check fields"); \ +} + + #if defined(SS_DEBUG) bool conn_open[10240]; diff --git a/utils/skygw_types.h b/utils/skygw_types.h index d497fbfd7..cccf55f2b 100644 --- a/utils/skygw_types.h +++ b/utils/skygw_types.h @@ -44,4 +44,6 @@ # endif #endif +#define MAX_ERROR_MSG PATH_MAX + #endif /* SKYGW_TYPES_H */ diff --git a/utils/skygw_utils.cc b/utils/skygw_utils.cc index 1ed569962..707957214 100644 --- a/utils/skygw_utils.cc +++ b/utils/skygw_utils.cc @@ -23,9 +23,10 @@ #include #include #include - +#include +#include #include "skygw_debug.h" -#include "skygw_types.h" +#include #include "skygw_utils.h" const char* timestamp_formatstr = "%04d %02d/%02d %02d:%02d:%02d "; @@ -1863,3 +1864,104 @@ void skygw_file_done( free(file); } } + + +/** + * Find the given needle - user-provided literal - and replace it with + * replacement string. Separate user-provided literals from matching table names + * etc. by searching only substrings preceded by non-letter and non-number. + * + * @param haystack Plain text query string, not to be freed + * @param needle Substring to be searched, not to be freed + * @param replacement Replacement text, not to be freed + * + * @return newly allocated string where needle is replaced + */ +char* replace_literal( + char* haystack, + const char* needle, + const char* replacement) +{ + const char* prefix = "[ ='\",\\(]"; /*< ' ','=','(',''',''"',',' are allowed before needle */ + const char* suffix = "([^[:alnum:]]|$)"; /*< alpha-num chars aren't allowed after the needle */ + char* search_re; + char* newstr; + regex_t re; + regmatch_t match; + int rc; + size_t rlen = strlen(replacement); + size_t nlen = strlen(needle); + size_t hlen = strlen(haystack); + + search_re = (char *)malloc(strlen(prefix)+nlen+strlen(suffix)+1); + + if (search_re == NULL) + { + fprintf(stderr, "Regex memory allocation failed : %s\n", + strerror(errno)); + newstr = haystack; + goto retblock; + } + + sprintf(search_re, "%s%s%s", prefix, needle, suffix); + /** Allocate memory for new string +1 for terminating byte */ + newstr = (char *)malloc(hlen-nlen+rlen+1); + + if (newstr == NULL) + { + fprintf(stderr, "Regex memory allocation failed : %s\n", + strerror(errno)); + free(search_re); + free(newstr); + newstr = haystack; + goto retblock; + } + + rc = regcomp(&re, search_re, REG_EXTENDED|REG_ICASE); + ss_dassert(rc == 0); + + if (rc != 0) + { + char error_message[MAX_ERROR_MSG]; + regerror (rc, &re, error_message, MAX_ERROR_MSG); + fprintf(stderr, + "Regex error compiling '%s': %s\n", + search_re, + error_message); + free(search_re); + free(newstr); + newstr = haystack; + goto retblock; + } + rc = regexec(&re, haystack, 1, &match, 0); + + if (rc != 0) + { + free(search_re); + free(newstr); + regfree(&re); + newstr = haystack; + goto retblock; + } + memcpy(newstr, haystack, match.rm_so+1); + memcpy(newstr+match.rm_so+1, replacement, rlen); + /** +1 is terminating byte */ + memcpy(newstr+match.rm_so+1+rlen, haystack+match.rm_so+1+nlen, hlen-(match.rm_so+1)-nlen+1); + + regfree(&re); + free(haystack); + free(search_re); +retblock: + return newstr; +} + + + + + + + + + + + diff --git a/utils/skygw_utils.h b/utils/skygw_utils.h index 992f169d2..a50dd08b6 100644 --- a/utils/skygw_utils.h +++ b/utils/skygw_utils.h @@ -192,4 +192,12 @@ int skygw_rwlock_init(skygw_rwlock_t** rwlock); int atomic_add(int *variable, int value); +EXTERN_C_BLOCK_BEGIN + +char* replace_literal(char* haystack, + const char* needle, + const char* replacement); + +EXTERN_C_BLOCK_END + #endif /* SKYGW_UTILS_H */