diff --git a/CMakeLists.txt b/CMakeLists.txt index c7ef07397..0156fc6ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() \ No newline at end of file +endif() diff --git a/CheckPlatform.cmake b/CheckPlatform.cmake new file mode 100644 index 000000000..709b86db8 --- /dev/null +++ b/CheckPlatform.cmake @@ -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) diff --git a/Documentation/Administration Tutorial.pdf b/Documentation/Administration Tutorial.pdf deleted file mode 100755 index 49afa4de8..000000000 Binary files a/Documentation/Administration Tutorial.pdf and /dev/null differ diff --git a/Documentation/Filter Tutorial.pdf b/Documentation/Filter Tutorial.pdf deleted file mode 100755 index cd7c2fead..000000000 Binary files a/Documentation/Filter Tutorial.pdf and /dev/null differ diff --git a/Documentation/Galera Cluster Connection Routing Tutorial.pdf b/Documentation/Galera Cluster Connection Routing Tutorial.pdf deleted file mode 100755 index 1c10725a0..000000000 Binary files a/Documentation/Galera Cluster Connection Routing Tutorial.pdf and /dev/null differ diff --git a/Documentation/Galera Cluster Read-Write Splitting Tutorial.pdf b/Documentation/Galera Cluster Read-Write Splitting Tutorial.pdf deleted file mode 100755 index fbf2d2a5b..000000000 Binary files a/Documentation/Galera Cluster Read-Write Splitting Tutorial.pdf and /dev/null differ diff --git a/Documentation/Getting Started With MaxScale.pdf b/Documentation/Getting Started With MaxScale.pdf deleted file mode 100755 index 1a1f2f572..000000000 Binary files a/Documentation/Getting Started With MaxScale.pdf and /dev/null differ diff --git a/Documentation/MariaDB MaxScale Administration Tutorial.pdf b/Documentation/MariaDB MaxScale Administration Tutorial.pdf new file mode 100755 index 000000000..187ac0950 Binary files /dev/null and b/Documentation/MariaDB MaxScale Administration Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale 1.0.4 Release Notes.pdf b/Documentation/MariaDB MaxScale 1.0.4 Release Notes.pdf new file mode 100644 index 000000000..f8c47d5c0 Binary files /dev/null and b/Documentation/MariaDB MaxScale 1.0.4 Release Notes.pdf differ diff --git a/Documentation/MariaDB MaxScale Configuration Guide.pdf b/Documentation/MariaDB MaxScale Configuration Guide.pdf new file mode 100755 index 000000000..8924c85e7 Binary files /dev/null and b/Documentation/MariaDB MaxScale Configuration Guide.pdf differ diff --git a/Documentation/MariaDB MaxScale Debug-And-Diagnostic-Support.pdf b/Documentation/MariaDB MaxScale Debug-And-Diagnostic-Support.pdf new file mode 100755 index 000000000..b4313e2ab Binary files /dev/null and b/Documentation/MariaDB MaxScale Debug-And-Diagnostic-Support.pdf differ diff --git a/Documentation/MariaDB MaxScale Filter Tutorial.pdf b/Documentation/MariaDB MaxScale Filter Tutorial.pdf new file mode 100755 index 000000000..7a3337af6 Binary files /dev/null and b/Documentation/MariaDB MaxScale Filter Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale Galera Cluster Connection Routing Tutorial.pdf b/Documentation/MariaDB MaxScale Galera Cluster Connection Routing Tutorial.pdf new file mode 100755 index 000000000..e6a684054 Binary files /dev/null and b/Documentation/MariaDB MaxScale Galera Cluster Connection Routing Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale Galera Cluster Read-Write Splitting Tutorial.pdf b/Documentation/MariaDB MaxScale Galera Cluster Read-Write Splitting Tutorial.pdf new file mode 100755 index 000000000..7ca94cf87 Binary files /dev/null and b/Documentation/MariaDB MaxScale Galera Cluster Read-Write Splitting Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale HA with Corosync-Pacemaker.pdf b/Documentation/MariaDB MaxScale HA with Corosync-Pacemaker.pdf new file mode 100755 index 000000000..fe07170c2 Binary files /dev/null and b/Documentation/MariaDB MaxScale HA with Corosync-Pacemaker.pdf differ diff --git a/Documentation/MariaDB MaxScale Limitations.pdf b/Documentation/MariaDB MaxScale Limitations.pdf new file mode 100755 index 000000000..46762a18a Binary files /dev/null and b/Documentation/MariaDB MaxScale Limitations.pdf differ diff --git a/Documentation/MariaDB MaxScale MaxAdmin.pdf b/Documentation/MariaDB MaxScale MaxAdmin.pdf new file mode 100755 index 000000000..a22f785a3 Binary files /dev/null and b/Documentation/MariaDB MaxScale MaxAdmin.pdf differ diff --git a/Documentation/MariaDB MaxScale MySQL Replication Connection Routing Tutorial.pdf b/Documentation/MariaDB MaxScale MySQL Replication Connection Routing Tutorial.pdf new file mode 100755 index 000000000..e2e9bc7e1 Binary files /dev/null and b/Documentation/MariaDB MaxScale MySQL Replication Connection Routing Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale MySQL Replication Read-Write Splitting Tutorial.pdf b/Documentation/MariaDB MaxScale MySQL Replication Read-Write Splitting Tutorial.pdf new file mode 100755 index 000000000..c2eb92106 Binary files /dev/null and b/Documentation/MariaDB MaxScale MySQL Replication Read-Write Splitting Tutorial.pdf differ diff --git a/Documentation/MariaDB MaxScale_ Getting Started With MaxScale.pdf b/Documentation/MariaDB MaxScale_ Getting Started With MaxScale.pdf new file mode 100755 index 000000000..3d66adabb Binary files /dev/null and b/Documentation/MariaDB MaxScale_ Getting Started With MaxScale.pdf differ diff --git a/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf b/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf deleted file mode 100755 index 0a23131e2..000000000 Binary files a/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf and /dev/null differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios.pdf b/Documentation/MaxScale Configuration And Usage Scenarios.pdf deleted file mode 100644 index e5b649413..000000000 Binary files a/Documentation/MaxScale Configuration And Usage Scenarios.pdf and /dev/null differ diff --git a/Documentation/MaxScale Debug And Diagnostic Support.pdf b/Documentation/MaxScale Debug And Diagnostic Support.pdf deleted file mode 100755 index 7ea71f97b..000000000 Binary files a/Documentation/MaxScale Debug And Diagnostic Support.pdf and /dev/null differ diff --git a/Documentation/MaxScale HA with Corosync and Pacemaker.pdf b/Documentation/MaxScale HA with Corosync and Pacemaker.pdf deleted file mode 100644 index aa0daac7c..000000000 Binary files a/Documentation/MaxScale HA with Corosync and Pacemaker.pdf and /dev/null differ diff --git a/Documentation/MaxScale HA with Corosync-Pacemaker.pdf b/Documentation/MaxScale HA with Corosync-Pacemaker.pdf deleted file mode 100755 index 786839186..000000000 Binary files a/Documentation/MaxScale HA with Corosync-Pacemaker.pdf and /dev/null differ diff --git a/Documentation/MaxScale Known Limitiations.pdf b/Documentation/MaxScale Known Limitiations.pdf deleted file mode 100755 index a9155cbf6..000000000 Binary files a/Documentation/MaxScale Known Limitiations.pdf and /dev/null differ diff --git a/Documentation/MaxScale MySQL Cluster setup.pdf b/Documentation/MaxScale MySQL Cluster setup.pdf deleted file mode 100644 index 5d4490fd6..000000000 Binary files a/Documentation/MaxScale MySQL Cluster setup.pdf and /dev/null differ diff --git a/Documentation/MySQL Replication Connection Routing Tutorial.pdf b/Documentation/MySQL Replication Connection Routing Tutorial.pdf deleted file mode 100755 index 7658da9d7..000000000 Binary files a/Documentation/MySQL Replication Connection Routing Tutorial.pdf and /dev/null differ diff --git a/Documentation/MySQL Replication Read-Write Splitting Tutorial.pdf b/Documentation/MySQL Replication Read-Write Splitting Tutorial.pdf deleted file mode 100755 index 51afe9437..000000000 Binary files a/Documentation/MySQL Replication Read-Write Splitting Tutorial.pdf and /dev/null differ diff --git a/Documentation/RabbitMQ Setup And MaxScale Integration.pdf b/Documentation/RabbitMQ Setup And MaxScale Integration.pdf deleted file mode 100644 index e9c728087..000000000 Binary files a/Documentation/RabbitMQ Setup And MaxScale Integration.pdf and /dev/null differ diff --git a/Documentation/filters/MariaDB MaxScale_ Filter_ Regex Filter.pdf b/Documentation/filters/MariaDB MaxScale_ Filter_ Regex Filter.pdf new file mode 100755 index 000000000..75c38183d Binary files /dev/null and b/Documentation/filters/MariaDB MaxScale_ Filter_ Regex Filter.pdf differ diff --git a/Documentation/filters/MariaDB MaxScale_ Filter_ Tee Filter.pdf b/Documentation/filters/MariaDB MaxScale_ Filter_ Tee Filter.pdf new file mode 100755 index 000000000..fa49fcb5f Binary files /dev/null and b/Documentation/filters/MariaDB MaxScale_ Filter_ Tee Filter.pdf differ diff --git a/Documentation/filters/MariaDB MaxScale_ Filter_ TopN Filter.pdf b/Documentation/filters/MariaDB MaxScale_ Filter_ TopN Filter.pdf new file mode 100755 index 000000000..ab7190c1e Binary files /dev/null and b/Documentation/filters/MariaDB MaxScale_ Filter_ TopN Filter.pdf differ diff --git a/Documentation/filters/Regex Filter.pdf b/Documentation/filters/Regex Filter.pdf deleted file mode 100755 index 7f6fe2726..000000000 Binary files a/Documentation/filters/Regex Filter.pdf and /dev/null differ diff --git a/Documentation/filters/Tee Filter.pdf b/Documentation/filters/Tee Filter.pdf deleted file mode 100755 index 59d1e028c..000000000 Binary files a/Documentation/filters/Tee Filter.pdf and /dev/null differ diff --git a/Documentation/filters/Top Filter.pdf b/Documentation/filters/Top Filter.pdf deleted file mode 100755 index 11f0bf924..000000000 Binary files a/Documentation/filters/Top Filter.pdf and /dev/null differ diff --git a/Documentation/history/MariaDB MaxScale 1.0.3 Release Notes.pdf b/Documentation/history/MariaDB MaxScale 1.0.3 Release Notes.pdf new file mode 100644 index 000000000..eb04e9146 Binary files /dev/null and b/Documentation/history/MariaDB MaxScale 1.0.3 Release Notes.pdf differ diff --git a/Documentation/MaxScale 1.0.1beta Release Notes.pdf b/Documentation/history/MaxScale 1.0.1beta Release Notes.pdf similarity index 100% rename from Documentation/MaxScale 1.0.1beta Release Notes.pdf rename to Documentation/history/MaxScale 1.0.1beta Release Notes.pdf diff --git a/FindMySQLConfig.cmake b/FindMySQLConfig.cmake new file mode 100644 index 000000000..a20e6dc47 --- /dev/null +++ b/FindMySQLConfig.cmake @@ -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() diff --git a/debian/rules b/debian/rules index fd6b52622..5acfaf7aa 100755 --- a/debian/rules +++ b/debian/rules @@ -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: diff --git a/log_manager/log_manager.cc b/log_manager/log_manager.cc index 1d5512756..04eef42e4 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -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 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 - - * + * @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 - - * - * - * @param logfile_id - - * - * - * @param logmanager - - * - * - * @param store_shmem - - * - * + * @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: /** diff --git a/log_manager/log_manager.h b/log_manager/log_manager.h index af11d3a86..f2146d303 100644 --- a/log_manager/log_manager.h +++ b/log_manager/log_manager.h @@ -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); diff --git a/macros.cmake b/macros.cmake index de842ea5c..ade5e02be 100644 --- a/macros.cmake +++ b/macros.cmake @@ -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) diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 9ae734fa0..8a92beba9 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -67,28 +67,18 @@ extern __thread log_info_t tls_log_info; #define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t= ~((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) { - 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; xdb && + 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); + } } /** diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 2c1a9d960..754e69478 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -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 *)); diff --git a/server/core/buffer.c b/server/core/buffer.c index 991e78b1b..6f9a162be 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -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, diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 17f5ecbd6..4a4174699 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -52,8 +52,32 @@ #include +#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; +} \ No newline at end of file diff --git a/server/core/dcb.c b/server/core/dcb.c index f8332de22..0e9c8b594 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -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; } diff --git a/server/core/filter.c b/server/core/filter.c index c697a264e..2cc18a42c 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -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); + } } /** diff --git a/server/core/gateway.c b/server/core/gateway.c index ee2290e28..a32c83a9a 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -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(); diff --git a/server/core/gw_utils.c b/server/core/gw_utils.c index 1b8836ba6..5d4fb5ed2 100644 --- a/server/core/gw_utils.c +++ b/server/core/gw_utils.c @@ -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)))); diff --git a/server/core/hashtable.c b/server/core/hashtable.c index a513c0367..ab979e472 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -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); } diff --git a/server/core/modutil.c b/server/core/modutil.c index 5824de9e2..d6dbfa7b1 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -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; } diff --git a/server/core/poll.c b/server/core/poll.c index 9ecd4367e..2dc4c34ae 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -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; diff --git a/server/core/server.c b/server/core/server.c index 61a3bdb3d..9841b96ff 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -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; + } } /** diff --git a/server/core/service.c b/server/core/service.c index fc2d128fc..e0a378647 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -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); +} \ No newline at end of file diff --git a/server/core/session.c b/server/core/session.c index 031780012..fb1dda79f 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -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"; } diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 0107f127d..2a0977088 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -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}/) diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c index ec2917783..7dfd9ef9c 100644 --- a/server/core/test/testadminusers.c +++ b/server/core/test/testadminusers.c @@ -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); } diff --git a/server/core/test/testhint.c b/server/core/test/testhint.c index 0f46e915b..e95d2bdd5 100644 --- a/server/core/test/testhint.c +++ b/server/core/test/testhint.c @@ -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; diff --git a/server/core/test/testpoll.c b/server/core/test/testpoll.c index a790e03a6..7b9175b2c 100644 --- a/server/core/test/testpoll.c +++ b/server/core/test/testpoll.c @@ -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"); diff --git a/server/core/test/testserver.c b/server/core/test/testserver.c index 5d4d130a8..de40847d6 100644 --- a/server/core/test/testserver.c +++ b/server/core/test/testserver.c @@ -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"); diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index 579bd617f..085a411ca 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -30,9 +30,9 @@ #include #include #include - +#include #include - +#include /** * 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."); diff --git a/server/core/test/testusers.c b/server/core/test/testusers.c index c2b2c9f15..d947075a0 100644 --- a/server/core/test/testusers.c +++ b/server/core/test/testusers.c @@ -33,6 +33,8 @@ #include +#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; diff --git a/server/core/users.c b/server/core/users.c index 127ec7f22..086a6b81c 100644 --- a/server/core/users.c +++ b/server/core/users.c @@ -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"); } diff --git a/server/include/buffer.h b/server/include/buffer.h index 76eebe63d..df426baca 100644 --- a/server/include/buffer.h +++ b/server/include/buffer.h @@ -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 diff --git a/server/include/dcb.h b/server/include/dcb.h index c0bb80d73..abd2cbcb6 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -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 */ diff --git a/server/include/modutil.h b/server/include/modutil.h index 762757617..fac39cbcc 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -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, diff --git a/server/include/server.h b/server/include/server.h index 1e4aa25ab..4bb514d65 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -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, diff --git a/server/include/service.h b/server/include/service.h index f68cb0774..ab18e5d29 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -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 diff --git a/server/include/session.h b/server/include/session.h index 33763a4d5..e008cc4ff 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -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 diff --git a/server/modules/filter/hint/hintparser.c b/server/modules/filter/hint/hintparser.c index 91140ed97..abf2fb141 100644 --- a/server/modules/filter/hint/hintparser.c +++ b/server/modules/filter/hint/hintparser.c @@ -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. */ diff --git a/server/modules/filter/tee.c b/server/modules/filter/tee.c index c8d2bc9c5..af56660db 100644 --- a/server/modules/filter/tee.c +++ b/server/modules/filter/tee.c @@ -58,6 +58,10 @@ #include #include #include +#include +#include +#include +#include #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;in_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; +} diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 2356affd2..ed1f18e7e 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -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 ") \ No newline at end of file +set_tests_properties(TestHintfilter TestRegexfilter TestFwfilter1 TestFwfilter2 TestTeeRecursion +PROPERTIES +ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/) diff --git a/server/modules/filter/test/fwfilter/fwtest.cnf.in b/server/modules/filter/test/fwfilter/fwtest.cnf.in old mode 100644 new mode 100755 diff --git a/server/modules/filter/test/fwfilter/fwtest.input b/server/modules/filter/test/fwfilter/fwtest.input old mode 100644 new mode 100755 diff --git a/server/modules/filter/test/fwfilter/fwtest2.expected b/server/modules/filter/test/fwfilter/fwtest2.expected old mode 100644 new mode 100755 diff --git a/server/modules/filter/test/fwfilter/fwtest2.input b/server/modules/filter/test/fwfilter/fwtest2.input old mode 100644 new mode 100755 diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index 20fe81c49..026f46234 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -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 " + 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 diff --git a/server/modules/filter/test/tee_recursion1.cnf b/server/modules/filter/test/tee_recursion1.cnf new file mode 100644 index 000000000..61acb9574 --- /dev/null +++ b/server/modules/filter/test/tee_recursion1.cnf @@ -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 diff --git a/server/modules/filter/test/tee_recursion2.cnf b/server/modules/filter/test/tee_recursion2.cnf new file mode 100644 index 000000000..55eafd728 --- /dev/null +++ b/server/modules/filter/test/tee_recursion2.cnf @@ -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 diff --git a/server/modules/filter/test/testdriver.sh b/server/modules/filter/test/testdriver.sh new file mode 100755 index 000000000..1b34f3781 --- /dev/null +++ b/server/modules/filter/test/testdriver.sh @@ -0,0 +1,11 @@ +#! /bin/bash +if [[ $# -lt 4 ]] +then + echo "Usage: $0 " + 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 $? diff --git a/server/modules/include/blr.h b/server/modules/include/blr.h index 2225ff655..f7b8f38ea 100644 --- a/server/modules/include/blr.h +++ b/server/modules/include/blr.h @@ -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 *); diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index b891bd7ba..bef74f45c 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -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); diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index dfd4f522f..19939c9e7 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -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 */ diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index f7724e62e..41d9872a9 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -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); } } diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 1c1b6eb04..1056f9026 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -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); diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index fbddb6275..e5f96f088 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -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. diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index b24260bef..0b59e4593 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -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( diff --git a/server/modules/routing/GaleraHACRoute.c b/server/modules/routing/GaleraHACRoute.c index 2f2627acb..27394c5c8 100644 --- a/server/modules/routing/GaleraHACRoute.c +++ b/server/modules/routing/GaleraHACRoute.c @@ -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++; } diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 7e3937106..1383c3322 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -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 diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 1287eacf4..40c87b092 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -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); +} diff --git a/server/modules/routing/binlog/blr_file.c b/server/modules/routing/binlog/blr_file.c index 24be12782..6d9f395eb 100644 --- a/server/modules/routing/binlog/blr_file.c +++ b/server/modules/routing/binlog/blr_file.c @@ -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; } /** diff --git a/server/modules/routing/binlog/blr_master.c b/server/modules/routing/binlog/blr_master.c index db95cf6c5..17e6f3ce0 100644 --- a/server/modules/routing/binlog/blr_master.c +++ b/server/modules/routing/binlog/blr_master.c @@ -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; } /** diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index 685a6e5ae..a98de60f7 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -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); +} diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 60afe1c83..545aff554 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -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]); diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index ecce1e0b3..6fb2a0098 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -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; +} diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 1bb4c8d78..170fbf6b2 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -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; irses_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; irses_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; diff --git a/server/modules/routing/readwritesplit/test/test_hints/CMakeLists.txt b/server/modules/routing/readwritesplit/test/test_hints/CMakeLists.txt index ed804382b..1aea27c7a 100644 --- a/server/modules/routing/readwritesplit/test/test_hints/CMakeLists.txt +++ b/server/modules/routing/readwritesplit/test/test_hints/CMakeLists.txt @@ -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}) diff --git a/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh b/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh index 1f751861f..67de5d4f6 100755 --- a/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh +++ b/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh @@ -7,16 +7,16 @@ TMASTER_ID=$4 TUSER=$5 TPWD=$6 TESTINPUT=$7 - +TESTFILE=$PWD/$(basename -z $TESTINPUT) if [ $# -lt $(( NARGS - 1 )) ] ; then -echo"" -echo "Wrong number of arguments, gave "$#" but "$(( NARGS - 1 ))" is required" -echo "" -echo "Usage :" -echo " rwsplit_hints.sh " -echo "" -exit 1 + echo"" + echo "Wrong number of arguments, gave "$#" but "$(( NARGS - 1 ))" is required" + echo "" + echo "Usage :" + echo " rwsplit_hints.sh " + echo "" + exit 1 fi if [ $# -eq $NARGS ] @@ -26,20 +26,18 @@ else TDIR=. fi -TESTINPUT=$TDIR/$TESTINPUT - RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --disable-reconnect\ --silent\ --comment i=0 while read -r LINE do -TINPUT[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[1]}'` -TRETVAL[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[2]}'` -echo "${TINPUT[i]}" >> $TESTINPUT.sql -i=$((i+1)) + TINPUT[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[1]}'` + TRETVAL[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[2]}'` + echo "${TINPUT[i]}" >> $TESTFILE.sql + i=$((i+1)) done < $TESTINPUT -`$RUNCMD < $TESTINPUT.sql > $TESTINPUT.output` +`$RUNCMD < $TESTFILE.sql > $TESTFILE.output` x=0 crash=1 @@ -47,32 +45,31 @@ all_passed=1 while read -r TOUTPUT do -crash=0 -if [ "$TOUTPUT" != "${TRETVAL[x]}" -a "${TRETVAL[x]}" != "" ] -then - all_passed=0 - echo "$TESTINPUT:$((x + 1)): ${TINPUT[x]} FAILED, return value $TOUTPUT when ${TRETVAL[x]} was expected">>$TLOG; -fi -x=$((x+1)) -done < $TESTINPUT.output + crash=0 + if [ "$TOUTPUT" != "${TRETVAL[x]}" -a "${TRETVAL[x]}" != "" ] + then + all_passed=0 + echo "$TESTINPUT:$((x + 1)): ${TINPUT[x]} FAILED, return value $TOUTPUT when ${TRETVAL[x]} was expected">>$TLOG; + fi + x=$((x+1)) +done < $TESTFILE.output if [ $crash -eq 1 ] then all_passed=0 for ((v=0;v<$i;v++)) do - echo "${TINPUT[v]} FAILED, nothing was returned">>$TLOG; + echo "${TINPUT[v]} FAILED, nothing was returned">>$TLOG; done fi if [ $all_passed -eq 1 ] then - echo "Test set: PASSED">>$TLOG; -else - echo "Test set: FAILED">>$TLOG; -fi - -if [ $# -eq $NARGS ] -then + echo "Test set: PASSED">>$TLOG; cat $TLOG + exit 0 +else + echo "Test set: FAILED">>$TLOG; + cat $TLOG + exit 1 fi diff --git a/server/test/MaxScale_test.cnf b/server/test/MaxScale_test.cnf index 60587ff92..ff6a09f24 100644 --- a/server/test/MaxScale_test.cnf +++ b/server/test/MaxScale_test.cnf @@ -15,6 +15,7 @@ router=readwritesplit servers=server1,server2,server3,server4 user=maxuser passwd=maxpwd +max_slave_connections=100% [RW Split Hint Router] type=service @@ -22,6 +23,7 @@ router=readwritesplit servers=server1,server2,server3,server4 user=maxuser passwd=maxpwd +max_slave_connections=100% filters=Hint @@ -37,6 +39,22 @@ passwd=maxpwd type=filter module=hintfilter +[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 diff --git a/server/test/maxscale_test.h.in b/server/test/maxscale_test.h.in new file mode 100644 index 000000000..d41ff181f --- /dev/null +++ b/server/test/maxscale_test.h.in @@ -0,0 +1,9 @@ +#ifndef MAXSCALE_TEST_H +#define MAXSCALE_TEST_H +#define TEST_DIR "${CMAKE_BINARY_DIR}" +#define TEST_LOG_DIR "${CMAKE_BINARY_DIR}/log" +#define TEST_BIN_DIR "${CMAKE_BINARY_DIR}/bin" +#define TEST_MOD_DIR "${CMAKE_BINARY_DIR}/modules" +#define TEST_LIB_DIR "${CMAKE_BINARY_DIR}/lib" +#define TEST_ETC_DIR "${CMAKE_BINARY_DIR}/etc" +#endif diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 1ade113a3..da92b84ac 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -124,7 +124,8 @@ typedef enum skygw_chk_t { CHK_NUM_BACKEND, CHK_NUM_BACKEND_REF, CHK_NUM_PREP_STMT, - CHK_NUM_PINFO + CHK_NUM_PINFO, + CHK_NUM_MYSQLSES } skygw_chk_t; # define STRBOOL(b) ((b) ? "true" : "false") @@ -278,6 +279,14 @@ typedef enum skygw_chk_t { ((t) == HINT_ROUTE_TO_ALL ? "HINT_ROUTE_TO_ALL" : \ ((t) == HINT_PARAMETER ? "HINT_PARAMETER" : "UNKNOWN HINT TYPE")))))) +#define STRDCBREASON(r) ((r) == DCB_REASON_CLOSE ? "DCB_REASON_CLOSE" : \ + ((r) == DCB_REASON_DRAINED ? "DCB_REASON_DRAINED" : \ + ((r) == DCB_REASON_HIGH_WATER ? "DCB_REASON_HIGH_WATER" : \ + ((r) == DCB_REASON_LOW_WATER ? "DCB_REASON_LOW_WATER" : \ + ((r) == DCB_REASON_ERROR ? "DCB_REASON_ERROR" : \ + ((r) == DCB_REASON_HUP ? "DCB_REASON_HUP" : \ + ((r) == DCB_REASON_NOT_RESPONDING ? "DCB_REASON_NOT_RESPONDING" : \ + "Unknown DCB reason"))))))) #define CHK_MLIST(l) { \ ss_info_dassert((l->mlist_chk_top == CHK_NUM_MLIST && \ @@ -534,6 +543,11 @@ typedef enum skygw_chk_t { "Parsing info struct has invalid check fields"); \ } +#define CHK_MYSQL_SESSION(s) { \ + ss_info_dassert((s)->myses_chk_top == CHK_NUM_MYSQLSES && \ + (s)->myses_chk_tail == CHK_NUM_MYSQLSES, \ + "MYSQL session struct has invalid check fields"); \ +} #if defined(FAKE_CODE) diff --git a/utils/skygw_types.h b/utils/skygw_types.h index 4410cc23a..c3bd8b491 100644 --- a/utils/skygw_types.h +++ b/utils/skygw_types.h @@ -20,6 +20,7 @@ #include #include +#include #define SECOND_USEC (1024*1024L) #define MSEC_USEC (1024L) diff --git a/utils/skygw_utils.cc b/utils/skygw_utils.cc index 3e8e15e67..272a54f9a 100644 --- a/utils/skygw_utils.cc +++ b/utils/skygw_utils.cc @@ -2058,11 +2058,29 @@ size_t get_decimal_len( return value > 0 ? (size_t) log10 ((double) value) + 1 : 1; } - - - - - - - - +/** + * Check if the provided pathname is POSIX-compliant. The valid characters + * are [a-z A-Z 0-9._-]. + * @param path A null-terminated string + * @return true if it is a POSIX-compliant pathname, otherwise false + */ +bool is_valid_posix_path(char* path) +{ + char* ptr = path; + while (*ptr != '\0') + { + if (isalnum (*ptr) || + *ptr == '/' || + *ptr == '.' || + *ptr == '-' || + *ptr == '_') + { + ptr++; + } + else + { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/utils/skygw_utils.h b/utils/skygw_utils.h index 2379dbb03..eb1fb0371 100644 --- a/utils/skygw_utils.h +++ b/utils/skygw_utils.h @@ -198,7 +198,7 @@ size_t get_decimal_len(size_t s); char* replace_literal(char* haystack, const char* needle, const char* replacement); - +bool is_valid_posix_path(char* path); EXTERN_C_BLOCK_END #endif /* SKYGW_UTILS_H */