This commit is contained in:
Markus Makela
2014-09-04 10:36:59 +03:00
80 changed files with 2981 additions and 1082 deletions

View File

@ -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)

2
README
View File

@ -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

View File

@ -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

View File

@ -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

169
gcov.diff
View File

@ -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)

View File

@ -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);
/**

View File

@ -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./ \

View File

@ -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

View File

@ -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
@ -33,7 +35,6 @@ 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

View File

@ -34,6 +34,7 @@
#include "../utils/skygw_types.h"
#include "../utils/skygw_debug.h"
#include <log_manager.h>
#include <mysql_client_server_protocol.h>
#include <mysql.h>
#include <my_sys.h>
@ -83,122 +84,158 @@ static bool skygw_stmt_causes_implicit_commit(
static int is_autocommit_stmt(
LEX* lex);
static void parsing_info_set_plain_str(void* ptr,
char* str);
/**
* @node (write brief function description here)
* 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.
*
* Parameters:
* @param query_str - <usage>
* <description>
*
* @param client_flag - <usage>
* <description>
*
* @return
*
*
* @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;
bool succp;
ss_info_dassert(query != NULL, ("query_str is NULL"));
ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
query_str = const_cast<char*>(query);
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Query : \"%s\"", query_str)));
/** Create parsing info for the query and store it to buffer */
succp = query_is_parsed(querybuf);
/** Get server handle */
mysql = mysql_init(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))));
mysql_library_end();
goto return_qtype;
}
if (p_mysql != NULL)
if (!succp)
{
*p_mysql = mysql;
succp = parse_query(querybuf);
}
/** 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)
/** Read thd pointer and resolve the query type with it. */
if (succp)
{
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);
parsing_info_t* pi;
if (p_mysql == NULL)
{
skygw_query_classifier_free(mysql);
pi = (parsing_info_t*)gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
if (pi != NULL)
{
mysql = (MYSQL *)pi->pi_handle;
/** Find out the query type */
if (mysql != NULL)
{
qtype = resolve_query_type((THD *)mysql->thd);
}
}
}
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 - <usage>
* <description>
* @param mysql Database handle
*
* @param query_str - <usage>
* <description>
* @param query_str Query in plain txt string
*
* @return
*
*
* @details (write detailed description here)
* @return Thread context pointer
*
*/
static THD* get_or_create_thd_for_parsing(
@ -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 {
type |= QUERY_TYPE_WRITE;
}
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;x<currtblsz;x++){
tmp[x] = tables[x];
}
free(tables);
}
tables = tmp;
currtblsz = currtblsz*2 + 1;
}
}
tables[i++] = strdup(tbl->alias);
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;
}

View File

@ -20,7 +20,8 @@ Copyright SkySQL Ab
/** getpid */
#include <unistd.h>
#include <mysql.h>
#include "../utils/skygw_utils.h"
#include <skygw_utils.h>
#include <buffer.h>
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

View File

@ -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

View File

@ -0,0 +1,101 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <query_classifier.h>
#include <buffer.h>
#include <mysql.h>
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 <input file> <output file>\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<lines;i++){
parse_query(qbuff[i]);
tok = skygw_get_canonical(qbuff[i]);
write(fdout,tok,strlen(tok));
write(fdout,"\n",1);
gwbuf_free(qbuff[i]);
}
close(fdin);
close(fdout);
return 0;
}

View File

@ -0,0 +1,21 @@
#! /bin/sh
if [[ $# -ne 4 ]]
then
echo "Usage: canontest.sh <logfile name> <input file> <output file> <expected output>"
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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View File

@ -41,6 +41,11 @@
#include <atomic.h>
#include <skygw_debug.h>
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;
}

View File

@ -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(

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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;
@ -143,7 +175,11 @@ HASHENTRIES *entry, *ptr;
}
}
free(table->entries);
free(table);
if (!table->ht_isflat)
{
free(table);
}
}
/**

View File

@ -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)

View File

@ -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)

View File

@ -29,6 +29,7 @@
*/
#include <buffer.h>
#include <string.h>
#include <mysql_client_server_protocol.h>
/**
* 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;
}

View File

@ -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);
}

View File

@ -252,8 +252,10 @@ 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)
{
@ -495,6 +497,8 @@ poll_waitevents(void *arg)
return;
}
} /*< while(1) */
/** Release mysql thread context */
mysql_thread_end();
}
/**

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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)

10
server/core/test/runtest.sh Executable file
View File

@ -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

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <adminusers.h>
/**
* 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);
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <filter.h>
/**
* 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);
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <spinlock.h>
#include <thread.h>
/**
* 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);
}

View File

@ -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

View File

@ -29,6 +29,8 @@
*
* @endverbatim
*/
#include <dcb.h>
#define ADMIN_SALT "MS"
extern int admin_verify(char *, char *);

View File

@ -43,11 +43,14 @@
*
* @endverbatim
*/
#include <spinlock.h>
#include <string.h>
#include <skygw_debug.h>
#include <hint.h>
#include <spinlock.h>
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

View File

@ -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 {

View File

@ -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 *);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 *);

View File

@ -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

View File

@ -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)

View File

@ -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 <stdio.h>
#include <fcntl.h>
@ -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
*/

View File

@ -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
*/

View File

@ -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 <stdio.h>
#include <fcntl.h>
@ -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
*/

View File

@ -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 <filter name>","Loads a filter and appeds it to the end of the chain."
@ -407,6 +408,8 @@ void print_help()
,"config <file name>","Loads filter configurations from a file."
,"in <file name>","Source file for the SQL statements."
,"out <file name>","Destination file for the SQL statements. Defaults to stdout if no parameters were passed."
,"threads <number>","Sets the amount of threads to use"
,"sessions <number>","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;

View File

@ -21,13 +21,15 @@
#include <modutil.h>
/**
* 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
*/

View File

@ -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 <stdio.h>
#include <fcntl.h>
@ -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
*/

View File

@ -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;

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 <httpd.h>
#include <gw.h>
#include <modinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
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");
}
}

View File

@ -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;
@ -938,18 +964,39 @@ gw_backend_hangup(DCB *dcb)
router = session->service->router;
router_instance = session->service->router_instance;
errbuf = mysql_create_custom_error(
1,
0,
"Lost connection to backend server.");
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
/**
* Session might be initialized when DCB already is in the poll set.
* Thus hangup can occur in the middle of session initialization.
* Only complete and successfully initialized sessions allow for
* calling error handler.
*/
while (ses_state == SESSION_STATE_READY)
{
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
}
if (ses_state != SESSION_STATE_ROUTER_READY)
{
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;
}
/**

View File

@ -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.
@ -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;

View File

@ -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);
}
@ -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
* Copy first MySQL packet to packetbuf and leave posible other
* packets to read buffer.
*/
else if (packetlen > buflen)
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);
bytestocopy = MIN(buflen,packetlen-nbytes_copied);
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);
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,17 +1649,18 @@ void protocol_archive_srv_command(
p->owner_dcb->fd)));
/** Copy to history list */
s2 = &p->protocol_cmd_history;
if (*s2 != NULL)
if ((h1 = p->protocol_cmd_history) == NULL)
{
while ((*s2)->scom_next != NULL)
{
*s2 = (*s2)->scom_next;
len += 1;
}
p->protocol_cmd_history = server_command_copy(s1);
}
else
{
while (h1->scom_next != NULL)
{
h1 = h1->scom_next;
}
h1->scom_next = server_command_copy(s1);
}
*s2 = 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);
}

View File

@ -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)

View File

@ -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)

View File

@ -31,6 +31,7 @@
#include <dcb.h>
#include <spinlock.h>
#include <modinfo.h>
#include <modutil.h>
#include <mysql_client_server_protocol.h>
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
*
@ -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; i<rses->rses_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; i<rses->rses_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; i<rses->rses_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; i<rses->rses_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,19 +1304,22 @@ 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 */
case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */
@ -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; i<tsize && !target_tmp_table && tbl[i]; i++)
{
klen = strlen(dbname) + strlen(tbl[i]) + 2;
hkey = calloc(klen,sizeof(char));
strcpy(hkey,dbname);
strcat(hkey,".");
strcat(hkey,tbl[0]);
if (rses_prop_tmp &&
rses_prop_tmp->rses_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; i<tsize; i++)
{
free(tbl[i]);
}
free(tbl);
}
}
/**
* If autocommit is disabled or transaction is explicitly started
* transaction becomes active and master gets all statements until
@ -1350,6 +1442,118 @@ static int routeQuery(
router_cli_ses->rses_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; i<tsize; i++)
{
klen = strlen(dbname) + strlen(tbl[i]) + 2;
hkey = calloc(klen,sizeof(char));
strcpy(hkey,dbname);
strcat(hkey,".");
strcat(hkey,tbl[i]);
if (rses_prop_tmp &&
rses_prop_tmp->rses_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))
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)
{
/**
* 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);
char* canonical_query_str;
if (succp)
canonical_query_str = skygw_get_canonical(querybuf);
if (canonical_query_str != NULL)
{
ret = 1;
}
goto return_ret;
}
/**
* Handle routing to master and to slave
*/
else
{
bool succp = true;
HINT* hint;
char* named_server = NULL;
int rlag_max = MAX_RLAG_UNDEFINED;
if (router_cli_ses->rses_transaction_active) /*< all to master */
{
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
@ -3831,4 +4089,3 @@ static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers) {
}
return master_host;
}

View File

@ -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

View File

@ -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

View File

@ -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 */;

View File

@ -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;

View File

@ -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;

View File

@ -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:

View File

@ -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];

View File

@ -44,4 +44,6 @@
# endif
#endif
#define MAX_ERROR_MSG PATH_MAX
#endif /* SKYGW_TYPES_H */

View File

@ -23,9 +23,10 @@
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stddef.h>
#include <regex.h>
#include "skygw_debug.h"
#include "skygw_types.h"
#include <skygw_types.h>
#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;
}

View File

@ -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 */