Merge branch 'develop' into firewall

Conflicts:
	server/modules/filter/test/CMakeLists.txt
This commit is contained in:
Markus Makela 2015-01-13 07:48:43 +02:00
commit 2b4e6bb594
107 changed files with 2718 additions and 800 deletions

View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 2.6)
message(STATUS "CMake version: ${CMAKE_VERSION}")
include(macros.cmake)
enable_testing()
@ -13,10 +14,14 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/")
project(MaxScale)
#Disabled for now pending evaluation
#include(CheckPlatform.cmake)
check_deps()
check_dirs()
find_package(Valgrind)
find_package(MySQLClient)
find_package(MySQLConfig)
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/modules)
@ -25,6 +30,7 @@ configure_file(${CMAKE_SOURCE_DIR}/server/include/version.h.in ${CMAKE_BINARY_DI
configure_file(${CMAKE_SOURCE_DIR}/maxscale.conf.in ${CMAKE_BINARY_DIR}/maxscale.conf.prep @ONLY)
configure_file(${CMAKE_SOURCE_DIR}/etc/init.d/maxscale.in ${CMAKE_BINARY_DIR}/etc/init.d/maxscale.prep @ONLY)
configure_file(${CMAKE_SOURCE_DIR}/etc/ubuntu/init.d/maxscale.in ${CMAKE_BINARY_DIR}/etc/ubuntu/init.d/maxscale.prep @ONLY)
configure_file(${CMAKE_SOURCE_DIR}/server/test/maxscale_test.h.in ${CMAKE_BINARY_DIR}/server/include/maxscale_test.h)
set(CMAKE_C_FLAGS "-Wall -fPIC")
@ -119,44 +125,54 @@ message(STATUS "Installing MaxScale to: ${CMAKE_INSTALL_PREFIX}/")
install(FILES server/MaxScale_template.cnf DESTINATION etc)
install(FILES ${ERRMSG} DESTINATION mysql)
install(FILES ${DOCS} DESTINATION Documentation)
install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION ${CMAKE_INSTALL_PREFIX}/)
install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION ${CMAKE_INSTALL_PREFIX}/)
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_INSTALL_PREFIX}/)
install(FILES ${CMAKE_SOURCE_DIR}/SETUP DESTINATION ${CMAKE_INSTALL_PREFIX}/)
install(DIRECTORY DESTINATION log)
# See if we are on a RPM-capable or DEB-capable system
find_program(RPMBUILD rpmbuild)
find_program(DEBBUILD dpkg-buildpackage)
set(CPACK_GENERATOR "TGZ")
if(NOT ( ${RPMBUILD} STREQUAL "RPMBUILD-NOTFOUND" ) )
message(STATUS "Generating RPM packages")
set(CPACK_GENERATOR "${CPACK_GENERATOR};RPM")
if(${CMAKE_VERSION} VERSION_LESS 2.8.12)
message(WARNING "CMake version is ${CMAKE_VERSION}. Building of packages requires version 2.8.12 or greater.")
else()
# See if we are on a RPM-capable or DEB-capable system
find_program(RPMBUILD rpmbuild)
find_program(DEBBUILD dpkg-buildpackage)
set(CPACK_GENERATOR "TGZ")
if(NOT ( ${RPMBUILD} STREQUAL "RPMBUILD-NOTFOUND" ) )
message(STATUS "Generating RPM packages")
set(CPACK_GENERATOR "${CPACK_GENERATOR};RPM")
endif()
if(NOT ( ${DEBBUILD} STREQUAL "DEBBUILD-NOTFOUND" ) )
set(CPACK_GENERATOR "${CPACK_GENERATOR};DEB")
execute_process(COMMAND dpgk --print-architecture OUTPUT_VARIABLE DEB_ARCHITECTURE)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEB_ARCHITECTURE})
set (CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
message(STATUS "Generating DEB packages for ${DEB_ARCHITECTURE}")
endif()
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale")
set(CPACK_PACKAGE_VERSION_MAJOR "${MAXSCALE_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${MAXSCALE_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${MAXSCALE_VERSION_PATCH}")
set(CPACK_PACKAGE_CONTACT "MariaDB Corporation Ab")
set(CPACK_PACKAGE_FILE_NAME "maxscale-${MAXSCALE_VERSION}")
set(CPACK_PACKAGE_NAME "maxscale")
set(CPACK_PACKAGE_VENDOR "MariaDB Corporation Ab")
set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_SOURCE_DIR}/README)
set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
set(CPACK_RPM_SPEC_INSTALL_POST "/sbin/ldconfig")
set(CPACK_RPM_PACKAGE_NAME "maxscale")
set(CPACK_RPM_PACKAGE_VENDOR "MariaDB Corporation Ab")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/etc /etc/ld.so.conf.d /etc/init.d /etc/rc.d/init.d")
set(CPACK_RPM_SPEC_MORE_DEFINE "%define ignore \#")
set(CPACK_RPM_USER_FILELIST "%ignore /etc/init.d")
set(CPACK_RPM_USER_FILELIST "%ignore /etc/ld.so.conf.d")
set(CPACK_RPM_USER_FILELIST "%ignore /etc")
include(CPack)
endif()
if(NOT ( ${DEBBUILD} STREQUAL "DEBBUILD-NOTFOUND" ) )
set(CPACK_GENERATOR "${CPACK_GENERATOR};DEB")
execute_process(COMMAND dpgk --print-architecture OUTPUT_VARIABLE DEB_ARCHITECTURE)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEB_ARCHITECTURE})
set (CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
message(STATUS "Generating DEB packages for ${DEB_ARCHITECTURE}")
endif()
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale")
set(CPACK_PACKAGE_VERSION_MAJOR "${MAXSCALE_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${MAXSCALE_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${MAXSCALE_VERSION_PATCH}")
set(CPACK_PACKAGE_CONTACT "MariaDB Corporation Ab")
set(CPACK_PACKAGE_FILE_NAME "maxscale-${MAXSCALE_VERSION}")
set(CPACK_PACKAGE_NAME "maxscale")
set(CPACK_PACKAGE_VENDOR "MariaDB Corporation Ab")
set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_SOURCE_DIR}/README)
set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
set(CPACK_RPM_SPEC_INSTALL_POST "/sbin/ldconfig")
set(CPACK_RPM_PACKAGE_NAME "maxscale")
set(CPACK_RPM_PACKAGE_VENDOR "MariaDB Corporation Ab")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/etc /etc/ld.so.conf.d /etc/init.d /etc/rc.d/init.d")
set(CPACK_RPM_SPEC_MORE_DEFINE "%define ignore \#")
set(CPACK_RPM_USER_FILELIST "%ignore /etc/init.d")
set(CPACK_RPM_USER_FILELIST "%ignore /etc/ld.so.conf.d")
set(CPACK_RPM_USER_FILELIST "%ignore /etc")
include(CPack)
add_custom_target(buildtests
COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DBUILD_TYPE=Debug -DINSTALL_DIR=${CMAKE_BINARY_DIR} -DINSTALL_SYSTEM_FILES=N ${CMAKE_SOURCE_DIR}
@ -207,4 +223,4 @@ add_custom_target(testall-valgrind
COMMAND killall maxscale
COMMENT "Running full test suite with Valgrind..." VERBATIM)
endif()
endif()

45
CheckPlatform.cmake Normal file
View File

@ -0,0 +1,45 @@
#Checks for all the C system headers found in all the files
include(CheckFunctionExists)
include(CheckIncludeFiles)
check_include_files(arpa/inet.h HAVE_ARPA_INET)
check_include_files(crypt.h HAVE_CRYPT)
check_include_files(ctype.h HAVE_CTYPE)
check_include_files(dirent.h HAVE_DIRENT)
check_include_files(dlfcn.h HAVE_DLFCN)
check_include_files(errno.h HAVE_ERRNO)
check_include_files(execinfo.h HAVE_EXECINFO)
check_include_files(fcntl.h HAVE_FCNTL)
check_include_files(ftw.h HAVE_FTW)
check_include_files(getopt.h HAVE_GETOPT)
check_include_files(ini.h HAVE_INI)
check_include_files(math.h HAVE_MATH)
check_include_files(memlog.h HAVE_MEMLOG)
check_include_files(netdb.h HAVE_NETDB)
check_include_files(netinet/in.h HAVE_NETINET_IN)
check_include_files(openssl/aes.h HAVE_OPENSSL_AES)
check_include_files(openssl/sha.h HAVE_OPENSSL_SHA)
check_include_files(pthread.h HAVE_PTHREAD)
check_include_files(pwd.h HAVE_PWD)
check_include_files(rdtsc.h HAVE_RDTSC)
check_include_files(regex.h HAVE_REGEX)
check_include_files(signal.h HAVE_SIGNAL)
check_include_files(stdarg.h HAVE_STDARG)
check_include_files(stdbool.h HAVE_STDBOOL)
check_include_files(stdint.h HAVE_STDINT)
check_include_files(stdio.h HAVE_STDIO)
check_include_files(stdlib.h HAVE_STDLIB)
check_include_files(string.h HAVE_STRING)
check_include_files(strings.h HAVE_STRINGS)
check_include_files(sys/epoll.h HAVE_SYS_EPOLL)
check_include_files(sys/ioctl.h HAVE_SYS_IOCTL)
check_include_files(syslog.h HAVE_SYSLOG)
check_include_files(sys/param.h HAVE_SYS_PARAM)
check_include_files(sys/socket.h HAVE_SYS_SOCKET)
check_include_files(sys/stat.h HAVE_SYS_STAT)
check_include_files(sys/time.h HAVE_SYS_TIME)
check_include_files(sys/types.h HAVE_SYS_TYPES)
check_include_files(sys/un.h HAVE_SYS_UN)
check_include_files(time.h HAVE_TIME)
check_include_files(unistd.h HAVE_UNISTD)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
FindMySQLConfig.cmake Normal file
View File

@ -0,0 +1,13 @@
# This CMake file tries to find the the MySQL configuration tool
# The following variables are set:
# MYSQLCONFIG_FOUND - System has MySQL and the tool was found
# MYSQLCONFIG_EXECUTABLE - The MySQL configuration tool executable
find_program(MYSQLCONFIG_EXECUTABLE mysql_config)
if(MYSQLCONFIG_EXECUTABLE MATCHES "MYSQLCONFIG_EXECUTABLE-NOTFOUND")
message(FATAL_ERROR "Cannot find mysql_config.")
set(MYSQLCONFIG_FOUND FALSE CACHE INTERNAL "")
unset(MYSQLCONFIG_EXECUTABLE)
else()
message(STATUS "mysql_config found: ${MYSQLCONFIG_EXECUTABLE}")
set(MYSQLCONFIG_FOUND TRUE CACHE INTERNAL "")
endif()

2
debian/rules vendored
View File

@ -3,7 +3,7 @@
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" clean
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" depend
$(MAKE) ROOT_PATH=$(shell pwd) HOME=""
$(MAKE) DEST="$(shell pwd)/binaries" ROOT_PATH=$(shell pwd) HOME="" ERRMSG="/usr/share/mysql/english" EMBEDDED_LIB="/usr/lib/x86_64-linux-gnu/" install
$(MAKE) DEST="$(shell pwd)/binaries" ROOT_PATH=$(shell pwd) HOME="" ERRMSG="/usr/share/mysql/english" EMBEDDED_LIB="$(mysql_config --variable=pkglibdir)" install
dh $@
override_dh_usrlocal:
override_dh_auto_clean:

View File

@ -677,14 +677,18 @@ static int logmanager_write_log(
logfile_rotate(lf); /*< wakes up file writer */
}
}
else
else
{
/** Length of string that will be written, limited by bufsize */
int safe_str_len;
/** Length of session id */
int sesid_str_len;
/** 2 braces, 2 spaces and terminating char */
/**
* 2 braces, 2 spaces and terminating char
* If session id is stored to tls_log_info structure, allocate
* room for session id too.
*/
if (id == LOGFILE_TRACE && tls_log_info.li_sesid != 0)
{
sesid_str_len = 2+2+get_decimal_len(tls_log_info.li_sesid)+1;
@ -747,7 +751,6 @@ static int logmanager_write_log(
wp += strlen(wp);
}
#endif
/**
* Write timestamp with at most <timestamp_len> characters
* to wp.
@ -1511,7 +1514,7 @@ return_unregister:
logmanager_unregister();
return_err:
return_err:
return err;
}
@ -1848,10 +1851,9 @@ static char* fname_conf_get_suffix(
*
*
* Parameters:
* @param lm - <usage>
* <description>
* @param lm Log manager pointer
*
* @return
* @return succp true if succeed, otherwise false.
*
*
* @details If logfile is supposed to be located to shared memory
@ -1871,6 +1873,7 @@ static bool logfiles_init(
bool store_shmem;
bool write_syslog;
/** Open syslog immediately. Print pid of loggind process. */
if (syslog_id_str != NULL)
{
openlog(syslog_ident_str, LOG_PID | LOG_NDELAY, LOG_USER);
@ -2483,21 +2486,13 @@ static bool file_is_symlink(
* link name. Create block buffer for logfile.
*
* Parameters:
* @param logfile - <usage>
* <description>
*
* @param logfile_id - <usage>
* <description>
*
* @param logmanager - <usage>
* <description>
*
* @param store_shmem - <usage>
* <description>
*
* @param logfile log file
* @param logfile_id identifier for log file
* @param logmanager log manager pointer
* @param store_shmem flag to indicate whether log is physically written to shmem
* @param write_syslog flag to indicate whether log is also written to syslog
*
* @return true if succeed, false otherwise
*
*
*/
static bool logfile_init(
logfile_t* logfile,
@ -2841,7 +2836,7 @@ static void* thr_filewriter_fun(
}
/** Process all logfiles which have buffered writes. */
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
{
retry_flush_on_exit:
/**

View File

@ -49,7 +49,7 @@ typedef struct log_info_st
{
size_t li_sesid;
int li_enabled_logs;
} log_info_t;
} log_info_t;
#define LE LOGFILE_ERROR
#define LM LOGFILE_MESSAGE
@ -113,16 +113,15 @@ void skygw_logmanager_exit(void);
void skygw_log_done(void);
int skygw_log_write(logfile_id_t id, const char* format, ...);
int skygw_log_flush(logfile_id_t id);
void skygw_log_sync_all(void);
int skygw_log_rotate(logfile_id_t id);
int skygw_log_write_flush(logfile_id_t id, const char* format, ...);
int skygw_log_enable(logfile_id_t id);
int skygw_log_disable(logfile_id_t id);
void skygw_log_sync_all(void);
EXTERN_C_BLOCK_END
void writebuf_clear(void* data);
const char* get_trace_prefix_default(void);
const char* get_trace_suffix_default(void);
const char* get_msg_prefix_default(void);

View File

@ -9,9 +9,9 @@ macro(set_maxscale_version)
#MaxScale version number
set(MAXSCALE_VERSION_MAJOR "1")
set(MAXSCALE_VERSION_MINOR "0")
set(MAXSCALE_VERSION_PATCH "2")
set(MAXSCALE_VERSION_PATCH "5")
set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}")
set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-rc")
set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-unstable")
endmacro()
@ -21,7 +21,7 @@ macro(set_variables)
set(INSTALL_DIR "/usr/local/skysql/maxscale/" CACHE PATH "MaxScale installation directory.")
# Build type
set(BUILD_TYPE "None" CACHE STRING "Build type, possible values are:None, Debug, Optimized.")
set(BUILD_TYPE "None" CACHE STRING "Build type, possible values are:None, Debug, DebugSymbols, Optimized.")
# hostname or IP address of MaxScale's host
set(TEST_HOST "127.0.0.1" CACHE STRING "hostname or IP address of MaxScale's host")
@ -33,7 +33,7 @@ macro(set_variables)
set(TEST_PORT_RW "4006" CACHE STRING "port of read/write split router module")
# port of read/write split router module with hints
set(TEST_PORT_RW_HINT "4006" CACHE STRING "port of read/write split router module with hints")
set(TEST_PORT_RW_HINT "4009" CACHE STRING "port of read/write split router module with hints")
# master test server server_id
set(TEST_MASTER_ID "3000" CACHE STRING "master test server server_id")
@ -268,7 +268,7 @@ endmacro()
function(subdirs VAR DIRPATH)
if(${CMAKE_VERSION} VERSION_LESS 2.12 )
if(${CMAKE_VERSION} VERSION_LESS 2.8.12 )
set(COMP_VAR PATH)
else()
set(COMP_VAR DIRECTORY)

View File

@ -67,28 +67,18 @@ extern __thread log_info_t tls_log_info;
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
static THD* get_or_create_thd_for_parsing(
MYSQL* mysql,
char* query_str);
static unsigned long set_client_flags(
MYSQL* mysql);
static bool create_parse_tree(
THD* thd);
static skygw_query_type_t resolve_query_type(
THD* thd);
static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str);
static unsigned long set_client_flags(MYSQL* mysql);
static bool create_parse_tree(THD* thd);
static skygw_query_type_t resolve_query_type(THD* thd);
static bool skygw_stmt_causes_implicit_commit(
LEX* lex,
int* autocommit_stmt);
static int is_autocommit_stmt(
LEX* lex);
static int is_autocommit_stmt(LEX* lex);
static void parsing_info_set_plain_str(void* ptr, char* str);
static void* skygw_get_affected_tables(void* lexptr);
static void parsing_info_set_plain_str(void* ptr,
char* str);
/**
* Calls parser for the query includede in the buffer. Creates and adds parsing
@ -107,6 +97,11 @@ skygw_query_type_t query_classifier_get_type(
ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
if (querybuf == NULL)
{
succp = false;
goto retblock;
}
/** Create parsing info for the query and store it to buffer */
succp = query_is_parsed(querybuf);
@ -133,6 +128,7 @@ skygw_query_type_t query_classifier_get_type(
}
}
}
retblock:
return qtype;
}
@ -158,7 +154,7 @@ bool parse_query (
/** Do not parse without releasing previous parse info first */
ss_dassert(!query_is_parsed(querybuf));
if (query_is_parsed(querybuf))
if (querybuf == NULL || query_is_parsed(querybuf))
{
return false;
}
@ -174,8 +170,8 @@ bool parse_query (
data = (uint8_t*)GWBUF_DATA(querybuf);
len = MYSQL_GET_PACKET_LEN(data)-1; /*< distract 1 for packet type byte */
if (len < 1 || (query_str = (char *)malloc(len+1)) == NULL)
if (len < 1 || len >= ~((size_t)0) - 1 || (query_str = (char *)malloc(len+1)) == NULL)
{
/** Free parsing info data */
parsing_info_done(pi);
@ -225,7 +221,7 @@ bool query_is_parsed(
GWBUF* buf)
{
CHK_GWBUF(buf);
return GWBUF_IS_PARSED(buf);
return (buf != NULL && GWBUF_IS_PARSED(buf));
}
@ -957,13 +953,25 @@ return_rc:
return rc;
}
#if defined(NOT_USED)
char* skygw_query_classifier_get_stmtname(
MYSQL* mysql)
GWBUF* buf)
{
MYSQL* mysql;
if (buf == NULL ||
buf->gwbuf_bufobj == NULL ||
buf->gwbuf_bufobj->bo_data == NULL ||
(mysql = (MYSQL *)((parsing_info_t *)buf->gwbuf_bufobj->bo_data)->pi_handle) == NULL ||
mysql->thd == NULL ||
(THD *)(mysql->thd))->lex == NULL ||
(THD *)(mysql->thd))->lex->prepared_stmt_name == NULL)
{
return NULL;
}
return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str;
}
#endif
/**
* Get the parse tree from parsed querybuf.
@ -975,31 +983,29 @@ char* skygw_query_classifier_get_stmtname(
LEX* get_lex(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);
parsing_info_t* pi;
MYSQL* mysql;
THD* thd;
if (querybuf == NULL || !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;
}
return thd->lex;
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;
}
return thd->lex;
}
@ -1009,7 +1015,7 @@ LEX* get_lex(GWBUF* querybuf)
* @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* lexptr)
static void* skygw_get_affected_tables(void* lexptr)
{
LEX* lex = (LEX*)lexptr;
@ -1027,87 +1033,93 @@ void* skygw_get_affected_tables(void* lexptr)
/**
* 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.
* 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, bool fullnames)
char** skygw_get_table_names(GWBUF* querybuf, int* tblsize, bool fullnames)
{
LEX* lex;
TABLE_LIST* tbl;
int i = 0,
LEX* lex;
TABLE_LIST* tbl;
int i = 0,
currtblsz = 0;
char **tables = NULL,
char **tables = NULL,
**tmp = NULL;
if( (lex = get_lex(querybuf)) == NULL ||
lex->current_select == NULL )
if(querybuf == NULL ||
tblsize == NULL ||
(lex = get_lex(querybuf)) == NULL ||
lex->current_select == NULL)
{
goto retblock;
}
goto retblock;
}
lex->current_select = lex->all_selects_list;
lex->current_select = lex->all_selects_list;
while(lex->current_select){
tbl = (TABLE_LIST*)skygw_get_affected_tables(lex);
while(lex->current_select)
{
tbl = (TABLE_LIST*)skygw_get_affected_tables(lex);
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;
}
}
if(tmp != NULL){
char *catnm = NULL;
if(fullnames)
while (tbl)
{
if (i >= currtblsz)
{
if(tbl->db && strcmp(tbl->db,"skygw_virtual") != 0)
tmp = (char**)malloc(sizeof(char*)*(currtblsz*2+1));
if(tmp)
{
if(currtblsz > 0)
{
catnm = (char*)calloc(strlen(tbl->db) + strlen(tbl->table_name) + 2,sizeof(char));
int x;
for(x = 0; x<currtblsz; x++)
{
tmp[x] = tables[x];
}
free(tables);
}
tables = tmp;
currtblsz = currtblsz*2 + 1;
}
}
if(tmp != NULL)
{
char *catnm = NULL;
if(fullnames)
{
if (tbl->db &&
strcmp(tbl->db,"skygw_virtual") != 0)
{
catnm = (char*)calloc(strlen(tbl->db) +
strlen(tbl->table_name) +
2,
sizeof(char));
strcpy(catnm,tbl->db);
strcat(catnm,".");
strcat(catnm,tbl->table_name);
}
}
if(catnm)
{
tables[i++] = catnm;
}
else
{
tables[i++] = strdup(tbl->table_name);
}
tbl=tbl->next_local;
}
if(catnm)
{
tables[i++] = catnm;
}
else
{
tables[i++] = strdup(tbl->table_name);
}
tbl=tbl->next_local;
}
}
lex->current_select = lex->current_select->next_select_in_list();
}
retblock:
*tblsize = i;
return tables;
} /*< while (tbl) */
lex->current_select = lex->current_select->next_select_in_list();
} /*< while(lex->current_select) */
retblock:
*tblsize = i;
return tables;
}
/**
@ -1117,52 +1129,70 @@ char** skygw_get_table_names(GWBUF* querybuf,int* tblsize, bool fullnames)
*/
char* skygw_get_created_table_name(GWBUF* querybuf)
{
LEX* lex;
if((lex = get_lex(querybuf)) == NULL)
{
return NULL;
}
LEX* lex;
if(querybuf == NULL || (lex = get_lex(querybuf)) == NULL)
{
return NULL;
}
if(lex->create_last_non_select_table &&
lex->create_last_non_select_table->table_name){
char* name = strdup(lex->create_last_non_select_table->table_name);
return name;
}else{
return NULL;
}
if (lex->create_last_non_select_table &&
lex->create_last_non_select_table->table_name)
{
char* name = strdup(lex->create_last_non_select_table->table_name);
return name;
}
else
{
return NULL;
}
}
/**
* Checks whether the query is a "real" query ie. SELECT,UPDATE,INSERT,DELETE or any variation of these.
* Queries that affect the underlying database are not considered as real queries and the queries that target
* specific row or variable data are regarded as the real queries.
* Checks whether the query is a "real" query ie. SELECT,UPDATE,INSERT,DELETE or
* any variation of these. Queries that affect the underlying database are not
* considered as real queries and the queries that target specific row or
* variable data are regarded as the real queries.
*
* @param GWBUF to analyze
*
* @return true if the query is a real query, otherwise false
*/
bool skygw_is_real_query(GWBUF* querybuf)
{
LEX* lex = get_lex(querybuf);
if(lex){
switch(lex->sql_command){
case SQLCOM_SELECT:
return lex->all_selects_list->table_list.elements > 0;
case SQLCOM_UPDATE:
case SQLCOM_INSERT:
case SQLCOM_INSERT_SELECT:
case SQLCOM_DELETE:
case SQLCOM_TRUNCATE:
case SQLCOM_REPLACE:
case SQLCOM_REPLACE_SELECT:
case SQLCOM_PREPARE:
case SQLCOM_EXECUTE:
return true;
default:
return false;
bool succp;
LEX* lex;
if (querybuf == NULL ||
(lex = get_lex(querybuf)) == NULL)
{
succp = false;
goto retblock;
}
}
return false;
switch(lex->sql_command) {
case SQLCOM_SELECT:
succp = lex->all_selects_list->table_list.elements > 0;
goto retblock;
break;
case SQLCOM_UPDATE:
case SQLCOM_INSERT:
case SQLCOM_INSERT_SELECT:
case SQLCOM_DELETE:
case SQLCOM_TRUNCATE:
case SQLCOM_REPLACE:
case SQLCOM_REPLACE_SELECT:
case SQLCOM_PREPARE:
case SQLCOM_EXECUTE:
succp = true;
goto retblock;
break;
default:
succp = false;
goto retblock;
break;
}
retblock:
return succp;
}
@ -1173,10 +1203,11 @@ bool skygw_is_real_query(GWBUF* querybuf)
*/
bool is_drop_table_query(GWBUF* querybuf)
{
LEX* lex;
LEX* lex;
return (lex = get_lex(querybuf)) != NULL &&
lex->sql_command == SQLCOM_DROP_TABLE;
return (querybuf != NULL &&
(lex = get_lex(querybuf)) != NULL &&
lex->sql_command == SQLCOM_DROP_TABLE);
}
inline void add_str(char** buf, int* buflen, int* bufsize, char* str)
@ -1319,7 +1350,8 @@ char* skygw_get_canonical(
Item* item;
char* querystr;
if (!GWBUF_IS_PARSED(querybuf))
if (querybuf == NULL ||
!GWBUF_IS_PARSED(querybuf))
{
querystr = NULL;
goto retblock;
@ -1456,25 +1488,30 @@ retblock:
void parsing_info_done(
void* ptr)
{
parsing_info_t* pi = (parsing_info_t *)ptr;
parsing_info_t* pi;
if (ptr)
{
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);
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);
}
}
/**

View File

@ -97,12 +97,13 @@ typedef struct parsing_info_st {
skygw_query_type_t query_classifier_get_type(GWBUF* querybuf);
skygw_query_op_t query_classifier_get_operation(GWBUF* querybuf);
/** Free THD context and close MYSQL */
char* skygw_query_classifier_get_stmtname(MYSQL* mysql);
#if defined(NOT_USED)
char* skygw_query_classifier_get_stmtname(GWBUF* buf);
#endif
char* skygw_get_created_table_name(GWBUF* querybuf);
bool is_drop_table_query(GWBUF* querybuf);
bool skygw_is_real_query(GWBUF* querybuf);
void* skygw_get_affected_tables(void* lexptr);
char** skygw_get_table_names(GWBUF* querybuf,int* tblsize,bool fullnames);
char** skygw_get_table_names(GWBUF* querybuf, int* tblsize, bool fullnames);
char* skygw_get_canonical(GWBUF* querybuf);
bool parse_query (GWBUF* querybuf);
parsing_info_t* parsing_info_init(void (*donefun)(void *));

View File

@ -201,6 +201,36 @@ GWBUF *rval;
return rval;
}
/**
* Clone whole GWBUF list instead of single buffer.
*
* @param buf head of the list to be cloned till the tail of it
*
* @return head of the cloned list or NULL if the list was empty.
*/
GWBUF* gwbuf_clone_all(
GWBUF* buf)
{
GWBUF* rval;
GWBUF* clonebuf;
if (buf == NULL)
{
return NULL;
}
/** Store the head of the list to rval. */
clonebuf = gwbuf_clone(buf);
rval = clonebuf;
while (buf->next)
{
buf = buf->next;
clonebuf->next = gwbuf_clone(buf);
clonebuf = clonebuf->next;
}
return rval;
}
GWBUF *gwbuf_clone_portion(
GWBUF *buf,

View File

@ -52,8 +52,32 @@
#include <mysqld_error.h>
#define DEFAULT_CONNECT_TIMEOUT 3
#define DEFAULT_READ_TIMEOUT 1
#define DEFAULT_WRITE_TIMEOUT 2
#define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')"
#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
#if 0
# define LOAD_MYSQL_USERS_QUERY \
"SELECT DISTINCT \
user.user AS user, \
user.host AS host, \
user.password AS password, \
concat(user.user,user.host,user.password, \
IF((user.Select_priv+0)||find_in_set('Select',Coalesce(tp.Table_priv,0)),'Y','N') , \
COALESCE( db.db,tp.db, '')) AS userdata, \
user.Select_priv AS anydb, \
COALESCE( db.db,tp.db, NULL) AS db \
FROM \
mysql.user LEFT JOIN \
mysql.db ON user.user=db.user AND user.host=db.host LEFT JOIN \
mysql.tables_priv tp ON user.user=tp.user AND user.host=tp.host \
WHERE user.user IS NOT NULL AND user.user <> ''"
#else
# define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
#endif
#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user"
#define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC"
@ -85,6 +109,11 @@ void *resource_fetch(HASHTABLE *, char *);
int resource_add(HASHTABLE *, char *, char *);
int resource_hash(char *);
static int normalize_hostname(char *input_host, char *output_host);
static int gw_mysql_set_timeouts(
MYSQL* handle,
int read_timeout,
int write_timeout,
int connect_timeout);
/**
* Load the user/passwd form mysql.user table into the service users' hashtable
@ -219,7 +248,7 @@ HASHTABLE *oldresources;
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db) {
struct sockaddr_in serv_addr;
MYSQL_USER_HOST key;
char ret_ip[INET_ADDRSTRLEN + 1]="";
char ret_ip[400]="";
int ret = 0;
if (users == NULL || user == NULL || host == NULL) {
@ -409,7 +438,8 @@ getDatabases(SERVICE *service, MYSQL *con)
*
* @param service The current service
* @param users The users table into which to load the users
* @return -1 on any error or the number of users inserted (0 means no users at all)
* @return -1 on any error or the number of users inserted
* (0 means no users at all)
*/
static int
getUsers(SERVICE *service, USERS *users)
@ -421,20 +451,24 @@ getUsers(SERVICE *service, USERS *users)
char *service_passwd = NULL;
char *dpwd;
int total_users = 0;
SERVER *server;
SERVER_REF *server;
char *users_query;
unsigned char hash[SHA_DIGEST_LENGTH]="";
char *users_data = NULL;
int nusers = 0;
int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN;
int users_data_row_len = MYSQL_USER_MAXLEN +
MYSQL_HOST_MAXLEN +
MYSQL_PASSWORD_LEN +
sizeof(char) +
MYSQL_DATABASE_MAXLEN;
int dbnames = 0;
int db_grants = 0;
serviceGetUser(service, &service_user, &service_passwd);
if (service_user == NULL || service_passwd == NULL)
return -1;
if (serviceGetUser(service, &service_user, &service_passwd) == 0)
{
ss_dassert(service_passwd == NULL || service_user == NULL);
return -1;
}
con = mysql_init(NULL);
if (con == NULL) {
@ -444,13 +478,26 @@ getUsers(SERVICE *service, USERS *users)
mysql_error(con))));
return -1;
}
/** Set read, write and connect timeout values */
if (gw_mysql_set_timeouts(con,
DEFAULT_READ_TIMEOUT,
DEFAULT_WRITE_TIMEOUT,
DEFAULT_CONNECT_TIMEOUT))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to set timeout values for backend "
"connection.")));
mysql_close(con);
return -1;
}
if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to set external connection. "
"It is needed for backend server connections. "
"Exiting.")));
"It is needed for backend server connections.")));
mysql_close(con);
return -1;
}
/**
@ -458,46 +505,69 @@ getUsers(SERVICE *service, USERS *users)
* out of databases
* to try
*/
server = service->databases;
server = service->dbref;
dpwd = decryptPassword(service_passwd);
/* Select a server with Master bit, if available */
while (server != NULL && !(server->status & SERVER_MASTER)) {
server = server->nextdb;
while (server != NULL && !(server->server->status & SERVER_MASTER)) {
server = server->next;
}
if (service->svc_do_shutdown)
{
free(dpwd);
mysql_close(con);
return -1;
}
/* Try loading data from master server */
if (server != NULL && (mysql_real_connect(con, server->name, service_user, dpwd, NULL, server->port, NULL, 0) != NULL)) {
if (server != NULL &&
(mysql_real_connect(con,
server->server->name, service_user,
dpwd,
NULL,
server->server->port,
NULL, 0) != NULL))
{
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"Dbusers : Loading data from backend database with Master role [%s:%i] "
"for service [%s]",
server->name,
server->port,
"Dbusers : Loading data from backend database with "
"Master role [%s:%i] for service [%s]",
server->server->name,
server->server->port,
service->name)));
} else {
/* load data from other servers via loop */
server = service->databases;
server = service->dbref;
while (server != NULL && (mysql_real_connect(con,
server->name,
service_user,
dpwd,
NULL,
server->port,
NULL,
0) == NULL))
while (!service->svc_do_shutdown &&
server != NULL &&
(mysql_real_connect(con,
server->server->name,
service_user,
dpwd,
NULL,
server->server->port,
NULL,
0) == NULL))
{
server = server->nextdb;
server = server->next;
}
if (service->svc_do_shutdown)
{
free(dpwd);
mysql_close(con);
return -1;
}
if (server != NULL) {
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"Dbusers : Loading data from backend database [%s:%i] "
"for service [%s]",
server->name,
server->port,
"Dbusers : Loading data from backend database "
"[%s:%i] for service [%s]",
server->server->name,
server->server->port,
service->name)));
}
}
@ -515,9 +585,7 @@ getUsers(SERVICE *service, USERS *users)
return -1;
}
/* count users */
/* start with users and db grants for users */
/** Count users. Start with users and db grants for users */
if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) {
if (mysql_errno(con) != ER_TABLEACCESS_DENIED_ERROR) {
/* This is an error we cannot handle, return */
@ -1193,3 +1261,58 @@ int useorig = 0;
return netmask;
}
/**
* Set read, write and connect timeout values for MySQL database connection.
*
* @param handle MySQL handle
* @param read_timeout Read timeout value in seconds
* @param write_timeout Write timeout value in seconds
* @param connect_timeout Connect timeout value in seconds
*
* @return 0 if succeed, 1 if failed
*/
static int gw_mysql_set_timeouts(
MYSQL* handle,
int read_timeout,
int write_timeout,
int connect_timeout)
{
int rc;
if ((rc = mysql_options(handle,
MYSQL_OPT_READ_TIMEOUT,
(void *)&read_timeout)))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to set read timeout for backend "
"connection.")));
goto retblock;
}
if ((rc = mysql_options(handle,
MYSQL_OPT_CONNECT_TIMEOUT,
(void *)&connect_timeout)))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to set connect timeout for backend "
"connection.")));
goto retblock;
}
if ((rc = mysql_options(handle,
MYSQL_OPT_WRITE_TIMEOUT,
(void *)&write_timeout)))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to set write timeout for backend "
"connection.")));
goto retblock;
}
retblock:
return rc;
}

View File

@ -310,7 +310,7 @@ DCB *clone;
return NULL;
}
clone->fd = DCBFD_CLONED;;
clone->fd = DCBFD_CLOSED;
clone->flags |= DCBF_CLONE;
clone->state = orig->state;
clone->data = orig->data;
@ -321,7 +321,10 @@ DCB *clone;
clone->protocol = orig->protocol;
clone->func.write = dcb_null_write;
clone->func.close = dcb_null_close;
/**
* Close triggers closing of router session as well which is needed.
*/
clone->func.close = orig->func.close;
clone->func.auth = dcb_null_auth;
return clone;
@ -385,22 +388,25 @@ DCB_CALLBACK *cb;
*/
{
SESSION *local_session = dcb->session;
dcb->session = NULL;
CHK_SESSION(local_session);
/*<
* Remove reference from session if dcb is client.
*/
if (local_session->client == dcb) {
local_session->client = NULL;
}
dcb->session = NULL;
/**
* Set session's client pointer NULL so that other threads
* won't try to call dcb_close for client DCB
* after this call.
*/
if (local_session->client == dcb)
{
spinlock_acquire(&local_session->ses_lock);
local_session->client = NULL;
spinlock_release(&local_session->ses_lock);
}
session_free(local_session);
}
}
if (dcb->protocol && ((dcb->flags & DCBF_CLONE) ==0))
free(dcb->protocol);
if (dcb->data && ((dcb->flags & DCBF_CLONE) ==0))
free(dcb->data);
if (dcb->protocol && (!DCB_IS_CLONE(dcb)))
free(dcb->protocol);
if (dcb->remote)
free(dcb->remote);
if (dcb->user)
@ -425,7 +431,6 @@ DCB_CALLBACK *cb;
}
spinlock_release(&dcb->cb_lock);
bitmask_free(&dcb->memdata.bitmask);
free(dcb);
}
@ -900,7 +905,8 @@ int below_water;
dcb->state != DCB_STATE_POLLING &&
dcb->state != DCB_STATE_LISTENING &&
dcb->state != DCB_STATE_NOPOLLING &&
dcb->session->state != SESSION_STATE_STOPPING))
(dcb->session == NULL ||
dcb->session->state != SESSION_STATE_STOPPING)))
{
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
@ -911,7 +917,7 @@ int below_water;
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
ss_dassert(false);
//ss_dassert(false);
return 0;
}
@ -1254,20 +1260,20 @@ dcb_close(DCB *dcb)
if (rc == 0)
{
/**
* close protocol and router session
*/
* close protocol and router session
*/
if (dcb->func.close != NULL)
{
dcb->func.close(dcb);
}
/** Call possible callback for this DCB in case of close */
dcb_call_callback(dcb, DCB_REASON_CLOSE);
if (dcb->state == DCB_STATE_NOPOLLING)
{
dcb_add_to_zombieslist(dcb);
}
}
}
ss_dassert(dcb->state == DCB_STATE_NOPOLLING ||
dcb->state == DCB_STATE_ZOMBIE);
}
@ -1604,7 +1610,9 @@ void dcb_hashtable_stats(
hashsize);
dcb_printf(dcb, "\tNo. of entries: %d\n", total);
dcb_printf(dcb, "\tAverage chain length: %.1f\n", (float)total / hashsize);
dcb_printf(dcb,
"\tAverage chain length: %.1f\n",
(hashsize == 0 ? (float)hashsize : (float)total / hashsize));
dcb_printf(dcb, "\tLongest chain length: %d\n", longest);
}
@ -1983,6 +1991,12 @@ DCB_CALLBACK *cb, *nextcb;
{
nextcb = cb->next;
spinlock_release(&dcb->cb_lock);
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"%lu [dcb_call_callback] %s",
pthread_self(),
STRDCBREASON(reason))));
cb->cb(dcb, reason, cb->userdata);
spinlock_acquire(&dcb->cb_lock);
cb = nextcb;
@ -2068,6 +2082,10 @@ dcb_get_next (DCB* dcb)
void
dcb_call_foreach(DCB_REASON reason)
{
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"%lu [dcb_call_foreach]",
pthread_self())));
switch (reason) {
case DCB_REASON_CLOSE:
case DCB_REASON_DRAINED:
@ -2100,7 +2118,7 @@ dcb_call_foreach(DCB_REASON reason)
/**
* Null protocol write routine used for cloned dcb's. It merely consumes
* buffers written on the cloned DCB.
* buffers written on the cloned DCB and sets the DCB_REPLIED flag.
*
* @param dcb The descriptor control block
* @param buf The buffer being written
@ -2113,6 +2131,9 @@ dcb_null_write(DCB *dcb, GWBUF *buf)
{
buf = gwbuf_consume(buf, GWBUF_LENGTH(buf));
}
dcb->flags |= DCBF_REPLIED;
return 1;
}

View File

@ -91,28 +91,31 @@ filter_free(FILTER_DEF *filter)
{
FILTER_DEF *ptr;
/* First of all remove from the linked list */
spinlock_acquire(&filter_spin);
if (allFilters == filter)
if (filter)
{
allFilters = filter->next;
}
else
{
ptr = allFilters;
while (ptr && ptr->next != filter)
/* First of all remove from the linked list */
spinlock_acquire(&filter_spin);
if (allFilters == filter)
{
ptr = ptr->next;
allFilters = filter->next;
}
if (ptr)
ptr->next = filter->next;
}
spinlock_release(&filter_spin);
else
{
ptr = allFilters;
while (ptr && ptr->next != filter)
{
ptr = ptr->next;
}
if (ptr)
ptr->next = filter->next;
}
spinlock_release(&filter_spin);
/* Clean up session and free the memory */
free(filter->name);
free(filter->module);
free(filter);
/* Clean up session and free the memory */
free(filter->name);
free(filter->module);
free(filter);
}
}
/**

View File

@ -56,6 +56,7 @@
#include <config.h>
#include <poll.h>
#include <housekeeper.h>
#include <service.h>
#include <memlog.h>
#include <stdlib.h>
@ -216,6 +217,7 @@ static void sigterm_handler (int i) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"MaxScale received signal SIGTERM. Exiting.")));
skygw_log_sync_all();
shutdown_server();
}
@ -227,6 +229,7 @@ sigint_handler (int i)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"MaxScale received signal SIGINT. Shutting down.")));
skygw_log_sync_all();
shutdown_server();
fprintf(stderr, "\n\nShutting down MaxScale\n\n");
}
@ -269,6 +272,8 @@ sigfatal_handler (int i)
}
}
skygw_log_sync_all();
/* re-raise signal to enforce core dump */
fprintf(stderr, "\n\nWriting core dump\n");
signal_set(i, SIG_DFL);
@ -1526,7 +1531,7 @@ int main(int argc, char **argv)
free(log_context);
}
/*<
/**
* Init Log Manager for MaxScale.
* If $MAXSCALE_HOME is set then write the logs into $MAXSCALE_HOME/log.
* The skygw_logmanager_init expects to take arguments as passed to main
@ -1536,23 +1541,28 @@ int main(int argc, char **argv)
{
char buf[1024];
char *argv[8];
bool succp;
bool succp;
/** Set log directory under $MAXSCALE_HOME/log */
sprintf(buf, "%s/log", home_dir);
if(mkdir(buf, 0777) != 0){
if(errno != EEXIST){
fprintf(stderr,
"Error: Cannot create log directory: %s\n",buf);
goto return_main;
}
}
if(mkdir(buf, 0777) != 0)
{
if(errno != EEXIST)
{
fprintf(stderr,
"Error: Cannot create log directory: %s\n",
buf);
goto return_main;
}
}
argv[0] = "MaxScale";
argv[1] = "-j";
argv[2] = buf;
if (logtofile)
{
argv[3] = "-l"; /*< write to syslog */
/** Logs that should be syslogged */
argv[4] = "LOGFILE_MESSAGE,LOGFILE_ERROR"
"LOGFILE_DEBUG,LOGFILE_TRACE";
argv[5] = NULL;
@ -1561,9 +1571,9 @@ int main(int argc, char **argv)
else
{
argv[3] = "-s"; /*< store to shared memory */
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< ..these logs to shm */
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< to shm */
argv[5] = "-l"; /*< write to syslog */
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< ..these logs to syslog */
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< to syslog */
argv[7] = NULL;
succp = skygw_logmanager_init(7, argv);
}
@ -1574,8 +1584,7 @@ int main(int argc, char **argv)
goto return_main;
}
}
/*<
/**
* Resolve the full pathname for configuration file and check for
* read accessibility.
*/
@ -1837,7 +1846,8 @@ return_main:
void
shutdown_server()
{
poll_shutdown();
service_shutdown();
poll_shutdown();
hkshutdown();
memlog_flush_all();
log_flush_shutdown();

View File

@ -81,7 +81,7 @@ setipaddress(struct in_addr *a, char *p) {
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : getaddrinfo failed for [%s] due [%s]",
"Error: Failed to obtain address for host %s, %s",
p,
gai_strerror(rc))));
@ -94,7 +94,7 @@ setipaddress(struct in_addr *a, char *p) {
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : getaddrinfo failed for [%s] due [%s]",
"Error: Failed to obtain address for host %s, %s",
p,
gai_strerror(rc))));

View File

@ -449,28 +449,33 @@ void hashtable_get_stats(
int i;
int j;
ht = (HASHTABLE *)table;
CHK_HASHTABLE(ht);
*nelems = 0;
*longest = 0;
hashtable_read_lock(ht);
for (i = 0; i < ht->hashsize; i++)
*nelems = 0;
*longest = 0;
*hashsize = 0;
if (table != NULL)
{
j = 0;
entries = ht->entries[i];
while (entries)
ht = (HASHTABLE *)table;
CHK_HASHTABLE(ht);
hashtable_read_lock(ht);
for (i = 0; i < ht->hashsize; i++)
{
j++;
entries = entries->next;
j = 0;
entries = ht->entries[i];
while (entries)
{
j++;
entries = entries->next;
}
*nelems += j;
if (j > *longest) {
*longest = j;
}
}
*nelems += j;
if (j > *longest) {
*longest = j;
}
*hashsize = ht->hashsize;
hashtable_read_unlock(ht);
}
*hashsize = ht->hashsize;
hashtable_read_unlock(ht);
}
@ -503,7 +508,7 @@ hashtable_read_lock(HASHTABLE *table)
;
spinlock_acquire(&table->spin);
}
table->n_readers++;
atomic_add(&table->n_readers, 1);
spinlock_release(&table->spin);
}

View File

@ -121,6 +121,40 @@ unsigned char *ptr;
return 1;
}
/**
* Calculate the length of MySQL packet and how much is missing from the GWBUF
* passed as parameter.
*
* This routine assumes that there is only one MySQL packet in the buffer.
*
* @param buf buffer list including the query, may consist of
* multiple buffers
* @param nbytes_missing pointer to missing bytecount
*
* @return the length of MySQL packet and writes missing bytecount to
* nbytes_missing.
*/
int modutil_MySQL_query_len(
GWBUF* buf,
int* nbytes_missing)
{
int len;
int buflen;
if (!modutil_is_SQL(buf))
{
len = 0;
goto retblock;
}
len = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf));
*nbytes_missing = len-1;
buflen = gwbuf_length(buf);
*nbytes_missing -= buflen-5;
retblock:
return len;
}
/**
@ -178,7 +212,7 @@ GWBUF *addition;
/**
* Extract the SQL from a COM_QUERY packet and return in a NULL terminated buffer.
* The buffer shoudl be freed by the caller when it is no longer required.
* The buffer should be freed by the caller when it is no longer required.
*
* If the packet is not a COM_QUERY packet then the function will return NULL
*
@ -252,7 +286,7 @@ modutil_get_query(GWBUF *buf)
case MYSQL_COM_QUERY:
len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */
if (len < 1 || (query_str = (char *)malloc(len+1)) == NULL)
if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
{
goto retblock;
}
@ -262,7 +296,7 @@ modutil_get_query(GWBUF *buf)
default:
len = strlen(STRPACKETTYPE(packet_type))+1;
if (len < 1 || (query_str = (char *)malloc(len+1)) == NULL)
if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
{
goto retblock;
}

View File

@ -674,6 +674,14 @@ poll_set_maxwait(unsigned int maxwait)
* to process the DCB. If there are pending events the DCB will be moved to the
* back of the queue so that other DCB's will have a share of the threads to
* execute events for them.
*
* Including session id to log entries depends on this function. Assumption is
* that when maxscale thread starts processing of an event it processes one
* and only one session until it returns from this function. Session id is
* read to thread's local storage in macro LOGIF_MAYBE(...) and reset back
* to zero just before returning in LOGIF(...) macro.
* Thread local storage (tls_log_info_t) follows thread and is accessed every
* time log is written to particular log.
*
* @param thread_id The thread ID of the calling thread
* @return 0 if no DCB's have been processed
@ -797,7 +805,7 @@ unsigned long qtime;
simple_mutex_unlock(&dcb->dcb_write_lock);
#else
atomic_add(&pollStats.n_write, 1);
/** Read session id to thread's local storage */
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
dcb,
&tls_log_info.li_sesid,
@ -851,6 +859,7 @@ unsigned long qtime;
dcb,
dcb->fd)));
atomic_add(&pollStats.n_read, 1);
/** Read session id to thread's local storage */
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
dcb,
&tls_log_info.li_sesid,
@ -890,6 +899,7 @@ unsigned long qtime;
strerror(eno))));
}
atomic_add(&pollStats.n_error, 1);
/** Read session id to thread's local storage */
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
dcb,
&tls_log_info.li_sesid,
@ -918,6 +928,7 @@ unsigned long qtime;
{
dcb->flags |= DCBF_HUNG;
spinlock_release(&dcb->dcb_initlock);
/** Read session id to thread's local storage */
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
dcb,
&tls_log_info.li_sesid,
@ -950,6 +961,7 @@ unsigned long qtime;
{
dcb->flags |= DCBF_HUNG;
spinlock_release(&dcb->dcb_initlock);
/** Read session id to thread's local storage */
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
dcb,
&tls_log_info.li_sesid,
@ -1015,6 +1027,7 @@ unsigned long qtime;
}
}
dcb->evq.processing = 0;
/** Reset session id from thread's local storage */
LOGIF(LT, tls_log_info.li_sesid = 0);
spinlock_release(&pollqlock);
@ -1329,7 +1342,6 @@ void poll_add_epollin_event_to_dcb(
}
static void poll_add_event_to_dcb(
DCB* dcb,
GWBUF* buf,
@ -1345,6 +1357,10 @@ static void poll_add_event_to_dcb(
/** Set event to DCB */
if (DCB_POLL_BUSY(dcb))
{
if (dcb->evq.pending_events == 0)
{
pollStats.evq_pending++;
}
dcb->evq.pending_events |= ev;
}
else
@ -1365,6 +1381,8 @@ static void poll_add_event_to_dcb(
dcb->evq.next = dcb;
}
pollStats.evq_length++;
pollStats.evq_pending++;
if (pollStats.evq_length > pollStats.evq_max)
{
pollStats.evq_max = pollStats.evq_length;

View File

@ -68,25 +68,16 @@ server_alloc(char *servname, char *protocol, unsigned short port)
{
SERVER *server;
if ((server = (SERVER *)malloc(sizeof(SERVER))) == NULL)
if ((server = (SERVER *)calloc(1, sizeof(SERVER))) == NULL)
return NULL;
server->name = strdup(servname);
server->protocol = strdup(protocol);
server->port = port;
memset(&server->stats, 0, sizeof(SERVER_STATS));
server->status = SERVER_RUNNING;
server->nextdb = NULL;
server->monuser = NULL;
server->monpw = NULL;
server->unique_name = NULL;
server->server_string = NULL;
server->node_id = -1;
server->rlag = -2;
server->node_ts = 0;
server->parameters = NULL;
server->master_id = -1;
server->depth = -1;
server->slaves = NULL;
spinlock_acquire(&server_spin);
server->next = allServers;
@ -451,6 +442,12 @@ void
server_set_status(SERVER *server, int bit)
{
server->status |= bit;
/** clear error logged flag before the next failure */
if (SERVER_IS_MASTER(server))
{
server->master_err_is_logged = false;
}
}
/**

View File

@ -87,7 +87,6 @@ static void service_add_qualified_param(
SERVICE* svc,
CONFIG_PARAMETER* param);
/**
* Allocate a new service for the gateway to support
*
@ -102,7 +101,7 @@ service_alloc(const char *servname, const char *router)
{
SERVICE *service;
if ((service = (SERVICE *)malloc(sizeof(SERVICE))) == NULL)
if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL)
return NULL;
if ((service->router = load_module(router, MODULE_ROUTER)) == NULL)
{
@ -132,27 +131,10 @@ SERVICE *service;
free(service);
return NULL;
}
service->version_string = NULL;
memset(&service->stats, 0, sizeof(SERVICE_STATS));
service->ports = NULL;
service->stats.started = time(0);
service->state = SERVICE_STATE_ALLOC;
service->credentials.name = NULL;
service->credentials.authdata = NULL;
service->enable_root = 0;
service->localhost_match_wildcard_host = 0;
service->routerOptions = NULL;
service->databases = NULL;
service->svc_config_param = NULL;
service->svc_config_version = 0;
service->filters = NULL;
service->n_filters = 0;
service->weightby = 0;
service->users = NULL;
service->resources = NULL;
spinlock_init(&service->spin);
spinlock_init(&service->users_table_spin);
memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE));
spinlock_acquire(&service_spin);
service->next = allServices;
@ -233,11 +215,6 @@ GWPROTOCOL *funcs;
(port->address == NULL ? "0.0.0.0" : port->address),
port->port,
service->name)));
hashtable_free(service->users->data);
free(service->users);
dcb_free(port->listener);
port->listener = NULL;
goto retblock;
}
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
* This way MaxScale could try reloading users' just after startup
@ -349,24 +326,27 @@ serviceStart(SERVICE *service)
SERV_PROTOCOL *port;
int listeners = 0;
if((service->router_instance = service->router->createInstance(service,
service->routerOptions)) == NULL)
if ((service->router_instance = service->router->createInstance(service,
service->routerOptions)) == NULL)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Error : Failed to start router for service '%s'.",
service->name)));
return listeners;
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"%s: Failed to create router instance for service. Service not started.",
service->name)));
service->state = SERVICE_STATE_FAILED;
return 0;
}
port = service->ports;
while (port)
while (!service->svc_do_shutdown && port)
{
listeners += serviceStartPort(service, port);
port = port->next;
}
if (listeners)
{
service->state = SERVICE_STATE_STARTED;
service->stats.started = time(0);
}
return listeners;
}
@ -405,16 +385,16 @@ SERVICE *ptr;
int n = 0,i;
ptr = allServices;
while (ptr)
while (ptr && !ptr->svc_do_shutdown)
{
n += (i = serviceStart(ptr));
if(i == 0)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Error : Failed to start service '%s'.",
ptr->name)));
LOGFILE_ERROR,
"Error : Failed to start service '%s'.",
ptr->name)));
}
ptr = ptr->next;
@ -445,6 +425,7 @@ int listeners = 0;
port = port->next;
}
service->state = SERVICE_STATE_STOPPED;
return listeners;
}
@ -487,7 +468,7 @@ int
service_free(SERVICE *service)
{
SERVICE *ptr;
SERVER_REF *srv;
if (service->stats.n_current)
return 0;
/* First of all remove from the linked list */
@ -509,6 +490,13 @@ SERVICE *ptr;
spinlock_release(&service_spin);
/* Clean up session and free the memory */
while(service->dbref){
srv = service->dbref;
service->dbref = service->dbref->next;
free(srv);
}
free(service->name);
free(service->routerModule);
if (service->credentials.name)
@ -587,8 +575,13 @@ void
serviceAddBackend(SERVICE *service, SERVER *server)
{
spinlock_acquire(&service->spin);
server->nextdb = service->databases;
service->databases = server;
SERVER_REF *sref;
if((sref = calloc(1,sizeof(SERVER_REF))) != NULL)
{
sref->next = service->dbref;
sref->server = server;
service->dbref = sref;
}
spinlock_release(&service->spin);
}
@ -602,12 +595,12 @@ serviceAddBackend(SERVICE *service, SERVER *server)
int
serviceHasBackend(SERVICE *service, SERVER *server)
{
SERVER *ptr;
SERVER_REF *ptr;
spinlock_acquire(&service->spin);
ptr = service->databases;
while (ptr && ptr != server)
ptr = ptr->nextdb;
ptr = service->dbref;
while (ptr && ptr->server != server)
ptr = ptr->next;
spinlock_release(&service->spin);
return ptr != NULL;
@ -825,7 +818,7 @@ SERVICE *service;
void
printService(SERVICE *service)
{
SERVER *ptr = service->databases;
SERVER_REF *ptr = service->dbref;
struct tm result;
char time_buf[30];
int i;
@ -838,8 +831,8 @@ int i;
printf("\tBackend databases\n");
while (ptr)
{
printf("\t\t%s:%d Protocol: %s\n", ptr->name, ptr->port, ptr->protocol);
ptr = ptr->nextdb;
printf("\t\t%s:%d Protocol: %s\n", ptr->server->name, ptr->server->port, ptr->server->protocol);
ptr = ptr->next;
}
if (service->n_filters)
{
@ -906,7 +899,7 @@ SERVICE *ptr;
*/
void dprintService(DCB *dcb, SERVICE *service)
{
SERVER *server = service->databases;
SERVER_REF *server = service->dbref;
struct tm result;
char timebuf[30];
int i;
@ -916,7 +909,22 @@ int i;
service->name);
dcb_printf(dcb, "\tRouter: %s (%p)\n",
service->routerModule, service->router);
if (service->router)
switch (service->state)
{
case SERVICE_STATE_STARTED:
dcb_printf(dcb, "\tState: Started\n");
break;
case SERVICE_STATE_STOPPED:
dcb_printf(dcb, "\tState: Stopped\n");
break;
case SERVICE_STATE_FAILED:
dcb_printf(dcb, "\tState: Failed\n");
break;
case SERVICE_STATE_ALLOC:
dcb_printf(dcb, "\tState: Allocated\n");
break;
}
if (service->router && service->router_instance)
service->router->diagnostics(service->router_instance, dcb);
dcb_printf(dcb, "\tStarted: %s",
asctime_r(localtime_r(&service->stats.started, &result), timebuf));
@ -935,9 +943,9 @@ int i;
dcb_printf(dcb, "\tBackend databases\n");
while (server)
{
dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->name, server->port,
server->protocol);
server = server->nextdb;
dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->server->name, server->server->port,
server->server->protocol);
server = server->next;
}
if (service->weightby)
dcb_printf(dcb, "\tRouting weight parameter: %s\n",
@ -1403,3 +1411,16 @@ serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action)
return 1;
}
void service_shutdown()
{
SERVICE* svc;
spinlock_acquire(&service_spin);
svc = allServices;
while (svc != NULL)
{
svc->svc_do_shutdown = true;
svc = svc->next;
}
spinlock_release(&service_spin);
}

View File

@ -85,12 +85,21 @@ session_alloc(SERVICE *service, DCB *client_dcb)
"session object due error %d, %s.",
errno,
strerror(errno))));
if (client_dcb->data && !DCB_IS_CLONE(client_dcb))
{
free(client_dcb->data);
client_dcb->data = NULL;
}
goto return_session;
}
#if defined(SS_DEBUG)
session->ses_chk_top = CHK_NUM_SESSION;
session->ses_chk_tail = CHK_NUM_SESSION;
#endif
if (DCB_IS_CLONE(client_dcb))
{
session->ses_is_child = true;
}
spinlock_init(&session->ses_lock);
/*<
* Prevent backend threads from accessing before session is completely
@ -149,6 +158,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
* Decrease refcount, set dcb's session pointer NULL
* and set session pointer to NULL.
*/
session->client = NULL;
session_free(session);
client_dcb->session = NULL;
session = NULL;
@ -189,6 +199,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
* Decrease refcount, set dcb's session pointer NULL
* and set session pointer to NULL.
*/
session->client = NULL;
session_free(session);
client_dcb->session = NULL;
session = NULL;
@ -207,6 +218,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
if (session->state != SESSION_STATE_READY)
{
spinlock_release(&session->ses_lock);
session->client = NULL;
session_free(session);
client_dcb->session = NULL;
session = NULL;
@ -331,12 +343,16 @@ int session_unlink_dcb(
if (nlink == 0)
{
session->state = SESSION_STATE_FREE;
session->state = SESSION_STATE_TO_BE_FREED;
}
if (dcb != NULL)
{
dcb->session = NULL;
if (session->client == dcb)
{
session->client = NULL;
}
dcb->session = NULL;
}
spinlock_release(&session->ses_lock);
@ -357,7 +373,6 @@ bool session_free(
int i;
CHK_SESSION(session);
/*<
* Remove one reference. If there are no references left,
* free session.
@ -388,8 +403,12 @@ bool session_free(
spinlock_release(&session_spin);
atomic_add(&session->service->stats.n_current, -1);
/* Free router_session and session */
if (session->router_session) {
/**
* If session is not child of some other session, free router_session.
* Otherwise let the parent free it.
*/
if (!session->ses_is_child && session->router_session)
{
session->service->router->freeSession(
session->service->router_instance,
session->router_session);
@ -422,7 +441,17 @@ bool session_free(
/** Disable trace and decrease trace logger counter */
session_disable_log(session, LT);
free(session);
/** If session doesn't have parent referencing to it, it can be freed */
if (!session->ses_is_child)
{
session->state = SESSION_STATE_FREE;
if (session->data)
{
free(session->data);
}
free(session);
}
succp = true;
return_succp :
@ -687,6 +716,15 @@ session_state(int state)
return "Listener Session";
case SESSION_STATE_LISTENER_STOPPED:
return "Stopped Listener Session";
#ifdef SS_DEBUG
case SESSION_STATE_STOPPING:
return "Stopping session";
case SESSION_STATE_TO_BE_FREED:
return "Session to be freed";
case SESSION_STATE_FREE:
return "Freed session";
#endif
default:
return "Invalid State";
}

View File

@ -25,8 +25,8 @@ target_link_libraries(test_service fullcore)
target_link_libraries(test_server fullcore)
target_link_libraries(test_users fullcore)
target_link_libraries(test_adminusers fullcore)
add_test(testMySQLUsers test_mysql_users)
target_link_libraries(testmemlog fullcore)
add_test(testMySQLUsers test_mysql_users)
add_test(TestHash test_hash)
add_test(TestHint test_hint)
add_test(TestSpinlock test_spinlock)
@ -40,3 +40,17 @@ add_test(TestServer test_server)
add_test(TestUsers test_users)
add_test(TestAdminUsers test_adminusers)
add_test(TestMemlog testmemlog)
set_tests_properties(testMySQLUsers
TestHash
TestHint
TestSpinlock
TestFilter
TestBuffer
TestDCB
TestModutil
TestPoll
TestService
TestServer
TestUsers
TestAdminUsers
TestMemlog PROPERTIES ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/)

View File

@ -272,6 +272,8 @@ char *home, buf[1024];
if ((home = getenv("MAXSCALE_HOME")) == NULL || strlen(home) >= 1024)
home = "/usr/local/skysql";
sprintf(buf, "%s/etc/passwd", home);
if(!is_valid_posix_path(buf))
exit(1);
if (strcmp(buf, "/etc/passwd") != 0)
unlink(buf);
@ -281,6 +283,9 @@ char *home, buf[1024];
result += test4();
result += test5();
/* Add the default user back so other tests can use it */
admin_add_user("admin", "skysql");
exit(result);
}

View File

@ -49,11 +49,13 @@ HINT *hint;
char* name = strdup("name");
hint = hint_create_parameter(NULL, name, "value");
free(name);
skygw_log_sync_all();
ss_info_dassert(NULL != hint, "New hint list should not be null");
ss_info_dassert(0 == strcmp("value", hint->value), "Hint value should be correct");
ss_info_dassert(0 != hint_exists(&hint, HINT_PARAMETER), "Hint of parameter type should exist");
ss_dfprintf(stderr, "\t..done\nFree hints.");
if (NULL != hint) hint_free(hint);
skygw_log_sync_all();
ss_dfprintf(stderr, "\t..done\n");
return 0;

View File

@ -51,7 +51,7 @@ int result;
"testpoll : Initialise the polling system.");
poll_init();
ss_dfprintf(stderr, "\t..done\nAdd a DCB");
dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER);
dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER);
if(dcb == NULL){
ss_dfprintf(stderr, "\nError on function call: dcb_alloc() returned NULL.\n");

View File

@ -48,7 +48,7 @@ char *status;
ss_dfprintf(stderr,
"testserver : creating server called MyServer");
server = server_alloc("MyServer", "HTTPD", 9876);
skygw_log_sync_all();
//ss_info_dassert(NULL != service, "New server with valid protocol and port must not be null");
//ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation");
@ -56,26 +56,32 @@ char *status;
ss_dfprintf(stderr, "\t..done\nTest Parameter for Server.");
ss_info_dassert(NULL == serverGetParameter(server, "name"), "Parameter should be null when not set");
serverAddParameter(server, "name", "value");
skygw_log_sync_all();
ss_info_dassert(0 == strcmp("value", serverGetParameter(server, "name")), "Parameter should be returned correctly");
ss_dfprintf(stderr, "\t..done\nTesting Unique Name for Server.");
ss_info_dassert(NULL == server_find_by_unique_name("uniquename"), "Should not find non-existent unique name.");
server_set_unique_name(server, "uniquename");
skygw_log_sync_all();
ss_info_dassert(server == server_find_by_unique_name("uniquename"), "Should find by unique name.");
ss_dfprintf(stderr, "\t..done\nTesting Status Setting for Server.");
status = server_status(server);
skygw_log_sync_all();
ss_info_dassert(0 == strcmp("Running", status), "Status of Server should be Running by default.");
if (NULL != status) free(status);
server_set_status(server, SERVER_MASTER);
status = server_status(server);
skygw_log_sync_all();
ss_info_dassert(0 == strcmp("Master, Running", status), "Should find correct status.");
server_clear_status(server, SERVER_MASTER);
free(status);
status = server_status(server);
skygw_log_sync_all();
ss_info_dassert(0 == strcmp("Running", status), "Status of Server should be Running after master status cleared.");
if (NULL != status) free(status);
ss_dfprintf(stderr, "\t..done\nRun Prints for Server and all Servers.");
printServer(server);
printAllServers();
skygw_log_sync_all();
ss_dfprintf(stderr, "\t..done\nFreeing Server.");
ss_info_dassert(0 != server_free(server), "Free should succeed");
ss_dfprintf(stderr, "\t..done\n");

View File

@ -30,9 +30,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <maxscale_test.h>
#include <service.h>
#include <poll.h>
/**
* test1 Allocate a service and do lots of other things
*
@ -42,15 +42,27 @@ test1()
{
SERVICE *service;
int result;
int argc = 3;
char buffer[1024];
sprintf(buffer,"%s",TEST_LOG_DIR);
char* argv[] = {
"log_manager",
"-j",
buffer,
NULL
};
skygw_logmanager_init(argc,argv);
poll_init();
/* Service tests */
ss_dfprintf(stderr,
"testservice : creating service called MyService with router nonexistent");
service = service_alloc("MyService", "non-existent");
skygw_log_sync_all();
ss_info_dassert(NULL == service, "New service with invalid router should be null");
ss_info_dassert(0 == service_isvalid(service), "Service must not be valid after incorrect creation");
ss_dfprintf(stderr, "\t..done\nValid service creation, router testroute.");
service = service_alloc("MyService", "testroute");
skygw_log_sync_all();
ss_info_dassert(NULL != service, "New service with valid router must not be null");
ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation");
ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name");
@ -58,12 +70,16 @@ int result;
ss_info_dassert(0 != serviceAddProtocol(service, "HTTPD", "localhost", 9876), "Add Protocol should succeed");
ss_info_dassert(0 != serviceHasProtocol(service, "HTTPD", 9876), "Service should have new protocol as requested");
serviceStartProtocol(service, "HTTPD", 9876);
skygw_log_sync_all();
ss_dfprintf(stderr, "\t..done\nStarting Service.");
result = serviceStart(service);
skygw_log_sync_all();
ss_info_dassert(0 != result, "Start should succeed");
result = serviceStop(service);
skygw_log_sync_all();
ss_info_dassert(0 != result, "Stop should succeed");
result = serviceStartAll();
skygw_log_sync_all();
ss_info_dassert(0 != result, "Start all should succeed");
ss_dfprintf(stderr, "\t..done\nStopping Service.");

View File

@ -33,6 +33,8 @@
#include <users.h>
#include "log_manager.h"
/**
* test1 Allocate table of users and mess around with it
*
@ -49,26 +51,39 @@ int result, count;
ss_dfprintf(stderr,
"testusers : Initialise the user table.");
users = users_alloc();
skygw_log_sync_all();
ss_info_dassert(NULL != users, "Allocating user table should not return NULL.")
ss_dfprintf(stderr, "\t..done\nAdd a user");
count = users_add(users, "username", "authorisation");
skygw_log_sync_all();
ss_info_dassert(1 == count, "Should add one user");
authdata = users_fetch(users, "username");
skygw_log_sync_all();
ss_info_dassert(NULL != authdata, "Fetch valid user must not return NULL");
ss_info_dassert(0 == strcmp("authorisation", authdata), "User authorisation should be correct");
ss_dfprintf(stderr, "\t..done\nPrint users");
usersPrint(users);
skygw_log_sync_all();
ss_dfprintf(stderr, "\t..done\nUpdate a user");
count = users_update(users, "username", "newauth");
skygw_log_sync_all();
ss_info_dassert(1 == count, "Should update just one user");
authdata = users_fetch(users, "username");
skygw_log_sync_all();
ss_info_dassert(NULL != authdata, "Fetch valid user must not return NULL");
ss_info_dassert(0 == strcmp("newauth", authdata), "User authorisation should be correctly updated");
ss_dfprintf(stderr, "\t..done\nAdd another user");
count = users_add(users, "username2", "authorisation2");
skygw_log_sync_all();
ss_info_dassert(1 == count, "Should add one user");
ss_dfprintf(stderr, "\t..done\nDelete a user.");
count = users_delete(users, "username");
skygw_log_sync_all();
ss_info_dassert(1 == count, "Should delete just one user");
ss_dfprintf(stderr, "\t..done\nFree user table.");
users_free(users);
skygw_log_sync_all();
ss_dfprintf(stderr, "\t..done\n");
return 0;

View File

@ -183,32 +183,41 @@ char *sep;
void *user;
dcb_printf(dcb, "Users table data\n");
dcb_hashtable_stats(dcb, users->data);
if ((iter = hashtable_iterator(users->data)) != NULL)
if (users == NULL || users->data == NULL)
{
dcb_printf(dcb, "User names: ");
sep = "";
dcb_printf(dcb, "Users table is empty\n");
}
else
{
dcb_hashtable_stats(dcb, users->data);
if ((iter = hashtable_iterator(users->data)) != NULL)
{
dcb_printf(dcb, "User names: ");
sep = "";
if (users->usersCustomUserFormat != NULL) {
while ((user = hashtable_next(iter)) != NULL)
{
char *custom_user;
custom_user = users->usersCustomUserFormat(user);
if (custom_user) {
dcb_printf(dcb, "%s%s", sep, custom_user);
free(custom_user);
if (users->usersCustomUserFormat != NULL) {
while ((user = hashtable_next(iter)) != NULL)
{
char *custom_user;
custom_user = users->usersCustomUserFormat(user);
if (custom_user) {
dcb_printf(dcb, "%s%s", sep, custom_user);
free(custom_user);
sep = ", ";
}
}
} else {
while ((user = hashtable_next(iter)) != NULL)
{
dcb_printf(dcb, "%s%s", sep, (char *)user);
sep = ", ";
}
}
} else {
while ((user = hashtable_next(iter)) != NULL)
{
dcb_printf(dcb, "%s%s", sep, (char *)user);
sep = ", ";
}
}
dcb_printf(dcb, "\n");
hashtable_iterator_free(iter);
hashtable_iterator_free(iter);
}
}
dcb_printf(dcb, "\n");
}

View File

@ -184,6 +184,7 @@ extern GWBUF *gwbuf_rtrim(GWBUF *head, unsigned int length);
extern unsigned int gwbuf_length(GWBUF *head);
extern GWBUF *gwbuf_clone_portion(GWBUF *head, size_t offset, size_t len);
extern GWBUF *gwbuf_clone_transform(GWBUF *head, gwbuf_type_t type);
extern GWBUF *gwbuf_clone_all(GWBUF* head);
extern void gwbuf_set_type(GWBUF *head, gwbuf_type_t type);
extern int gwbuf_add_property(GWBUF *buf, char *name, char *value);
extern char *gwbuf_get_property(GWBUF *buf, char *name);
@ -195,7 +196,6 @@ void gwbuf_add_buffer_object(GWBUF* buf,
void* data,
void (*donefun_fp)(void *));
void* gwbuf_get_buffer_object_data(GWBUF* buf, bufobj_id_t id);
EXTERN_C_BLOCK_END

View File

@ -130,7 +130,6 @@ typedef struct {
#define GWPROTOCOL_VERSION {1, 0, 0}
#define DCBFD_CLOSED -1
#define DCBFD_CLONED -2
/**
* The statitics gathered on a descriptor control block
@ -332,4 +331,8 @@ bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs);
*/
#define DCBF_CLONE 0x0001 /*< DCB is a clone */
#define DCBF_HUNG 0x0002 /*< Hangup has been dispatched */
#define DCBF_REPLIED 0x0004 /*< DCB was written to */
#define DCB_IS_CLONE(d) ((d)->flags & DCBF_CLONE)
#define DCB_REPLIED(d) ((d)->flags & DCBF_REPLIED)
#endif /* _DCB_H */

View File

@ -42,6 +42,8 @@ extern GWBUF *modutil_replace_SQL(GWBUF *, char *);
extern char *modutil_get_query(GWBUF* buf);
extern int modutil_send_mysql_err_packet(DCB *, int, int, int, const char *, const char *);
GWBUF* modutil_get_next_MySQL_packet(GWBUF** p_readbuf);
int modutil_MySQL_query_len(GWBUF* buf, int* nbytes_missing);
GWBUF *modutil_create_mysql_err_msg(
int packet_number,

View File

@ -91,6 +91,7 @@ typedef struct server {
long master_id; /**< Master server id of this node */
int depth; /**< Replication level in the tree */
long *slaves; /**< Slaves of this node */
bool master_err_is_logged; /*< If node failed, this indicates whether it is logged */
} SERVER;
/**
@ -123,8 +124,11 @@ typedef struct server {
* Is the server a master? The server must be both running and marked as master
* in order for the macro to return true
*/
#define SERVER_IS_MASTER(server) \
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER))
#define SERVER_IS_MASTER(server) SRV_MASTER_STATUS((server)->status)
#define SRV_MASTER_STATUS(status) ((status & \
(SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \
(SERVER_RUNNING|SERVER_MASTER))
/**
* Is the server valid candidate for root master. The server must be running,

View File

@ -99,6 +99,11 @@ typedef struct {
time_t last;
} SERVICE_REFRESH_RATE;
typedef struct server_ref_t{
struct server_ref_t *next;
SERVER* server;
}SERVER_REF;
/**
* Defines a service within the gateway.
*
@ -119,7 +124,7 @@ typedef struct service {
void *router_instance;
/**< The router instance for this service */
char *version_string;/** version string for this service listeners */
struct server *databases; /**< The set of servers in the backend */
SERVER_REF *dbref; /** server references */
SERVICE_USER credentials; /**< The cedentials of the service user */
SPINLOCK spin; /**< The service spinlock */
SERVICE_STATS stats; /**< The service statistics */
@ -130,6 +135,7 @@ typedef struct service {
CONFIG_PARAMETER*
svc_config_param; /*< list of config params and values */
int svc_config_version; /*< Version number of configuration */
bool svc_do_shutdown; /*< tells the service to exit loops etc. */
SPINLOCK
users_table_spin; /**< The spinlock for users data refresh */
SERVICE_REFRESH_RATE
@ -144,6 +150,8 @@ typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOS
#define SERVICE_STATE_ALLOC 1 /**< The service has been allocated */
#define SERVICE_STATE_STARTED 2 /**< The service has been started */
#define SERVICE_STATE_FAILED 3 /**< The service failed to start */
#define SERVICE_STATE_STOPPED 4 /**< The service has been stopped */
extern SERVICE *service_alloc(const char *, const char *);
extern int service_free(SERVICE *);
@ -184,4 +192,5 @@ extern void dprintService(DCB *, SERVICE *);
extern void dListServices(DCB *);
extern void dListListeners(DCB *);
char* service_get_name(SERVICE* svc);
void service_shutdown();
#endif

View File

@ -61,6 +61,7 @@ typedef enum {
SESSION_STATE_STOPPING, /*< session and router are being closed */
SESSION_STATE_LISTENER, /*< for listener session */
SESSION_STATE_LISTENER_STOPPED, /*< for listener session */
SESSION_STATE_TO_BE_FREED, /*< ready to be freed as soon as there are no references */
SESSION_STATE_FREE /*< for all sessions */
} session_state_t;
@ -124,6 +125,7 @@ typedef struct session {
UPSTREAM tail; /*< The tail of the filter chain */
struct session *next; /*< Linked list of all sessions */
int refcount; /*< Reference count on the session */
bool ses_is_child; /*< this is a child session */
#if defined(SS_DEBUG)
skygw_chk_t ses_chk_tail;
#endif

View File

@ -213,7 +213,7 @@ HINT_MODE mode = HM_EXECUTE;
/*
* If we have got here then we have a comment, ptr point to
* the comment character if it is a '#' comment or the second
* character of the comment if it is a -- or /* comment
* character of the comment if it is a -- or \/\* comment
*
* Move to the next character in the SQL.
*/

View File

@ -58,6 +58,10 @@
#include <service.h>
#include <router.h>
#include <dcb.h>
#include <sys/time.h>
#include <poll.h>
#include <mysql_client_server_protocol.h>
#include <housekeeper.h>
#define MYSQL_COM_QUIT 0x01
#define MYSQL_COM_INITDB 0x02
@ -69,6 +73,13 @@
#define MYSQL_COM_STMT_CLOSE 0x19
#define MYSQL_COM_STMT_RESET 0x1a
#define REPLY_TIMEOUT_SECOND 5
#define REPLY_TIMEOUT_MILLISECOND 1
#define PARENT 0
#define CHILD 1
#define PTR_IS_RESULTSET(b) (b[0] == 0x01 && b[1] == 0x0 && b[2] == 0x0 && b[3] == 0x01)
#define PTR_IS_EOF(b) (b[4] == 0xfe)
static unsigned char required_packets[] = {
MYSQL_COM_QUIT,
@ -104,19 +115,20 @@ static void *newSession(FILTER *instance, SESSION *session);
static void closeSession(FILTER *instance, void *session);
static void freeSession(FILTER *instance, void *session);
static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream);
static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream);
static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue);
static int clientReply(FILTER *instance, void *fsession, GWBUF *queue);
static void diagnostic(FILTER *instance, void *fsession, DCB *dcb);
static FILTER_OBJECT MyObject = {
createInstance,
newSession,
closeSession,
freeSession,
setDownstream,
NULL, // No Upstream requirement
setUpstream,
routeQuery,
NULL, // No client reply
clientReply,
diagnostic,
};
@ -144,15 +156,135 @@ typedef struct {
*/
typedef struct {
DOWNSTREAM down; /* The downstream filter */
UPSTREAM up; /* The upstream filter */
FILTER_DEF* dummy_filterdef;
int active; /* filter is active? */
bool waiting[2]; /* if the client is waiting for a reply */
int eof[2];
int replies[2]; /* Number of queries received */
DCB *branch_dcb; /* Client DCB for "branch" service */
SESSION *branch_session;/* The branch service session */
int n_duped; /* Number of duplicated queries */
int n_rejected; /* Number of rejected queries */
int residual; /* Any outstanding SQL text */
GWBUF* tee_replybuf; /* Buffer for reply */
SPINLOCK tee_lock;
} TEE_SESSION;
typedef struct orphan_session_tt
{
SESSION* session;
struct orphan_session_tt* next;
}orphan_session_t;
static orphan_session_t* allOrphans = NULL;
static SPINLOCK orphanLock;
static int packet_is_required(GWBUF *queue);
static int detect_loops(TEE_INSTANCE *instance, HASHTABLE* ht, SERVICE* session);
static int hkfn(
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 hcfn(
void* v1,
void* v2)
{
char* i1 = (char*) v1;
char* i2 = (char*) v2;
return strcmp(i1,i2);
}
static void
orphan_free(void* data)
{
spinlock_acquire(&orphanLock);
orphan_session_t *ptr = allOrphans, *finished = NULL, *tmp = NULL;
#ifdef SS_DEBUG
int o_stopping = 0, o_ready = 0, o_freed = 0;
#endif
while(ptr)
{
if(ptr->session->state == SESSION_STATE_TO_BE_FREED)
{
if(ptr == allOrphans)
{
tmp = ptr;
allOrphans = ptr->next;
}
else
{
tmp = allOrphans;
while(tmp && tmp->next != ptr)
tmp = tmp->next;
if(tmp)
{
tmp->next = ptr->next;
tmp = ptr;
}
}
}
#ifdef SS_DEBUG
else if(ptr->session->state == SESSION_STATE_STOPPING)
{
o_stopping++;
}
else if(ptr->session->state == SESSION_STATE_ROUTER_READY)
{
o_ready++;
}
#endif
ptr = ptr->next;
if(tmp)
{
tmp->next = finished;
finished = tmp;
tmp = NULL;
}
}
spinlock_release(&orphanLock);
#ifdef SS_DEBUG
if(o_stopping + o_ready > 0)
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans in "
"SESSION_STATE_STOPPING, %d orphans in "
"SESSION_STATE_ROUTER_READY. ", o_stopping, o_ready);
#endif
while(finished)
{
o_freed++;
tmp = finished;
finished = finished->next;
tmp->session->service->router->freeSession(
tmp->session->service->router_instance,
tmp->session->router_session);
tmp->session->state = SESSION_STATE_FREE;
free(tmp->session);
free(tmp);
}
#ifdef SS_DEBUG
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans freed.", o_freed);
#endif
}
/**
* Implementation of the mandatory version entry point
*
@ -171,6 +303,8 @@ version()
void
ModuleInit()
{
spinlock_init(&orphanLock);
hktask_add("tee orphan cleanup",orphan_free,NULL,15);
}
/**
@ -257,7 +391,8 @@ int i;
free(my_instance->source);
free(my_instance);
return NULL;
}
}
if (my_instance->match &&
regcomp(&my_instance->re, my_instance->match, REG_ICASE))
{
@ -313,12 +448,25 @@ char *remote, *userName;
my_session = NULL;
goto retblock;
}
HASHTABLE* ht = hashtable_alloc(100,hkfn,hcfn);
bool is_loop = detect_loops(my_instance,ht,session->service);
hashtable_free(ht);
if(is_loop)
{
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"Error : %s: Recursive use of tee filter in service.",
session->service->name)));
my_session = NULL;
goto retblock;
}
if ((my_session = calloc(1, sizeof(TEE_SESSION))) != NULL)
{
my_session->active = 1;
my_session->residual = 0;
spinlock_init(&my_session->tee_lock);
if (my_instance->source &&
(remote = session_get_remote(session)) != NULL)
{
@ -326,7 +474,7 @@ char *remote, *userName;
{
my_session->active = 0;
LOGIF(LE, (skygw_log_write(
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Warning : Tee filter is not active.")));
}
@ -339,7 +487,7 @@ char *remote, *userName;
{
my_session->active = 0;
LOGIF(LE, (skygw_log_write(
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Warning : Tee filter is not active.")));
}
@ -348,33 +496,88 @@ char *remote, *userName;
{
DCB* dcb;
SESSION* ses;
FILTER_DEF* dummy;
UPSTREAM* dummy_upstream;
if ((dcb = dcb_clone(session->client)) == NULL)
{
freeSession(my_instance, (void *)my_session);
freeSession(instance, (void *)my_session);
my_session = NULL;
LOGIF(LE, (skygw_log_write(
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Creating client DCB for Tee "
"filter failed. Terminating session.")));
goto retblock;
}
if((dummy = filter_alloc("tee_dummy","tee_dummy")) == NULL)
{
dcb_close(dcb);
freeSession(instance, (void *)my_session);
my_session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : tee: Allocating memory for "
"dummy filter definition failed."
" Terminating session.")));
goto retblock;
}
if ((ses = session_alloc(my_instance->service, dcb)) == NULL)
{
dcb_close(dcb);
freeSession(my_instance, (void *)my_session);
freeSession(instance, (void *)my_session);
my_session = NULL;
LOGIF(LE, (skygw_log_write(
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Creating client session for Tee "
"filter failed. Terminating session.")));
goto retblock;
}
my_session->branch_session = ses;
my_session->branch_dcb = dcb;
ss_dassert(ses->ses_is_child);
dummy->obj = GetModuleObject();
dummy->filter = NULL;
if((dummy_upstream = filterUpstream(
dummy, my_session, &ses->tail)) == NULL)
{
spinlock_acquire(&ses->ses_lock);
ses->state = SESSION_STATE_STOPPING;
spinlock_release(&ses->ses_lock);
ses->service->router->closeSession(
ses->service->router_instance,
ses->router_session);
ses->client = NULL;
dcb->session = NULL;
session_free(ses);
dcb_close(dcb);
freeSession(instance, (void *) my_session);
my_session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : tee: Allocating memory for"
"dummy upstream failed."
" Terminating session.")));
goto retblock;
}
ses->tail = *dummy_upstream;
my_session->branch_session = ses;
my_session->branch_dcb = dcb;
my_session->dummy_filterdef = dummy;
free(dummy_upstream);
}
}
retblock:
@ -421,6 +624,7 @@ SESSION *bsession;
* a side effect of closing the client DCB of the
* session.
*/
my_session->active = 0;
}
}
@ -435,11 +639,57 @@ static void
freeSession(FILTER *instance, void *session)
{
TEE_SESSION *my_session = (TEE_SESSION *)session;
SESSION* ses = my_session->branch_session;
if (ses != NULL)
{
if (ses->state == SESSION_STATE_ROUTER_READY)
{
session_free(ses);
}
if (ses->state == SESSION_STATE_TO_BE_FREED)
{
/** Free branch router session */
ses->service->router->freeSession(
ses->service->router_instance,
ses->router_session);
/** Free memory of branch client session */
ses->state = SESSION_STATE_FREE;
free(ses);
/** This indicates that branch session is not available anymore */
my_session->branch_session = NULL;
}
else if(ses->state == SESSION_STATE_STOPPING)
{
orphan_session_t* orphan;
if((orphan = malloc(sizeof(orphan_session_t))) == NULL)
{
skygw_log_write(LOGFILE_ERROR,"Error : Failed to "
"allocate memory for orphan session struct, "
"child session might leak memory.");
}else{
orphan->session = ses;
spinlock_acquire(&orphanLock);
orphan->next = allOrphans;
allOrphans = orphan;
spinlock_release(&orphanLock);
}
if(ses->refcount == 0)
{
ss_dassert(ses->refcount == 0 && ses->client == NULL);
ses->state = SESSION_STATE_TO_BE_FREED;
}
}
}
if (my_session->dummy_filterdef)
{
filter_free(my_session->dummy_filterdef);
}
free(session);
return;
}
/**
* Set the downstream filter or router to which queries will be
* passed from this filter.
@ -451,9 +701,23 @@ TEE_SESSION *my_session = (TEE_SESSION *)session;
static void
setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
{
TEE_SESSION *my_session = (TEE_SESSION *)session;
TEE_SESSION *my_session = (TEE_SESSION *) session;
my_session->down = *downstream;
}
my_session->down = *downstream;
/**
* Set the downstream filter or router to which queries will be
* passed from this filter.
*
* @param instance The filter instance data
* @param session The filter session
* @param downstream The downstream filter or router.
*/
static void
setUpstream(FILTER *instance, void *session, UPSTREAM *upstream)
{
TEE_SESSION *my_session = (TEE_SESSION *) session;
my_session->up = *upstream;
}
/**
@ -483,51 +747,187 @@ char *ptr;
int length, rval, residual = 0;
GWBUF *clone = NULL;
if (my_session->residual)
if (my_session->branch_session &&
my_session->branch_session->state == SESSION_STATE_ROUTER_READY)
{
clone = gwbuf_clone(queue);
if (my_session->residual < GWBUF_LENGTH(clone))
GWBUF_RTRIM(clone, GWBUF_LENGTH(clone) - residual);
my_session->residual -= GWBUF_LENGTH(clone);
if (my_session->residual < 0)
my_session->residual = 0;
}
else if ( my_session->active && (ptr = modutil_get_SQL(queue)) != NULL)
{
if ((my_instance->match == NULL ||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
(my_instance->nomatch == NULL ||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
if (my_session->residual)
{
char *dummy;
modutil_MySQL_Query(queue, &dummy, &length, &residual);
clone = gwbuf_clone(queue);
my_session->residual = residual;
clone = gwbuf_clone_all(queue);
if (my_session->residual < GWBUF_LENGTH(clone))
{
GWBUF_RTRIM(clone, GWBUF_LENGTH(clone) - residual);
}
my_session->residual -= GWBUF_LENGTH(clone);
if (my_session->residual < 0)
{
my_session->residual = 0;
}
}
else if (my_session->active && (ptr = modutil_get_SQL(queue)) != NULL)
{
if ((my_instance->match == NULL ||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
(my_instance->nomatch == NULL ||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
{
length = modutil_MySQL_query_len(queue, &residual);
clone = gwbuf_clone_all(queue);
my_session->residual = residual;
}
free(ptr);
}
else if (packet_is_required(queue))
{
clone = gwbuf_clone_all(queue);
}
free(ptr);
}
else if (packet_is_required(queue))
{
clone = gwbuf_clone(queue);
}
/* Pass the query downstream */
rval = my_session->down.routeQuery(my_session->down.instance,
my_session->down.session, queue);
ss_dassert(my_session->tee_replybuf == NULL);
memset(my_session->replies,0,2*sizeof(int));
memset(my_session->eof,0,2*sizeof(int));
memset(my_session->waiting,0,2*sizeof(bool));
rval = my_session->down.routeQuery(my_session->down.instance,
my_session->down.session,
queue);
if (clone)
{
my_session->n_duped++;
SESSION_ROUTE_QUERY(my_session->branch_session, clone);
if (my_session->branch_session->state == SESSION_STATE_ROUTER_READY)
{
SESSION_ROUTE_QUERY(my_session->branch_session, clone);
}
else
{
/** Close tee session */
my_session->active = 0;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Closed tee filter session.")));
gwbuf_free(clone);
}
}
else
{
if (my_session->active)
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Closed tee filter session.")));
my_session->active = 0;
}
my_session->n_rejected++;
}
return rval;
}
/**
* Scans the GWBUF for EOF packets. If two packets for this session have been found
* from either the parent or the child branch, mark the response set from that branch as over.
* @param session The Tee filter session
* @param branch Parent or child branch
* @param reply Buffer to scan
*/
void
scan_resultset(TEE_SESSION *session, int branch, GWBUF *reply)
{
unsigned char* ptr = (unsigned char*) reply->start;
unsigned char* end = (unsigned char*) reply->end;
int pktlen = 0;
while(ptr < end)
{
pktlen = gw_mysql_get_byte3(ptr) + 4;
if(PTR_IS_EOF(ptr))
{
session->eof[branch]++;
if(session->eof[branch] == 2)
{
session->waiting[branch] = false;
session->eof[branch] = 0;
return;
}
}
ptr += pktlen;
}
}
/**
* The clientReply entry point. This is passed the response buffer
* to which the filter should be applied. Once processed the
* query is passed to the upstream component
* (filter or router) in the filter chain.
*
* @param instance The filter instance data
* @param session The filter session
* @param reply The response data
*/
static int
clientReply (FILTER* instance, void *session, GWBUF *reply)
{
int rc, branch;
TEE_SESSION *my_session = (TEE_SESSION *) session;
spinlock_acquire(&my_session->tee_lock);
ss_dassert(my_session->active);
branch = instance == NULL ? CHILD : PARENT;
unsigned char *ptr = (unsigned char*)reply->start;
if(my_session->replies[branch] == 0)
{
if(PTR_IS_RESULTSET(ptr))
{
my_session->waiting[branch] = true;
my_session->eof[branch] = 0;
}
}
if(my_session->waiting[branch])
{
scan_resultset(my_session,branch,reply);
}
if(branch == PARENT)
{
ss_dassert(my_session->tee_replybuf == NULL)
my_session->tee_replybuf = reply;
}
else
{
gwbuf_free(reply);
}
my_session->replies[branch]++;
if(my_session->tee_replybuf != NULL &&
(my_session->branch_session == NULL ||
my_session->waiting[PARENT] ||
(!my_session->waiting[CHILD] && !my_session->waiting[PARENT])))
{
rc = my_session->up.clientReply (
my_session->up.instance,
my_session->up.session,
my_session->tee_replybuf);
my_session->tee_replybuf = NULL;
}
else
{
rc = 1;
}
spinlock_release(&my_session->tee_lock);
return rc;
}
/**
* Diagnostics routine
*
@ -589,3 +989,52 @@ int i;
return 1;
return 0;
}
/**
* Detects possible loops in the query cloning chain.
*/
int detect_loops(TEE_INSTANCE *instance,HASHTABLE* ht, SERVICE* service)
{
SERVICE* svc = service;
int i;
if(ht == NULL)
{
return -1;
}
if(hashtable_add(ht,(void*)service->name,(void*)true) == 0)
{
return true;
}
for(i = 0;i<svc->n_filters;i++)
{
if(strcmp(svc->filters[i]->module,"tee") == 0)
{
/*
* Found a Tee filter, recurse down its path
* if the service name isn't already in the hashtable.
*/
TEE_INSTANCE* ninst = (TEE_INSTANCE*)svc->filters[i]->filter;
if(ninst == NULL)
{
/**
* This tee instance hasn't been initialized yet and full
* resolution of recursion cannot be done now.
*/
continue;
}
SERVICE* tgt = ninst->service;
if(detect_loops((TEE_INSTANCE*)svc->filters[i]->filter,ht,tgt))
{
return true;
}
}
}
return false;
}

View File

@ -11,16 +11,26 @@ add_executable(harness harness_util.c harness_common.c ${CORE})
target_link_libraries(harness_ui fullcore log_manager utils)
target_link_libraries(harness fullcore)
execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testdriver.sh ${CMAKE_CURRENT_BINARY_DIR}/testdriver.sh @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.cnf ${CMAKE_CURRENT_BINARY_DIR}/hintfilter/hint_testing.cnf)
add_test(TestHintfilter testdriver.sh hintfilter/hint_testing.cnf hintfilter/hint_testing.input hintfilter/hint_testing.output hintfilter/hint_testing.expected)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.cnf ${CMAKE_CURRENT_BINARY_DIR}/regexfilter/regextest.cnf)
add_test(TestRegexfilter testdriver.sh regexfilter/regextest.cnf regexfilter/regextest.input regexfilter/regextest.output regexfilter/regextest.expected)
add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hintfilter/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.expected ")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest.cnf)
add_test(TestFwfilter1 testdriver.sh fwfilter/fwtest.cnf fwfilter/fwtest.input fwfilter/fwtest.output fwfilter/fwtest.expected)
add_test(TestFwfilter2 testdriver.sh fwfilter/fwtest.cnf fwfilter/fwtest2.input fwfilter/fwtest2.output fwfilter/fwtest2.expected)
add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regexfilter/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.expected ")
add_test(TestTeeRecursion ${CMAKE_CURRENT_SOURCE_DIR}/tee_recursion.sh
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
${TEST_USER}
${TEST_PASSWORD}
${TEST_HOST}
${TEST_PORT})
add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.expected ")
add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.expected ")
add_test(TestFwfilter3 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest3.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.expected ")
set_tests_properties(TestHintfilter TestRegexfilter TestFwfilter1 TestFwfilter2 TestTeeRecursion
PROPERTIES
ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/)

0
server/modules/filter/test/fwfilter/fwtest.cnf.in Normal file → Executable file
View File

0
server/modules/filter/test/fwfilter/fwtest.input Normal file → Executable file
View File

0
server/modules/filter/test/fwfilter/fwtest2.expected Normal file → Executable file
View File

0
server/modules/filter/test/fwfilter/fwtest2.input Normal file → Executable file
View File

View File

@ -10,7 +10,7 @@ int dcbfun(struct dcb* dcb, GWBUF * buffer)
int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
int i = 0;
int i = 0,rval = 0;
MYSQL_session* mysqlsess;
DCB* dcb;
char cwd[1024];
@ -60,7 +60,7 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
skygw_logmanager_init( 3, optstr);
free(optstr);
process_opts(argc,argv);
rval = process_opts(argc,argv);
if(!(instance.thrpool = malloc(instance.thrcount * sizeof(pthread_t)))){
printf("Error: Out of memory\n");
@ -72,10 +72,10 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
pthread_mutex_lock(&instance.work_mtx);
size_t thr_num = 1;
for(i = 0;i<instance.thrcount;i++){
pthread_create(&instance.thrpool[i],NULL,(void*)work_buffer,(void*)thr_num++);
rval |= pthread_create(&instance.thrpool[i],NULL,(void*)work_buffer,(void*)thr_num++);
}
return 0;
return rval;
}
void free_filters()
@ -543,10 +543,14 @@ int load_config( char* fname)
{
CONFIG* iter;
CONFIG_ITEM* item;
int config_ok = 1;
int config_ok = 1,inirval;
free_filters();
if(ini_parse(fname,handler,instance.conf) < 0){
if((inirval = ini_parse(fname,handler,instance.conf)) < 0){
printf("Error parsing configuration file!\n");
if(inirval == -1)
printf("Inih file open error.\n");
else if(inirval == -2)
printf("inih memory error.\n");
skygw_log_write(LOGFILE_ERROR,"Error parsing configuration file!\n");
config_ok = 0;
goto cleanup;
@ -991,7 +995,7 @@ GWBUF* gen_packet(PACKET pkt)
int process_opts(int argc, char** argv)
{
int fd, buffsize = 1024;
int rd,rdsz, rval = 0;
int rd,rdsz, rval = 0,error;
size_t fsize;
char *buff = calloc(buffsize,sizeof(char)), *tok = NULL;
@ -1071,6 +1075,7 @@ int process_opts(int argc, char** argv)
free(conf_name);
}
conf_name = strdup(optarg);
printf("Configuration: %s\n",optarg);
break;
case 'q':
@ -1079,12 +1084,12 @@ int process_opts(int argc, char** argv)
case 's':
instance.session_count = atoi(optarg);
printf("Sessions: %i ",instance.session_count);
printf("Sessions: %i\n",instance.session_count);
break;
case 't':
instance.thrcount = atoi(optarg);
printf("Threads: %i ",instance.thrcount);
printf("Threads: %i\n",instance.thrcount);
break;
case 'd':
@ -1121,7 +1126,7 @@ int process_opts(int argc, char** argv)
}
printf("\n");
if(conf_name && load_config(conf_name)){
if(conf_name && (error = load_config(conf_name))){
load_query();
}else{
instance.running = 0;
@ -1129,6 +1134,11 @@ int process_opts(int argc, char** argv)
free(conf_name);
close(fd);
if(!error)
{
rval = 1;
}
return rval;
}

View File

@ -0,0 +1,86 @@
#!/bin/bash
function execute_test()
{
RVAL=$(mysql --connect-timeout=5 -u $USER -p$PWD -h $HOST -P $PORT -e "select 1;"|grep -i error)
if [[ ! -e $MAXPID ]]
then
echo "Test failed: $MAXPID was not found."
return 1
fi
if [[ "$RVAL" != "" ]]
then
echo "Test failed: Query to backend didn't return an error."
return 1
fi
LAST_LOG=$(ls $BINDIR/log -1|grep err|sort|uniq|tail -n 1)
TEST_RESULT=$(cat $BINDIR/log/$LAST_LOG | grep -i recursive)
if [[ "$TEST_RESULT" != "" ]]
then
return 0
fi
echo "Test failed: Log file didn't mention tee recursion."
return 1
}
function reload_conf()
{
$BINDIR/bin/maxadmin --user=admin --password=skysql reload config
if [[ $? -ne 0 ]]
then
echo "Test failed: maxadmin returned a non-zero value."
return 1
fi
return 0
}
if [[ $# -lt 6 ]]
then
echo "usage: $0 <build dir> <source dir>"
exit 1
fi
BINDIR=$1
SRCDIR=$2
USER=$3
PWD=$4
HOST=$5
PORT=$6
CONF=$BINDIR/etc/MaxScale.cnf
OLDCONF=$BINDIR/etc/MaxScale.cnf.old
MAXPID=$BINDIR/log/$(ls -1 $BINDIR/log|grep maxscale)
TEST1=$SRCDIR/server/modules/filter/test/tee_recursion1.cnf
TEST2=$SRCDIR/server/modules/filter/test/tee_recursion2.cnf
$BINDIR/bin/maxadmin --user=admin --password=skysql flush logs
mv $CONF $OLDCONF
cp $TEST1 $CONF
reload_conf
execute_test
T1RVAL=$?
mv $CONF $CONF.test1
cp $TEST2 $CONF
reload_conf
execute_test
T2RVAL=$?
mv $CONF $CONF.test2
mv $OLDCONF $CONF
reload_conf
if [[ $T1RVAL -ne 0 ]]
then
echo "Test 1 failed."
exit 1
elif [[ $T2RVAL -ne 0 ]]
then
echo "Test 2 failed"
exit 1
else
echo "Test successful: log mentions recursive tee usage."
fi
exit 0

View File

@ -0,0 +1,114 @@
[maxscale]
threads=4
[MySQL Monitor]
type=monitor
module=mysqlmon
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
monitor_interval=10000
[RW Split Router]
type=service
router=readwritesplit
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
filters=recurse1
[RW Split Hint Router]
type=service
router=readwritesplit
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
filters=recurse2
[Read Connection Router]
type=service
router=readconnroute
router_options=master
servers=server1
user=maxuser
passwd=maxpwd
filters=recurse3
[recurse3]
type=filter
module=tee
service=RW Split Router
[recurse2]
type=filter
module=tee
service=Read Connection Router
[recurse1]
type=filter
module=tee
service=RW Split Hint Router
[Debug Interface]
type=service
router=debugcli
[CLI]
type=service
router=cli
[Read Connection Listener]
type=listener
service=Read Connection Router
protocol=MySQLClient
port=4008
[RW Split Listener]
type=listener
service=RW Split Router
protocol=MySQLClient
port=4006
[RW Split Hint Listener]
type=listener
service=RW Split Hint Router
protocol=MySQLClient
port=4009
[Debug Listener]
type=listener
service=Debug Interface
protocol=telnetd
port=4442
[CLI Listener]
type=listener
service=CLI
protocol=maxscaled
port=6603
[server1]
type=server
address=127.0.0.1
port=3000
protocol=MySQLBackend
[server2]
type=server
address=127.0.0.1
port=3001
protocol=MySQLBackend
[server3]
type=server
address=127.0.0.1
port=3002
protocol=MySQLBackend
[server4]
type=server
address=127.0.0.1
port=3003
protocol=MySQLBackend

View File

@ -0,0 +1,112 @@
[maxscale]
threads=4
[MySQL Monitor]
type=monitor
module=mysqlmon
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
monitor_interval=10000
[RW Split Router]
type=service
router=readwritesplit
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
filters=recurse1|recurse2
[RW Split Hint Router]
type=service
router=readwritesplit
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
[Read Connection Router]
type=service
router=readconnroute
router_options=master
servers=server1
user=maxuser
passwd=maxpwd
filters=recurse3
[recurse3]
type=filter
module=tee
service=RW Split Router
[recurse2]
type=filter
module=tee
service=Read Connection Router
[recurse1]
type=filter
module=tee
service=RW Split Hint Router
[Debug Interface]
type=service
router=debugcli
[CLI]
type=service
router=cli
[Read Connection Listener]
type=listener
service=Read Connection Router
protocol=MySQLClient
port=4008
[RW Split Listener]
type=listener
service=RW Split Router
protocol=MySQLClient
port=4006
[RW Split Hint Listener]
type=listener
service=RW Split Hint Router
protocol=MySQLClient
port=4009
[Debug Listener]
type=listener
service=Debug Interface
protocol=telnetd
port=4442
[CLI Listener]
type=listener
service=CLI
protocol=maxscaled
port=6603
[server1]
type=server
address=127.0.0.1
port=3000
protocol=MySQLBackend
[server2]
type=server
address=127.0.0.1
port=3001
protocol=MySQLBackend
[server3]
type=server
address=127.0.0.1
port=3002
protocol=MySQLBackend
[server4]
type=server
address=127.0.0.1
port=3003
protocol=MySQLBackend

View File

@ -0,0 +1,11 @@
#! /bin/bash
if [[ $# -lt 4 ]]
then
echo "Usage: $0 <config file> <input> <output> <expected>"
exit 1
fi
TESTDIR=@CMAKE_CURRENT_BINARY_DIR@
SRCDIR=@CMAKE_CURRENT_SOURCE_DIR@
$TESTDIR/harness -i $SRCDIR/$2 -o $TESTDIR/$3 -c $TESTDIR/$1 -t 1 -s 1 -e $SRCDIR/$4
exit $?

View File

@ -161,6 +161,7 @@ typedef struct router_slave {
int binlog_pos; /*< Binlog position for this slave */
char binlogfile[BINLOG_FNAMELEN+1];
/*< Current binlog file for this slave */
char *uuid; /*< Slave UUID */
BLFILE *file; /*< Currently open binlog file */
int serverid; /*< Server-id of the slave */
char *hostname; /*< Hostname of the slave, if known */
@ -227,6 +228,8 @@ typedef struct {
GWBUF *utf8; /*< Set NAMES utf8 */
GWBUF *select1; /*< select 1 */
GWBUF *selectver; /*< select version() */
GWBUF *selectvercom; /*< select @@version_comment */
GWBUF *selecthostname;/*< select @@hostname */
uint8_t *fde_event; /*< Format Description Event */
int fde_len; /*< Length of fde_event */
} MASTER_RESPONSES;
@ -300,16 +303,19 @@ typedef struct router_instance {
#define BLRM_UTF8 0x000C
#define BLRM_SELECT1 0x000D
#define BLRM_SELECTVER 0x000E
#define BLRM_REGISTER 0x000F
#define BLRM_BINLOGDUMP 0x0010
#define BLRM_SELECTVERCOM 0x000F
#define BLRM_SELECTHOSTNAME 0x0010
#define BLRM_REGISTER 0x0011
#define BLRM_BINLOGDUMP 0x0012
#define BLRM_MAXSTATE 0x0010
#define BLRM_MAXSTATE 0x0012
static char *blrm_states[] = { "Unconnected", "Connecting", "Authenticated", "Timestamp retrieval",
"Server ID retrieval", "HeartBeat Period setup", "binlog checksum config",
"binlog checksum rerieval", "GTID Mode retrieval", "Master UUID retrieval",
"Set Slave UUID", "Set Names latin1", "Set Names utf8", "select 1",
"select version()", "Register slave", "Binlog Dump" };
"select version()", "select @@version_comment", "select @@hostname",
"Register slave", "Binlog Dump" };
#define BLRS_CREATED 0x0000
#define BLRS_UNREGISTERED 0x0001
@ -338,6 +344,8 @@ static char *blrs_states[] = { "Created", "Unregistered", "Registered",
*/
#define COM_QUIT 0x01
#define COM_QUERY 0x03
#define COM_STATISTICS 0x09
#define COM_PING 0x0e
#define COM_REGISTER_SLAVE 0x15
#define COM_BINLOG_DUMP 0x12
@ -429,9 +437,9 @@ extern void blr_slave_rotate(ROUTER_SLAVE *slave, uint8_t *ptr);
extern int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large);
extern void blr_init_cache(ROUTER_INSTANCE *);
extern void blr_file_init(ROUTER_INSTANCE *);
extern void blr_write_binlog_record(ROUTER_INSTANCE *, REP_HEADER *,uint8_t *);
extern void blr_file_rotate(ROUTER_INSTANCE *, char *, uint64_t);
extern int blr_file_init(ROUTER_INSTANCE *);
extern int blr_write_binlog_record(ROUTER_INSTANCE *, REP_HEADER *,uint8_t *);
extern int blr_file_rotate(ROUTER_INSTANCE *, char *, uint64_t);
extern void blr_file_flush(ROUTER_INSTANCE *);
extern BLFILE *blr_open_binlog(ROUTER_INSTANCE *, char *);
extern GWBUF *blr_read_binlog(ROUTER_INSTANCE *, BLFILE *, unsigned int, REP_HEADER *);

View File

@ -111,9 +111,15 @@ typedef enum {
*
*/
typedef struct mysql_session {
#if defined(SS_DEBUG)
skygw_chk_t myses_chk_top;
#endif
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(passowrd) */
char user[MYSQL_USER_MAXLEN+1]; /*< username */
char db[MYSQL_DATABASE_MAXLEN+1]; /*< database */
#if defined(SS_DEBUG)
skygw_chk_t myses_chk_tail;
#endif
} MYSQL_session;
@ -303,11 +309,9 @@ typedef struct {
#endif /** _MYSQL_PROTOCOL_H */
void gw_mysql_close(MySQLProtocol **ptr);
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd);
void mysql_protocol_done (DCB* dcb);
MySQLProtocol *gw_mysql_init(MySQLProtocol *data);
void gw_mysql_close(MySQLProtocol **ptr);
int gw_receive_backend_auth(MySQLProtocol *protocol);
int gw_decode_mysql_server_handshake(MySQLProtocol *protocol, uint8_t *payload);
int gw_read_backend_handshake(MySQLProtocol *protocol);

View File

@ -323,8 +323,5 @@ typedef struct router_instance {
#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \
(SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED));
#define RSES_SESSION(r) (r->rses_backend_ref->bref_dcb->session)
#define RSES_CLIENT_DCB(r) (RSES_SESSION(r)->client)
#endif /*< _RWSPLITROUTER_H */

View File

@ -671,7 +671,27 @@ int log_no_master = 1;
if (mon_status_changed(ptr))
{
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
if (SRV_MASTER_STATUS(ptr->mon_prev_status))
{
/** Master failed, can't recover */
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Server %s:%d lost the master status.",
ptr->server->name,
ptr->server->port)));
}
/**
* Here we say: If the server's state changed
* so that it isn't running or some other way
* lost cluster membership, call call-back function
* of every DCB for which such callback was
* registered for this kind of issue (DCB_REASON_...)
*/
if (!(SERVER_IS_RUNNING(ptr->server)) ||
!(SERVER_IS_IN_CLUSTER(ptr->server)))
{
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
}
}
if (mon_status_changed(ptr))
@ -734,7 +754,13 @@ int log_no_master = 1;
{
if (! SERVER_IN_MAINT(ptr->server)) {
/* If "detect_stale_master" option is On, let's use the previus master */
if (detect_stale_master && root_master && (!strcmp(ptr->server->name, root_master->server->name) && ptr->server->port == root_master->server->port) && (ptr->server->status & SERVER_MASTER) && !(ptr->pending_status & SERVER_MASTER)) {
if (detect_stale_master &&
root_master &&
(!strcmp(ptr->server->name, root_master->server->name) &&
ptr->server->port == root_master->server->port) &&
(ptr->server->status & SERVER_MASTER) &&
!(ptr->pending_status & SERVER_MASTER))
{
/**
* In this case server->status will not be updated from pending_statu
* Set the STALE bit for this server in server struct
@ -744,55 +770,71 @@ int log_no_master = 1;
/* log it once */
if (mon_status_changed(ptr)) {
LOGIF(LM, (skygw_log_write_flush(
LOGFILE_MESSAGE, "[mysql_mon]: root server [%s:%i] is no longer Master,"
" let's use it again even if it could be a stale master,"
" you have been warned!",
ptr->server->name,
ptr->server->port)));
LOGFILE_MESSAGE,
"[mysql_mon]: root server "
"[%s:%i] is no longer Master,"
" let's use it again even "
" if it could be a stale master,"
" you have been warned!",
ptr->server->name,
ptr->server->port)));
}
} else {
ptr->server->status = ptr->pending_status;
}
}
ptr = ptr->next;
}
/* log master detection failure od first master becomes available after failure */
if (root_master && mon_status_changed(root_master) && !(root_master->server->status & SERVER_STALE_STATUS)) {
if (root_master &&
mon_status_changed(root_master) &&
!(root_master->server->status & SERVER_STALE_STATUS))
{
if (root_master->pending_status & (SERVER_MASTER)) {
if (!(root_master->mon_prev_status & SERVER_STALE_STATUS) && !(root_master->server->status & SERVER_MAINT)) {
if (!(root_master->mon_prev_status & SERVER_STALE_STATUS) &&
!(root_master->server->status & SERVER_MAINT))
{
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Info: A Master Server is now available: %s:%i",
"Info : A Master Server is now available: %s:%i",
root_master->server->name,
root_master->server->port)));
}
} else {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: No Master can be determined. Last known was %s:%i",
"Error : No Master can be determined. Last known was %s:%i",
root_master->server->name,
root_master->server->port)));
}
log_no_master = 1;
} else {
if (!root_master && log_no_master) {
if (!root_master && log_no_master)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: No Master can be determined")));
"Error : No Master can be determined")));
log_no_master = 0;
}
}
/* Do now the heartbeat replication set/get for MySQL Replication Consistency */
if (replication_heartbeat && root_master && (SERVER_IS_MASTER(root_master->server) || SERVER_IS_RELAY_SERVER(root_master->server))) {
if (replication_heartbeat &&
root_master &&
(SERVER_IS_MASTER(root_master->server) ||
SERVER_IS_RELAY_SERVER(root_master->server)))
{
set_master_heartbeat(handle, root_master);
ptr = handle->databases;
while (ptr) {
if( (! SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server))
{
if (ptr->server->node_id != root_master->server->node_id && (SERVER_IS_SLAVE(ptr->server) || SERVER_IS_RELAY_SERVER(ptr->server))) {
if (ptr->server->node_id != root_master->server->node_id &&
(SERVER_IS_SLAVE(ptr->server) ||
SERVER_IS_RELAY_SERVER(ptr->server)))
{
set_slave_heartbeat(handle, ptr);
}
}

View File

@ -510,6 +510,16 @@ static int gw_read_backend_event(DCB *dcb) {
if (nbytes_read < 5) /*< read at least command type */
{
rc = 0;
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"%p [gw_read_backend_event] Read %d bytes "
"from DCB %p, fd %d, session %s. "
"Returning to poll wait.\n",
pthread_self(),
nbytes_read,
dcb,
dcb->fd,
dcb->session)));
goto return_rc;
}
/** There is at least length and command type. */
@ -699,16 +709,6 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
switch (backend_protocol->protocol_auth_state) {
case MYSQL_HANDSHAKE_FAILED:
case MYSQL_AUTH_FAILED:
{
size_t len;
char* str;
uint8_t* packet = (uint8_t *)queue->start;
uint8_t* startpoint;
len = (size_t)MYSQL_GET_PACKET_LEN(packet);
startpoint = &packet[5];
str = (char *)malloc(len+1);
snprintf(str, len+1, "%s", startpoint);
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Unable to write to backend due to "
@ -717,13 +717,11 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
while ((queue = gwbuf_consume(
queue,
GWBUF_LENGTH(queue))) != NULL);
free(str);
rc = 0;
spinlock_release(&dcb->authlock);
goto return_rc;
break;
}
case MYSQL_IDLE:
{
uint8_t* ptr = GWBUF_DATA(queue);
@ -1165,22 +1163,37 @@ gw_backend_close(DCB *dcb)
mysql_send_com_quit(dcb, 0, quitbuf);
mysql_protocol_done(dcb);
/**
* The lock is needed only to protect the read of session->state and
* session->client values. Client's state may change by other thread
* but client's close and adding client's DCB to zombies list is executed
* only if client's DCB's state does _not_ change in parallel.
*/
spinlock_acquire(&session->ses_lock);
/**
* If session->state is STOPPING, start closing client session.
* Otherwise only this backend connection is closed.
*/
if (session != NULL && session->state == SESSION_STATE_STOPPING)
{
client_dcb = session->client;
if (client_dcb != NULL &&
client_dcb->state == DCB_STATE_POLLING)
if (session != NULL &&
session->state == SESSION_STATE_STOPPING &&
session->client != NULL)
{
if (session->client->state == DCB_STATE_POLLING)
{
spinlock_release(&session->ses_lock);
/** Close client DCB */
dcb_close(client_dcb);
dcb_close(session->client);
}
else
{
spinlock_release(&session->ses_lock);
}
}
else
{
spinlock_release(&session->ses_lock);
}
return 1;
}
@ -1495,7 +1508,7 @@ static GWBUF* process_response_data (
/** Get command which was stored in gw_MySQLWrite_backend */
p = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(p);
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
/** All buffers processed here are sescmd responses */
gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE);
@ -1510,7 +1523,14 @@ static GWBUF* process_response_data (
bool succp;
srvcmd = protocol_get_srv_command(p, false);
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [process_response_data] Read command %s for DCB %p fd %d.",
pthread_self(),
STRPACKETTYPE(srvcmd),
dcb,
dcb->fd)));
/**
* Read values from protocol structure, fails if values are
* uninitialized.
@ -1582,7 +1602,7 @@ static GWBUF* process_response_data (
if (nbytes_left == 0)
{
/** No more packets in this response */
if (npackets_left == 0)
if (npackets_left == 0 && outbuf != NULL)
{
GWBUF* b = outbuf;
@ -1622,7 +1642,7 @@ static bool sescmd_response_complete(
bool succp;
p = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(p);
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
protocol_get_response_status(p, &npackets_left, &nbytes_left);

View File

@ -377,15 +377,18 @@ MySQLSendHandshake(DCB* dcb)
*
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue
*
* The useful data: user, db, client_sha1 are copied into the MYSQL_session * dcb->session->data
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
* the dcb->data and later to dcb->session->data.
*
* client_capabilitiesa are copied into the dcb->protocol
*
* @param dcb Descriptor Control Block of the client
* @param queue The GWBUF with data from client
* @return 0 If succeed, otherwise non-zero value
*
* @note in case of failure, dcb->data is freed before returning. If succeed,
* dcb->data is freed in session.c:session_free.
*/
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
MySQLProtocol *protocol = NULL;
/* int compress = -1; */
@ -405,6 +408,13 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session));
#if defined(SS_DEBUG)
client_data->myses_chk_top = CHK_NUM_MYSQLSES;
client_data->myses_chk_tail = CHK_NUM_MYSQLSES;
#endif
/**
* Assign authentication structure with client DCB.
*/
dcb->data = client_data;
stage1_hash = client_data->client_sha1;
@ -425,7 +435,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
*/
/* Detect now if there are enough bytes to continue */
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) {
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
{
return 1;
}
@ -618,7 +629,7 @@ int gw_read_client_event(
* Now there should be at least one complete mysql packet in read_buffer.
*/
switch (protocol->protocol_auth_state) {
case MYSQL_AUTH_SENT:
{
int auth_val;
@ -657,8 +668,8 @@ int gw_read_client_event(
"%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_FAILED.",
protocol->owner_dcb->fd,
pthread_self())));
pthread_self(),
protocol->owner_dcb->fd)));
/** Send ERR 1045 to client */
mysql_send_auth_error(
@ -670,7 +681,7 @@ int gw_read_client_event(
dcb_close(dcb);
}
}
else
else
{
char* fail_str = NULL;
@ -703,7 +714,15 @@ int gw_read_client_event(
"state = MYSQL_AUTH_FAILED.",
protocol->owner_dcb->fd,
pthread_self())));
/**
* Release MYSQL_session since it is not used anymore.
*/
if (!DCB_IS_CLONE(dcb))
{
free(dcb->data);
}
dcb->data = NULL;
dcb_close(dcb);
}
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
@ -1395,15 +1414,13 @@ gw_client_close(DCB *dcb)
dcb->state == DCB_STATE_NOPOLLING ||
dcb->state == DCB_STATE_ZOMBIE)
{
CHK_PROTOCOL(protocol);
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(protocol);
}
#endif
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"%lu [gw_client_close]",
pthread_self())));
mysql_protocol_done(dcb);
mysql_protocol_done(dcb);
session = dcb->session;
/**
* session may be NULL if session_alloc failed.

View File

@ -151,38 +151,6 @@ retblock:
}
/**
* gw_mysql_close
*
* close a connection if opened
* free data scructure for MySQLProtocol
*
* @param ptr The MySQLProtocol ** to close/free
*
*/
void gw_mysql_close(MySQLProtocol **ptr) {
MySQLProtocol *conn = *ptr;
ss_dassert(*ptr != NULL);
if (*ptr == NULL)
return;
if (conn->fd > 0) {
/* COM_QUIT will not be sent here, but from the caller of this routine! */
close(conn->fd);
} else {
// no socket here
}
free(*ptr);
*ptr = NULL;
}
/**
* Read the backend server MySQL handshake
*
@ -573,6 +541,16 @@ int gw_send_authentication_to_backend(
uint8_t *curr_passwd = NULL;
unsigned int charset;
/**
* If session is stopping return with error.
*/
if (conn->owner_dcb->session == NULL ||
(conn->owner_dcb->session->state != SESSION_STATE_READY &&
conn->owner_dcb->session->state != SESSION_STATE_ROUTER_READY))
{
return 1;
}
if (strlen(dbname))
curr_db = dbname;
@ -1659,7 +1637,9 @@ mysql_send_auth_error (
* Buffer contains at least one of the following:
* complete [complete] [partial] mysql packet
*
* return pointer to gwbuf containing a complete packet or
* @param p_readbuf Address of read buffer pointer
*
* @return pointer to gwbuf containing a complete packet or
* NULL if no complete packet was found.
*/
GWBUF* gw_MySQL_get_next_packet(

View File

@ -144,7 +144,7 @@ static ROUTER *
GHACreateInstance(SERVICE *service, char **options)
{
ROUTER_INSTANCE *inst;
SERVER *server;
SERVER_REF *server;
int i, n;
if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) {
@ -159,7 +159,7 @@ int i, n;
* that we can maintain a count of the number of connections to each
* backend server.
*/
for (server = service->databases, n = 0; server; server = server->nextdb)
for (server = service->dbref, n = 0; server; server = server->next)
n++;
inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *));
@ -169,7 +169,7 @@ int i, n;
return NULL;
}
for (server = service->databases, n = 0; server; server = server->nextdb)
for (server = service->dbref, n = 0; server; server = server->next)
{
if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL)
{
@ -179,7 +179,7 @@ int i, n;
free(inst);
return NULL;
}
inst->servers[n]->server = server;
inst->servers[n]->server = server->server;
inst->servers[n]->current_connection_count = 0;
n++;
}

View File

@ -47,7 +47,7 @@ CLIOBJ=$(CLISRCS:.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
MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so libbinlogrouter.so
all: $(MODULES)
@ -68,12 +68,16 @@ libcli.so: $(CLIOBJ)
libreadwritesplit.so:
(cd readwritesplit; touch depend.mk ; make; cp $@ ..)
libbinlogrouter.so:
(cd binlog; touch depend.mk ; make; cp $@ ..)
.c.o:
$(CC) $(CFLAGS) $< -o $@
clean:
$(DEL) $(OBJ) $(MODULES)
(cd readwritesplit; touch depend.mk; make clean)
(cd binlog; touch depend.mk; make clean)
tags:
ctags $(SRCS) $(HDRS)
@ -83,10 +87,12 @@ depend:
@$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk
(cd readwritesplit; touch depend.mk ; make depend)
(cd binlog; touch depend.mk ; make depend)
install: $(MODULES)
install -D $(MODULES) $(DEST)/modules
(cd readwritesplit; make DEST=$(DEST) install)
(cd binlog; make DEST=$(DEST) install)
cleantests:
$(MAKE) -C test cleantests

View File

@ -201,7 +201,7 @@ int i;
* which of these servers is currently the master and replicate from
* that server.
*/
if (service->databases == NULL || service->databases->nextdb != NULL)
if (service->dbref == NULL || service->dbref->next != NULL)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
@ -332,16 +332,6 @@ int i;
inst->fileroot = strdup(BINLOG_NAME_ROOT);
}
/*
* We have completed the creation of the instance data, so now
* insert this router instance into the linked list of routers
* that have been created with this module.
*/
spinlock_acquire(&instlock);
inst->next = instances;
instances = inst;
spinlock_release(&instlock);
inst->active_logs = 0;
inst->reconnect_pending = 0;
inst->handling_threads = 0;
@ -353,12 +343,31 @@ int i;
/*
* Initialise the binlog file and position
*/
blr_file_init(inst);
if (blr_file_init(inst) == 0)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"%s: Service not started due to lack of binlog directory.",
service->name)));
free(inst);
return NULL;
}
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Binlog router: current binlog file is: %s, current position %u\n",
inst->binlog_name, inst->binlog_position)));
/*
* We have completed the creation of the instance data, so now
* insert this router instance into the linked list of routers
* that have been created with this module.
*/
spinlock_acquire(&instlock);
inst->next = instances;
instances = inst;
spinlock_release(&instlock);
/*
* Initialise the binlog cache for this router instance
*/
@ -423,6 +432,8 @@ ROUTER_SLAVE *slave;
slave->cstate = 0;
slave->pthread = 0;
slave->overrun = 0;
slave->uuid = NULL;
slave->hostname = NULL;
spinlock_init(&slave->catch_lock);
slave->dcb = session->client;
slave->router = inst;
@ -532,7 +543,7 @@ ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Binlog router close session with master server %s",
router->service->databases->unique_name)));
router->service->dbref->server->unique_name)));
blr_master_reconnect(router);
return;
}
@ -777,8 +788,10 @@ struct tm tm;
session->serverid);
if (session->hostname)
dcb_printf(dcb, "\t\tHostname: %s\n", session->hostname);
if (session->uuid)
dcb_printf(dcb, "\t\tSlave UUID: %s\n", session->uuid);
dcb_printf(dcb,
"\t\tSlave: %d\n",
"\t\tSlave: %s\n",
session->dcb->remote);
dcb_printf(dcb,
"\t\tSlave DCB: %p\n",
@ -1034,3 +1047,144 @@ ROUTER_SLAVE *slave;
}
spinlock_release(&router->lock);
}
/**
* Return some basic statistics from the router in response to a COM_STATISTICS
* request.
*
* @param router The router instance
* @param slave The "slave" connection that requested the statistics
* @param queue The statistics request
*
* @return non-zero on sucessful send
*/
int
blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
char result[1000], *ptr;
GWBUF *ret;
int len;
snprintf(result, 1000,
"Uptime: %u Threads: %u Events: %u Slaves: %u Master State: %s",
time(0) - router->connect_time,
config_threadcount(),
router->stats.n_binlogs_ses,
router->stats.n_slaves,
blrm_states[router->master_state]);
if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL)
return 0;
len = strlen(result);
ptr = GWBUF_DATA(ret);
*ptr++ = len & 0xff;
*ptr++ = (len & 0xff00) >> 8;
*ptr++ = (len & 0xff0000) >> 16;
*ptr++ = 1;
strncpy(ptr, result, len);
return slave->dcb->func.write(slave->dcb, ret);
}
int
blr_ping(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
char *ptr;
GWBUF *ret;
int len;
if ((ret = gwbuf_alloc(5)) == NULL)
return 0;
ptr = GWBUF_DATA(ret);
*ptr++ = 0x01;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1;
*ptr = 0; // OK
return slave->dcb->func.write(slave->dcb, ret);
}
/**
* mysql_send_custom_error
*
* Send a MySQL protocol Generic ERR message, to the dcb
* Note the errno and state are still fixed now
*
* @param dcb Owner_Dcb Control Block for the connection to which the OK is sent
* @param packet_number
* @param in_affected_rows
* @param msg
* @return 1 Non-zero if data was sent
*
*/
int
blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg)
{
uint8_t *outbuf = NULL;
uint32_t mysql_payload_size = 0;
uint8_t mysql_packet_header[4];
uint8_t *mysql_payload = NULL;
uint8_t field_count = 0;
uint8_t mysql_err[2];
uint8_t mysql_statemsg[6];
unsigned int mysql_errno = 0;
const char *mysql_error_msg = NULL;
const char *mysql_state = NULL;
GWBUF *errbuf = NULL;
mysql_errno = 2003;
mysql_error_msg = "An errorr occurred ...";
mysql_state = "HY000";
field_count = 0xff;
gw_mysql_set_byte2(mysql_err, mysql_errno);
mysql_statemsg[0]='#';
memcpy(mysql_statemsg+1, mysql_state, 5);
if (msg != NULL) {
mysql_error_msg = msg;
}
mysql_payload_size = sizeof(field_count) +
sizeof(mysql_err) +
sizeof(mysql_statemsg) +
strlen(mysql_error_msg);
/** allocate memory for packet header + payload */
errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
ss_dassert(errbuf != NULL);
if (errbuf == NULL)
{
return 0;
}
outbuf = GWBUF_DATA(errbuf);
/** write packet header and packet number */
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
mysql_packet_header[3] = packet_number;
/** write header */
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
mysql_payload = outbuf + sizeof(mysql_packet_header);
/** write field */
memcpy(mysql_payload, &field_count, sizeof(field_count));
mysql_payload = mysql_payload + sizeof(field_count);
/** write errno */
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
mysql_payload = mysql_payload + sizeof(mysql_err);
/** write sqlstate */
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
/** write error message */
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
return dcb->func.write(dcb, errbuf);
}

View File

@ -55,7 +55,7 @@ extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
static void blr_file_create(ROUTER_INSTANCE *router, char *file);
static int blr_file_create(ROUTER_INSTANCE *router, char *file);
static void blr_file_append(ROUTER_INSTANCE *router, char *file);
static uint32_t extract_field(uint8_t *src, int bits);
static void blr_log_header(logfile_id_t file, char *msg, uint8_t *ptr);
@ -68,7 +68,7 @@ static void blr_log_header(logfile_id_t file, char *msg, uint8_t *ptr);
*
* @param router The router instance this defines the master for this replication chain
*/
void
int
blr_file_init(ROUTER_INSTANCE *router)
{
char *ptr, path[1024], filename[1050];
@ -92,16 +92,28 @@ struct dirent *dp;
router->binlogdir = strdup(path);
}
else
{
strncpy(path, router->binlogdir, 1024);
}
if (access(router->binlogdir, R_OK) == -1)
{
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"%s: Unable to read the binlog directory %s.",
router->service->name, router->binlogdir)));
return 0;
}
/* First try to find a binlog file number by reading the directory */
root_len = strlen(router->fileroot);
dirp = opendir(path);
if ((dirp = opendir(path)) == NULL)
{
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"%s: Unable to read the binlog directory %s, %s.",
router->service->name, router->binlogdir,
strerror(errno))));
return 0;
}
while ((dp = readdir(dirp)) != NULL)
{
if (strncmp(dp->d_name, router->fileroot, root_len) == 0)
@ -134,20 +146,21 @@ struct dirent *dp;
router->initbinlog);
else
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, 1);
blr_file_create(router, filename);
if (! blr_file_create(router, filename))
return 0;
}
else
{
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, n);
blr_file_append(router, filename);
}
return 1;
}
void
int
blr_file_rotate(ROUTER_INSTANCE *router, char *file, uint64_t pos)
{
blr_file_create(router, file);
return blr_file_create(router, file);
}
@ -156,8 +169,9 @@ blr_file_rotate(ROUTER_INSTANCE *router, char *file, uint64_t pos)
*
* @param router The router instance
* @param file The binlog file name
* @return Non-zero if the fie creation succeeded
*/
static void
static int
blr_file_create(ROUTER_INSTANCE *router, char *file)
{
char path[1024];
@ -175,7 +189,9 @@ unsigned char magic[] = BINLOG_MAGIC;
else
{
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"Failed to create binlog file %s", path)));
"%s: Failed to create binlog file %s, %s.",
router->service->name, path, strerror(errno))));
return 0;
}
fsync(fd);
close(router->binlog_fd);
@ -184,6 +200,7 @@ unsigned char magic[] = BINLOG_MAGIC;
router->binlog_position = 4; /* Initial position after the magic number */
spinlock_release(&router->binlog_lock);
router->binlog_fd = fd;
return 1;
}
@ -225,15 +242,31 @@ int fd;
* @param router The router instance
* @param buf The binlog record
* @param len The length of the binlog record
* @return Return the number of bytes written
*/
void
int
blr_write_binlog_record(ROUTER_INSTANCE *router, REP_HEADER *hdr, uint8_t *buf)
{
pwrite(router->binlog_fd, buf, hdr->event_size, hdr->next_pos - hdr->event_size);
int n;
if ((n = pwrite(router->binlog_fd, buf, hdr->event_size,
hdr->next_pos - hdr->event_size)) != hdr->event_size)
{
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"%s: Failed to write binlog record at %d of %s, %s. "
"Truncating to previous record.",
router->service->name, hdr->next_pos - hdr->event_size,
router->binlog_name,
strerror(errno))));
/* Remove any partual event that was written */
ftruncate(router->binlog_fd, hdr->next_pos - hdr->event_size);
return 0;
}
spinlock_acquire(&router->binlog_lock);
router->binlog_position = hdr->next_pos;
router->last_written = hdr->next_pos - hdr->event_size;
spinlock_release(&router->binlog_lock);
return n;
}
/**

View File

@ -71,13 +71,13 @@ static GWBUF *blr_make_registration(ROUTER_INSTANCE *router);
static GWBUF *blr_make_binlog_dump(ROUTER_INSTANCE *router);
void encode_value(unsigned char *data, unsigned int value, int len);
void blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt);
static void blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *pkt, REP_HEADER *hdr);
static int blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *pkt, REP_HEADER *hdr);
void blr_distribute_binlog_record(ROUTER_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
static void *CreateMySQLAuthData(char *username, char *password, char *database);
void blr_extract_header(uint8_t *pkt, REP_HEADER *hdr);
inline uint32_t extract_field(uint8_t *src, int bits);
static void blr_log_packet(logfile_id_t file, char *msg, uint8_t *ptr, int len);
static void blr_master_close(ROUTER_INSTANCE *);
static int keepalive = 1;
/**
@ -121,7 +121,7 @@ GWBUF *buf;
return;
}
client->session = router->session;
if ((router->master = dcb_connect(router->service->databases, router->session, BLR_PROTOCOL)) == NULL)
if ((router->master = dcb_connect(router->service->dbref->server, router->session, BLR_PROTOCOL)) == NULL)
{
char *name;
if ((name = malloc(strlen(router->service->name)
@ -135,10 +135,10 @@ GWBUF *buf;
router->retry_backoff = BLR_MAX_BACKOFF;
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"Binlog router: failed to connect to master server '%s'",
router->service->databases->unique_name)));
router->service->dbref->server->unique_name)));
return;
}
router->master->remote = strdup(router->service->databases->name);
router->master->remote = strdup(router->service->dbref->server->name);
LOGIF(LM,(skygw_log_write(
LOGFILE_MESSAGE,
"%s: atempting to connect to master server %s.",
@ -247,6 +247,37 @@ int do_reconnect = 0;
}
}
/**
* Shutdown a connection to the master
*
* @param router The router instance
*/
void
blr_master_close(ROUTER_INSTANCE *router)
{
dcb_close(router->master);
router->master_state = BLRM_UNCONNECTED;
}
/**
* Mark this master connection for a delayed reconnect, used during
* error recovery to cause a reconnect after 60 seconds.
*
* @param router The router instance
*/
void
blr_master_delayed_connect(ROUTER_INSTANCE *router)
{
char *name;
if ((name = malloc(strlen(router->service->name)
+ strlen(" Master Recovery")+1)) != NULL);
{
sprintf(name, "%s Master Recovery", router->service->name);
hktask_oneshot(name, blr_start_master, router, 60);
}
}
/**
* Binlog router master side state machine event handler.
*
@ -407,6 +438,20 @@ char query[128];
case BLRM_SELECTVER:
// Response to SELECT VERSION should be stored
router->saved_master.selectver = buf;
buf = blr_make_query("SELECT @@version_comment limit 1;");
router->master_state = BLRM_SELECTVERCOM;
router->master->func.write(router->master, buf);
break;
case BLRM_SELECTVERCOM:
// Response to SELECT @@version_comment should be stored
router->saved_master.selectvercom = buf;
buf = blr_make_query("SELECT @@hostname;");
router->master_state = BLRM_SELECTHOSTNAME;
router->master->func.write(router->master, buf);
break;
case BLRM_SELECTHOSTNAME:
// Response to SELECT @@hostname should be stored
router->saved_master.selecthostname = buf;
buf = blr_make_registration(router);
router->master_state = BLRM_REGISTER;
router->master->func.write(router->master, buf);
@ -809,10 +854,36 @@ static REP_HEADER phdr;
// into the binlog file
if (hdr.event_type == ROTATE_EVENT)
router->rotating = 1;
blr_write_binlog_record(router, &hdr, ptr);
if (blr_write_binlog_record(router, &hdr, ptr) == 0)
{
/*
* Failed to write to the
* binlog file, destroy the
* buffer chain and close the
* connection with the master
*/
while ((pkt = gwbuf_consume(pkt,
GWBUF_LENGTH(pkt))) != NULL);
blr_master_close(router);
blr_master_delayed_connect(router);
return;
}
if (hdr.event_type == ROTATE_EVENT)
{
blr_rotate_event(router, ptr, &hdr);
if (!blr_rotate_event(router, ptr, &hdr))
{
/*
* Failed to write to the
* binlog file, destroy the
* buffer chain and close the
* connection with the master
*/
while ((pkt = gwbuf_consume(pkt,
GWBUF_LENGTH(pkt))) != NULL);
blr_master_close(router);
blr_master_delayed_connect(router);
return;
}
}
blr_distribute_binlog_record(router, &hdr, ptr);
}
@ -833,7 +904,20 @@ static REP_HEADER phdr;
if (hdr.event_type == ROTATE_EVENT)
{
router->rotating = 1;
blr_rotate_event(router, ptr, &hdr);
if (!blr_rotate_event(router, ptr, &hdr))
{
/*
* Failed to write to the
* binlog file, destroy the
* buffer chain and close the
* connection with the master
*/
while ((pkt = gwbuf_consume(pkt,
GWBUF_LENGTH(pkt))) != NULL);
blr_master_close(router);
blr_master_delayed_connect(router);
return;
}
}
}
}
@ -933,7 +1017,7 @@ register uint32_t rval = 0, shift = 0;
* @param ptr The packet containing the rotate event
* @param hdr The replication message header
*/
static void
static int
blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *ptr, REP_HEADER *hdr)
{
int len, slen;
@ -963,9 +1047,14 @@ char file[BINLOG_FNAMELEN+1];
if (strncmp(router->binlog_name, file, slen) != 0)
{
router->stats.n_rotates++;
blr_file_rotate(router, file, pos);
if (blr_file_rotate(router, file, pos) == 0)
{
router->rotating = 0;
return 0;
}
}
router->rotating = 0;
return 1;
}
/**

View File

@ -111,12 +111,20 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
case COM_BINLOG_DUMP:
return blr_slave_binlog_dump(router, slave, queue);
break;
case COM_STATISTICS:
return blr_statistics(router, slave, queue);
break;
case COM_PING:
return blr_ping(router, slave, queue);
break;
case COM_QUIT:
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"COM_QUIT received from slave with server_id %d",
slave->serverid)));
break;
default:
blr_send_custom_error(slave->dcb, 1, 0,
"MySQL command not supported by the binlog router.");
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Unexpected MySQL Command (%d) received from slave",
@ -133,12 +141,14 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
* when MaxScale registered as a slave. The exception to the rule is the
* request to obtain the current timestamp value of the server.
*
* Five select statements are currently supported:
* Seven select statements are currently supported:
* SELECT UNIX_TIMESTAMP();
* SELECT @master_binlog_checksum
* SELECT @@GLOBAL.GTID_MODE
* SELECT VERSION()
* SELECT 1
* SELECT @@version_comment limit 1
* SELECT @@hostname
*
* Two show commands are supported:
* SHOW VARIABLES LIKE 'SERVER_ID'
@ -208,6 +218,16 @@ int query_len;
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.selectver);
}
else if (strcasecmp(word, "@@version_comment") == 0)
{
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.selectvercom);
}
else if (strcasecmp(word, "@@hostname") == 0)
{
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.selecthostname);
}
}
else if (strcasecmp(word, "SHOW") == 0)
{
@ -251,6 +271,8 @@ int query_len;
}
else if (strcasecmp(word, "@slave_uuid") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
slave->uuid = strdup(word);
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
}
@ -1075,3 +1097,81 @@ uint32_t chksum;
encode_value(ptr, chksum, 32);
slave->dcb->func.write(slave->dcb, head);
}
/**
* Send the field count packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param count Number of columns in the result set
* @return Non-zero on success
*/
static int
blr_slave_send_fieldcount(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int count)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(5)) == NULL)
return 0;
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x01; // Sequence number in response
*ptr++ = count; // Length of result string
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send the column definition packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *name, int type, int len, uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL)
return 0;
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 22 + strlen(name), 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = 0; // Schema name length
*ptr++ = 0; // virtal table name length
*ptr++ = 0; // Table name length
*ptr++ = strlen(name); // Column name length;
while (*name)
*ptr++ = *name++; // Copy the column name
*ptr++ = 0; // Orginal column name
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
*ptr++ = 0x1f;
else
*ptr++ = 0x00;
*ptr++= 0;
*ptr++= 0;
*ptr++= 0;
return slave->dcb->func.write(slave->dcb, pkt);
}

View File

@ -678,7 +678,7 @@ SERVICE *service;
if (service)
return (unsigned long)(service->users);
else
return 0;
return 1; /*< invalid argument */
}
return rval;
case ARG_TYPE_DCB:
@ -798,7 +798,7 @@ bool in_space = false;
}
}
*lptr = 0;
args[i+1] = NULL;
args[MIN(MAXARGS-1,i+1)] = NULL;
if (args[0] == NULL || *args[0] == 0)
return 1;
@ -886,11 +886,15 @@ bool in_space = false;
break;
case 1:
arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
if (arg1)
cmds[i].options[j].fn(dcb, arg1);
else
if (arg1 == 0x1)
{
dcb_printf(dcb, "Invalid argument: %s\n",
args[2]);
args[2]);
}
else
{
cmds[i].options[j].fn(dcb, arg1);
}
break;
case 2:
arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);

View File

@ -146,7 +146,8 @@ static void rses_end_locked_router_action(
static BACKEND *get_root_master(
BACKEND **servers);
static int handle_state_switch(
DCB* dcb,DCB_REASON reason, void * routersession);
static SPINLOCK instlock;
static ROUTER_INSTANCE *instances;
@ -203,6 +204,7 @@ createInstance(SERVICE *service, char **options)
{
ROUTER_INSTANCE *inst;
SERVER *server;
SERVER_REF *sref;
int i, n;
BACKEND *backend;
char *weightby;
@ -219,7 +221,7 @@ char *weightby;
* that we can maintain a count of the number of connections to each
* backend server.
*/
for (server = service->databases, n = 0; server; server = server->nextdb)
for (sref = service->dbref, n = 0; sref; sref = sref->next)
n++;
inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *));
@ -229,7 +231,7 @@ char *weightby;
return NULL;
}
for (server = service->databases, n = 0; server; server = server->nextdb)
for (sref = service->dbref, n = 0; sref; sref = sref->next)
{
if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL)
{
@ -239,7 +241,7 @@ char *weightby;
free(inst);
return NULL;
}
inst->servers[n]->server = server;
inst->servers[n]->server = sref->server;
inst->servers[n]->current_connection_count = 0;
inst->servers[n]->weight = 1000;
n++;
@ -409,12 +411,12 @@ BACKEND *master_host = NULL;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [newSession] Examine server in port %d with "
"%d connections. Status is %d, "
"%d connections. Status is %s, "
"inst->bitvalue is %d",
pthread_self(),
inst->servers[i]->server->port,
inst->servers[i]->current_connection_count,
inst->servers[i]->server->status,
STRSRVSTATUS(inst->servers[i]->server),
inst->bitmask)));
}
@ -536,7 +538,12 @@ BACKEND *master_host = NULL;
free(client_rses);
return NULL;
}
inst->stats.n_sessions++;
dcb_add_callback(
client_rses->backend_dcb,
DCB_REASON_NOT_RESPONDING,
&handle_state_switch,
client_rses);
inst->stats.n_sessions++;
/**
* Add this session to the list of active sessions.
@ -693,7 +700,8 @@ routeQuery(ROUTER *instance, void *router_session, GWBUF *queue)
rses_end_locked_router_action(router_cli_ses);
}
if (rses_is_closed || backend_dcb == NULL)
if (rses_is_closed || backend_dcb == NULL ||
SERVER_IS_DOWN(router_cli_ses->backend->server))
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
@ -797,7 +805,7 @@ clientReply(
GWBUF *queue,
DCB *backend_dcb)
{
DCB *client = NULL;
DCB *client ;
client = backend_dcb->session->client;
@ -841,18 +849,21 @@ static void handleError(
else
{
backend_dcb->dcb_errhandle_called = true;
}
}
spinlock_acquire(&session->ses_lock);
sesstate = session->state;
client_dcb = session->client;
spinlock_release(&session->ses_lock);
ss_dassert(client_dcb != NULL);
if (sesstate == SESSION_STATE_ROUTER_READY)
{
CHK_DCB(client_dcb);
spinlock_release(&session->ses_lock);
client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
}
else
{
spinlock_release(&session->ses_lock);
}
/** false because connection is not available anymore */
*succp = false;
@ -954,3 +965,41 @@ static BACKEND *get_root_master(BACKEND **servers) {
}
return master_host;
}
static int handle_state_switch(DCB* dcb,DCB_REASON reason, void * routersession)
{
ss_dassert(dcb != NULL);
SESSION* session = dcb->session;
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*)routersession;
SERVICE* service = session->service;
ROUTER* router = (ROUTER *)service->router;
switch(reason)
{
case DCB_REASON_CLOSE:
dcb->func.close(dcb);
break;
case DCB_REASON_DRAINED:
/** Do we need to do anything? */
break;
case DCB_REASON_HIGH_WATER:
/** Do we need to do anything? */
break;
case DCB_REASON_LOW_WATER:
/** Do we need to do anything? */
break;
case DCB_REASON_ERROR:
dcb->func.error(dcb);
break;
case DCB_REASON_HUP:
dcb->func.hangup(dcb);
break;
case DCB_REASON_NOT_RESPONDING:
dcb->func.hangup(dcb);
break;
default:
break;
}
return 0;
}

View File

@ -99,6 +99,7 @@ static int router_get_servercount(ROUTER_INSTANCE* router);
static int rses_get_max_slavecount(ROUTER_CLIENT_SES* rses, int router_nservers);
static int rses_get_max_replication_lag(ROUTER_CLIENT_SES* rses);
static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb);
static DCB* rses_get_client_dcb(ROUTER_CLIENT_SES* rses);
static route_target_t get_route_target (
skygw_query_type_t qtype,
@ -391,7 +392,7 @@ ROUTER_OBJECT* GetModuleObject()
/**
* Refresh the instance by hte given parameter value.
* Refresh the instance by the given parameter value.
*
* @param router Router instance
* @param singleparam Parameter fo be reloaded
@ -557,6 +558,7 @@ createInstance(SERVICE *service, char **options)
{
ROUTER_INSTANCE* router;
SERVER* server;
SERVER_REF* sref;
int nservers;
int i;
CONFIG_PARAMETER* param;
@ -569,13 +571,13 @@ createInstance(SERVICE *service, char **options)
spinlock_init(&router->lock);
/** Calculate number of servers */
server = service->databases;
sref = service->dbref;
nservers = 0;
while (server != NULL)
while (sref != NULL)
{
nservers++;
server=server->nextdb;
sref=sref->next;
}
router->servers = (BACKEND **)calloc(nservers + 1, sizeof(BACKEND *));
@ -589,10 +591,11 @@ createInstance(SERVICE *service, char **options)
* maintain a count of the number of connections to each
* backend server.
*/
server = service->databases;
sref = service->dbref;
nservers= 0;
while (server != NULL) {
while (sref != NULL) {
if ((router->servers[nservers] = malloc(sizeof(BACKEND))) == NULL)
{
/** clean up */
@ -603,7 +606,7 @@ createInstance(SERVICE *service, char **options)
free(router);
return NULL;
}
router->servers[nservers]->backend_server = server;
router->servers[nservers]->backend_server = sref->server;
router->servers[nservers]->backend_conn_count = 0;
router->servers[nservers]->be_valid = false;
router->servers[nservers]->weight = 1000;
@ -612,7 +615,7 @@ createInstance(SERVICE *service, char **options)
router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND;
#endif
nservers += 1;
server = server->nextdb;
sref = sref->next;
}
router->servers[nservers] = NULL;
@ -1026,19 +1029,10 @@ static void freeSession(
ROUTER_CLIENT_SES* router_cli_ses;
ROUTER_INSTANCE* router;
int i;
backend_ref_t* backend_ref;
router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session;
router = (ROUTER_INSTANCE *)router_instance;
backend_ref = router_cli_ses->rses_backend_ref;
for (i=0; i<router_cli_ses->rses_nbackends; i++)
{
if (!BREF_IS_IN_USE((&backend_ref[i])))
{
continue;
}
}
spinlock_acquire(&router->lock);
if (router->connections == router_cli_ses) {
@ -1779,6 +1773,33 @@ static void check_create_tmp_table(
}
}
/**
* Get client DCB pointer of the router client session.
* This routine must be protected by Router client session lock.
*
* @param rses Router client session pointer
*
* @return Pointer to client DCB
*/
static DCB* rses_get_client_dcb(
ROUTER_CLIENT_SES* rses)
{
DCB* dcb = NULL;
int i;
for (i=0; i<rses->rses_nbackends; i++)
{
if ((dcb = rses->rses_backend_ref[i].bref_dcb) != NULL &&
BREF_IS_IN_USE(&rses->rses_backend_ref[i]) &&
dcb->session != NULL &&
dcb->session->client != NULL)
{
return dcb->session->client;
}
}
return NULL;
}
/**
* The main routing entry, this is called with every packet that is
* received and has to be forwarded to the backend database.
@ -1815,6 +1836,9 @@ static int routeQuery(
CHK_CLIENT_RSES(router_cli_ses);
/**
* GWBUF is called "type undefined" when the incoming data isn't parsed
* and MySQL packets haven't been extracted to separate buffers.
* "Undefined" == "untyped".
* Untyped GWBUF means that it can consist of incomplete and/or multiple
* MySQL packets.
* Read and route found MySQL packets one by one and store potential
@ -1833,7 +1857,7 @@ static int routeQuery(
{
if (GWBUF_LENGTH(tmpbuf) > 0)
{
DCB* dcb = RSES_CLIENT_DCB(router_cli_ses);
DCB* dcb = rses_get_client_dcb(router_cli_ses);
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, tmpbuf);
}
@ -2211,7 +2235,7 @@ static bool route_single_stmt(
{
rlag_max = rses_get_max_replication_lag(rses);
}
btype = BE_UNDEFINED; /*< target may be master or slave */
btype = route_target & TARGET_SLAVE ? BE_SLAVE : BE_MASTER; /*< 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.
@ -2259,8 +2283,6 @@ static bool route_single_stmt(
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE,
"Found DCB for slave.")));
#endif
ss_dassert(get_bref_from_dcb(rses, target_dcb) !=
rses->rses_master_ref);
ss_dassert(get_root_master_bref(rses) ==
rses->rses_master_ref);
atomic_add(&inst->stats.n_slave, 1);
@ -2339,7 +2361,10 @@ static bool route_single_stmt(
*
* !!! Note that according to MySQL protocol
* there can only be one such non-sescmd stmt at the time.
*
* It is possible that bref->bref_pending_cmd includes a pending
* command if rwsplit is parent or child for another router,
* which runs all the same commands.
*
* If the assertion below traps, pending queries are treated
* somehow wrong, or client is sending more queries before
* previous is received.
@ -2702,8 +2727,9 @@ static void clientReply (
CHK_GWBUF(bref->bref_pending_cmd);
if ((ret = bref->bref_dcb->func.write(bref->bref_dcb,
gwbuf_clone(bref->bref_pending_cmd))) == 1)
if ((ret = bref->bref_dcb->func.write(
bref->bref_dcb,
gwbuf_clone(bref->bref_pending_cmd))) == 1)
{
ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance;
atomic_add(&inst->stats.n_queries, 1);
@ -3083,8 +3109,10 @@ static bool select_connect_backend_servers(
*/
execute_sescmd_history(&backend_ref[i]);
/**
* When server fails, this callback
* is called.
* Here we actually say : When this
* type of issue occurs (DCB_REASON_...)
* for this particular DCB,
* call this function.
*/
dcb_add_callback(
backend_ref[i].bref_dcb,
@ -3205,6 +3233,7 @@ static bool select_connect_backend_servers(
if (slaves_connected == 0 && slaves_found > 0)
{
#if defined(SS_EXTRA_DEBUG)
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Warning : Couldn't connect to any of the %d "
@ -3218,9 +3247,11 @@ static bool select_connect_backend_servers(
"slaves. Routing to %s only.",
slaves_found,
(is_synced_master ? "Galera nodes" : "Master"))));
#endif
}
else if (slaves_found == 0)
{
#if defined(SS_EXTRA_DEBUG)
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Warning : Couldn't find any slaves from existing "
@ -3234,6 +3265,7 @@ static bool select_connect_backend_servers(
"%d servers. Routing to %s only.",
router_nservers,
(is_synced_master ? "Galera nodes" : "Master"))));
#endif
}
else if (slaves_connected < max_nslaves)
{
@ -3812,7 +3844,7 @@ static bool execute_sescmd_in_backend(
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);
if(qlen > 0)
if(qlen > 0 && qlen < MYSQL_DATABASE_MAXLEN+1)
strncpy(data->db,tmpbuf->start+5,qlen - 1);
}
/** Fallthrough */
@ -4027,8 +4059,16 @@ return_rc:
* Suppress redundant OK packets sent by backends.
*
* The first OK packet is replied to the client.
* Return true if succeed, false is returned if router session was closed or
* if execute_sescmd_in_backend failed.
*
* @param router_cli_ses Client's router session pointer
* @param querybuf GWBUF including the query to be routed
* @param inst Router instance
* @param packet_type Type of MySQL packet
* @param qtype Query type from query_classifier
*
* @return True if at least one backend is used and routing succeed to all
* backends being used, otherwise false.
*
*/
static bool route_session_write(
ROUTER_CLIENT_SES* router_cli_ses,
@ -4041,11 +4081,18 @@ static bool route_session_write(
rses_property_t* prop;
backend_ref_t* backend_ref;
int i;
int max_nslaves;
int nbackends;
int nsucc;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Session write, routing to all servers.")));
/** Maximum number of slaves in this router client session */
max_nslaves = rses_get_max_slavecount(router_cli_ses,
router_cli_ses->rses_nbackends);
nsucc = 0;
nbackends = 0;
backend_ref = router_cli_ses->rses_backend_ref;
/**
@ -4059,13 +4106,10 @@ static bool route_session_write(
packet_type == MYSQL_COM_STMT_CLOSE)
{
int rc;
succp = true;
/** Lock router session */
/** Lock router session */
if (!rses_begin_locked_router_action(router_cli_ses))
{
succp = false;
goto return_succp;
}
@ -4087,12 +4131,11 @@ static bool route_session_write(
if (BREF_IS_IN_USE((&backend_ref[i])))
{
rc = dcb->func.write(dcb, gwbuf_clone(querybuf));
if (rc != 1)
{
succp = false;
}
nbackends += 1;
if ((rc = dcb->func.write(dcb, gwbuf_clone(querybuf))) == 1)
{
nsucc += 1;
}
}
}
rses_end_locked_router_action(router_cli_ses);
@ -4102,13 +4145,16 @@ static bool route_session_write(
/** Lock router session */
if (!rses_begin_locked_router_action(router_cli_ses))
{
succp = false;
goto return_succp;
}
if (router_cli_ses->rses_nbackends <= 0)
{
succp = false;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Router session doesn't have any backends in use. "
"Routing failed. <")));
goto return_succp;
}
/**
@ -4128,6 +4174,8 @@ static bool route_session_write(
{
sescmd_cursor_t* scur;
nbackends += 1;
if (LOG_IS_ENABLED(LOGFILE_TRACE))
{
LOGIF(LT, (skygw_log_write(
@ -4155,8 +4203,7 @@ static bool route_session_write(
*/
if (sescmd_cursor_is_active(scur))
{
succp = true;
nsucc += 1;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Backend %s:%d already executing sescmd.",
@ -4165,10 +4212,12 @@ static bool route_session_write(
}
else
{
succp = execute_sescmd_in_backend(&backend_ref[i]);
if (!succp)
{
if (execute_sescmd_in_backend(&backend_ref[i]))
{
nsucc += 1;
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to execute session "
@ -4178,18 +4227,20 @@ static bool route_session_write(
}
}
}
else
{
succp = false;
}
}
/** Unlock router session */
rses_end_locked_router_action(router_cli_ses);
return_succp:
/**
* Routing must succeed to all backends that are used.
* There must be at leas one and at most max_nslaves+1 backends.
*/
succp = (nbackends > 0 && nsucc == nbackends && nbackends <= max_nslaves+1);
return succp;
}
#if defined(NOT_USED)
static bool router_option_configured(
@ -4308,6 +4359,7 @@ static void handleError (
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *)router_session;
CHK_DCB(backend_dcb);
/** Don't handle same error twice on same DCB */
if (backend_dcb->dcb_errhandle_called)
{
@ -4332,21 +4384,33 @@ static void handleError (
switch (action) {
case ERRACT_NEW_CONNECTION:
{
if (!rses_begin_locked_router_action(rses))
{
*succp = false;
return;
}
if (rses->rses_master_ref->bref_dcb == backend_dcb &&
!SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server))
SERVER* srv;
if (!rses_begin_locked_router_action(rses))
{
/** Master failed, can't recover */
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Master node have failed. "
"Session will be closed.")));
*succp = false;
return;
}
srv = rses->rses_master_ref->bref_backend->backend_server;
/**
* If master has lost its Master status error can't be
* handled so that session could continue.
*/
if (rses->rses_master_ref->bref_dcb == backend_dcb &&
!SERVER_IS_MASTER(srv))
{
if (!srv->master_err_is_logged)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : server %s:%d lost the "
"master status. Readwritesplit "
"service can't locate the master. "
"Client sessions will be closed.",
srv->name,
srv->port)));
srv->master_err_is_logged = true;
}
*succp = false;
}
else
@ -4374,7 +4438,7 @@ static void handleError (
break;
}
default:
default:
*succp = false;
break;
}
@ -4620,15 +4684,16 @@ static bool have_enough_servers(
}
if (nservers < min_nsrv)
{
LOGIF(LE, (skygw_log_write_flush(
double dbgpct = ((double)min_nsrv/(double)router_nsrv)*100.0;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Unable to start %s service. There are "
"too few backend servers configured in "
"MaxScale.cnf. Found %d%% when at least %d%% "
"MaxScale.cnf. Found %d%% when at least %.0f%% "
"would be required.",
router->service->name,
(*p_rses)->rses_config.rw_max_slave_conn_percent,
min_nsrv/(router_nsrv/100))));
dbgpct)));
}
}
free(*p_rses);
@ -4746,13 +4811,21 @@ static int router_handle_state_switch(
bref = (backend_ref_t *)data;
CHK_BACKEND_REF(bref);
srv = bref->bref_backend->backend_server;
srv = bref->bref_backend->backend_server;
if (SERVER_IS_RUNNING(srv) && SERVER_IS_IN_CLUSTER(srv))
{
goto return_rc;
}
ses = dcb->session;
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"%lu [router_handle_state_switch] %s %s:%d in state %s",
pthread_self(),
STRDCBREASON(reason),
srv->name,
srv->port,
STRSRVSTATUS(srv))));
ses = dcb->session;
CHK_SESSION(ses);
rses = (ROUTER_CLIENT_SES *)dcb->session->router_session;

View File

@ -1,3 +1,3 @@
add_test(NAME SimpleHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} simple_tests ${CMAKE_CURRENT_SOURCE_DIR})
add_test(NAME ComplexHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} complex_tests ${CMAKE_CURRENT_SOURCE_DIR})
add_test(NAME StackHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} stack_tests ${CMAKE_CURRENT_SOURCE_DIR})
add_test(NAME SimpleHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/simple_tests ${CMAKE_CURRENT_BINARY_DIR})
add_test(NAME ComplexHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/complex_tests ${CMAKE_CURRENT_BINARY_DIR})
add_test(NAME StackHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/stack_tests ${CMAKE_CURRENT_BINARY_DIR})

Some files were not shown because too many files have changed in this diff Show More