diff --git a/CMakeLists.txt b/CMakeLists.txt index c7ef07397..1dccc289c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,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") @@ -120,43 +121,49 @@ install(FILES server/MaxScale_template.cnf DESTINATION etc) install(FILES ${ERRMSG} DESTINATION mysql) install(FILES ${DOCS} DESTINATION Documentation) 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 +214,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/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.3 Release Notes.pdf b/Documentation/MariaDB MaxScale 1.0.3 Release Notes.pdf new file mode 100644 index 000000000..eb04e9146 Binary files /dev/null and b/Documentation/MariaDB MaxScale 1.0.3 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/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/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/log_manager/log_manager.cc b/log_manager/log_manager.cc index 8fcc0deb6..1d5512756 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -2585,6 +2585,7 @@ static bool logfile_init( goto return_with_succp; } +#if defined(SS_DEBUG) if (store_shmem) { fprintf(stderr, "%s\t: %s->%s\n", @@ -2598,6 +2599,7 @@ static bool logfile_init( STRLOGNAME(logfile_id), logfile->lf_full_file_name); } +#endif succp = true; logfile->lf_state = RUN; CHK_LOGFILE(logfile); diff --git a/log_manager/log_manager.h b/log_manager/log_manager.h index af11d3a86..fa5e212f7 100644 --- a/log_manager/log_manager.h +++ b/log_manager/log_manager.h @@ -117,7 +117,7 @@ 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 diff --git a/macros.cmake b/macros.cmake index 8431933e9..36c3f2105 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 "3") 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}-beta") + set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-rc") endmacro() @@ -36,7 +36,7 @@ macro(set_variables) set(TEST_PORT_DB "4010" CACHE STRING "port of dbshard 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") diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 05c81cb1f..9f71d41f0 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -151,7 +151,7 @@ bool parse_query ( THD* thd; uint8_t* data; size_t len; - char* query_str; + char* query_str = NULL; parsing_info_t* pi; CHK_GWBUF(querybuf); @@ -173,9 +173,9 @@ bool parse_query ( /** Extract query and copy it to different buffer */ data = (uint8_t*)GWBUF_DATA(querybuf); len = MYSQL_GET_PACKET_LEN(data)-1; /*< distract 1 for packet type byte */ - query_str = (char *)malloc(len+1); - if (query_str == NULL) + + if (len < 1 || len >= ~((size_t)0) - 1 || (query_str = (char *)malloc(len+1)) == NULL) { /** Free parsing info data */ parsing_info_done(pi); diff --git a/server/MaxScale_template.cnf b/server/MaxScale_template.cnf index a2f961c79..e6887fdd1 100644 --- a/server/MaxScale_template.cnf +++ b/server/MaxScale_template.cnf @@ -184,7 +184,7 @@ replace=select # max_slave_replication_lag= # # Valid router modules currently are: -# readwritesplit, readconnroute, dbshard, debugcli and CLI +# readwritesplit, readconnroute, debugcli and CLI # ## Examples: @@ -208,25 +208,13 @@ passwd=mypwd #router_options=slave_selection_criteria= #filters=fetch|qla -[DBShard Router] -type=service -router=dbshard -servers=server1,server2 -user=maxuser -passwd=maxpwd - -[HTTPD Router] -type=service -router=testroute -servers=server1,server2,server3 - [Debug Interface] type=service router=debugcli [CLI] type=service -router=CLI +router=cli ## Listener definitions for the services # @@ -270,12 +258,6 @@ protocol=MySQLClient port=4006 #socket=/tmp/rwsplit.sock -[DBShard Listener] -type=listener -service=DBShard Router -protocol=MySQLClient -port=4010 - [Debug Listener] type=listener service=Debug Interface @@ -283,12 +265,6 @@ protocol=telnetd #address=127.0.0.1 port=4442 -[HTTPD Listener] -type=listener -service=HTTPD Router -protocol=HTTPD -port=6444 - [CLI Listener] type=listener service=CLI diff --git a/server/core/buffer.c b/server/core/buffer.c index 8f4989a7e..6f9a162be 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -179,7 +179,7 @@ gwbuf_clone(GWBUF *buf) { GWBUF *rval; - if ((rval = (GWBUF *)malloc(sizeof(GWBUF))) == NULL) + if ((rval = (GWBUF *)calloc(1,sizeof(GWBUF))) == NULL) { ss_dassert(rval != NULL); LOGIF(LE, (skygw_log_write_flush( @@ -194,16 +194,43 @@ GWBUF *rval; rval->start = buf->start; rval->end = buf->end; rval->gwbuf_type = buf->gwbuf_type; - rval->properties = NULL; - rval->hint = NULL; rval->gwbuf_info = buf->gwbuf_info; rval->gwbuf_bufobj = buf->gwbuf_bufobj; - rval->next = NULL; rval->tail = rval; CHK_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/config.c b/server/core/config.c index 33a26062b..34741106a 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -1891,7 +1891,7 @@ config_truth_value(char *str) { return 1; } - if (strcasecmp(str, "flase") == 0 || strcasecmp(str, "off") == 0) + if (strcasecmp(str, "false") == 0 || strcasecmp(str, "off") == 0) { return 0; } 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 8066bcef5..0e9c8b594 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -190,7 +190,7 @@ DCB *rval; rval->readcheck = 0; rval->polloutbusy = 0; rval->writecheck = 0; - rval->fd = -1; + rval->fd = DCBFD_CLOSED; rval->evq.next = NULL; rval->evq.prev = NULL; @@ -235,8 +235,10 @@ DCB *rval; void dcb_free(DCB *dcb) { - if (dcb->fd == -1) + if (dcb->fd == DCBFD_CLOSED) + { dcb_final_free(dcb); + } else { LOGIF(LE, (skygw_log_write_flush( @@ -308,7 +310,7 @@ DCB *clone; return NULL; } - clone->fd = -1; + clone->fd = DCBFD_CLOSED; clone->flags |= DCBF_CLONE; clone->state = orig->state; clone->data = orig->data; @@ -319,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; @@ -383,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) @@ -423,7 +431,6 @@ DCB_CALLBACK *cb; } spinlock_release(&dcb->cb_lock); - bitmask_free(&dcb->memdata.bitmask); free(dcb); } @@ -551,40 +558,42 @@ bool succp = false; DCB* dcb_next = NULL; int rc = 0; - /*< - * Close file descriptor and move to clean-up phase. - */ - rc = close(dcb->fd); + if (dcb->fd > 0) + { + /*< + * Close file descriptor and move to clean-up phase. + */ + rc = close(dcb->fd); - if (rc < 0) { - int eno = errno; - errno = 0; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Failed to close " - "socket %d on dcb %p due error %d, %s.", - dcb->fd, - dcb, - eno, - strerror(eno)))); - } -#if defined(SS_DEBUG) - else { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [dcb_process_zombies] Closed socket " - "%d on dcb %p.", - pthread_self(), - dcb->fd, - dcb))); -#endif /* SS_DEBUG */ + if (rc < 0) + { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to close " + "socket %d on dcb %p due error %d, %s.", + dcb->fd, + dcb, + eno, + strerror(eno)))); + } + else + { + dcb->fd = DCBFD_CLOSED; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [dcb_process_zombies] Closed socket " + "%d on dcb %p.", + pthread_self(), + dcb->fd, + dcb))); #if defined(FAKE_CODE) - conn_open[dcb->fd] = false; + conn_open[dcb->fd] = false; #endif /* FAKE_CODE */ -#if defined(SS_DEBUG) - ss_debug(dcb->fd = -1;) - } -#endif /* SS_DEBUG */ + } + } LOGIF_MAYBE(LT, (dcb_get_ses_log_info( dcb, &tls_log_info.li_sesid, @@ -657,7 +666,7 @@ int rc; } fd = dcb->func.connect(dcb, server, session); - if (fd == -1) { + if (fd == DCBFD_CLOSED) { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [dcb_connect] Failed to connect to server %s:%d, " @@ -683,7 +692,7 @@ int rc; session->client, session->client->fd))); } - ss_dassert(dcb->fd == -1); /*< must be uninitialized at this point */ + ss_dassert(dcb->fd == DCBFD_CLOSED); /*< must be uninitialized at this point */ /*< * Successfully connected to backend. Assign file descriptor to dcb */ @@ -704,7 +713,7 @@ int rc; */ rc = poll_add_dcb(dcb); - if (rc == -1) { + if (rc == DCBFD_CLOSED) { dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); dcb_final_free(dcb); return NULL; @@ -736,11 +745,22 @@ int dcb_read( GWBUF *buffer = NULL; int b; int rc; - int n ; + int n; int nread = 0; CHK_DCB(dcb); - while (true) + + if (dcb->fd <= 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Read failed, dcb is %s.", + dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not readable"))); + n = 0; + goto return_n; + } + + while (true) { int bufsize; @@ -864,6 +884,14 @@ int below_water; below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; ss_dassert(queue != NULL); + if (dcb->fd <= 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write failed, dcb is %s.", + dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not writable"))); + return 0; + } /** * SESSION_STATE_STOPPING means that one of the backends is closing * the router session. Some backends may have not completed @@ -877,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, @@ -888,7 +917,7 @@ int below_water; dcb, STRDCBSTATE(dcb->state), dcb->fd))); - ss_dassert(false); + //ss_dassert(false); return 0; } @@ -1209,46 +1238,42 @@ dcb_close(DCB *dcb) */ if (dcb->state == DCB_STATE_POLLING) { - if (dcb->fd != -1) - { - rc = poll_remove_dcb(dcb); + rc = poll_remove_dcb(dcb); - if (rc == 0) { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_close] Removed dcb %p in state %s from " - "poll set.", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state)))); - } else { - LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, - "Error : Removing DCB fd == %d in state %s from " - "poll set failed.", - dcb->fd, - STRDCBSTATE(dcb->state)))); - } - - if (rc == 0) + if (rc == 0) { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_close] Removed dcb %p in state %s from " + "poll set.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + } else { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Error : Removing DCB fd == %d in state %s from " + "poll set failed.", + dcb->fd, + STRDCBSTATE(dcb->state)))); + } + + if (rc == 0) + { + /** + * close protocol and router session + */ + if (dcb->func.close != NULL) { - /** - * close protocol and router session - */ - if (dcb->func.close != NULL) - { - dcb->func.close(dcb); - } - dcb_call_callback(dcb, DCB_REASON_CLOSE); - - - if (dcb->state == DCB_STATE_NOPOLLING) - { - dcb_add_to_zombieslist(dcb); - } + 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); } @@ -1585,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); } @@ -1757,14 +1784,16 @@ static bool dcb_set_state_nomutex( * @param dcb The DCB to write buffer * @param buf Buffer to write * @param nbytes Number of bytes to write + * @return Number of written bytes */ int gw_write(DCB *dcb, const void *buf, size_t nbytes) { - int w; + int w = 0; int fd = dcb->fd; #if defined(FAKE_CODE) - if (dcb_fake_write_errno[fd] != 0) { + if (fd > 0 && dcb_fake_write_errno[fd] != 0) + { ss_dassert(dcb_fake_write_ev[fd] != 0); w = write(fd, buf, nbytes/2); /*< leave peer to read missing bytes */ @@ -1772,11 +1801,15 @@ gw_write(DCB *dcb, const void *buf, size_t nbytes) w = -1; errno = dcb_fake_write_errno[fd]; } - } else { + } else if (fd > 0) + { w = write(fd, buf, nbytes); } #else - w = write(fd, buf, nbytes); + if (fd > 0) + { + w = write(fd, buf, nbytes); + } #endif /* FAKE_CODE */ #if defined(SS_DEBUG_MYSQL) @@ -1958,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; @@ -2043,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: @@ -2075,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 @@ -2088,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 45f493adc..a49f18e86 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -572,7 +573,7 @@ return_succp: static bool resolve_maxscale_homedir( char** p_home_dir) { - bool succp; + bool succp = false; char* tmp; char* tmp2; char* log_context = NULL; @@ -1837,7 +1838,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 21978a740..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 * @@ -234,7 +268,7 @@ modutil_get_query(GWBUF *buf) uint8_t* packet; mysql_server_cmd_t packet_type; size_t len; - char* query_str; + char* query_str = NULL; packet = GWBUF_DATA(buf); packet_type = packet[4]; @@ -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 ((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 ((query_str = (char *)malloc(len+1)) == NULL) + if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL) { goto retblock; } @@ -390,3 +424,72 @@ int modutil_send_mysql_err_packet ( return dcb->func.write(dcb, buf); } +/** + * Buffer contains at least one of the following: + * complete [complete] [partial] mysql packet + * + * return pointer to gwbuf containing a complete packet or + * NULL if no complete packet was found. + */ +GWBUF* modutil_get_next_MySQL_packet( + GWBUF** p_readbuf) +{ + GWBUF* packetbuf; + GWBUF* readbuf; + size_t buflen; + size_t packetlen; + size_t totalbuflen; + uint8_t* data; + size_t nbytes_copied = 0; + uint8_t* target; + + readbuf = *p_readbuf; + + if (readbuf == NULL) + { + packetbuf = NULL; + goto return_packetbuf; + } + CHK_GWBUF(readbuf); + + if (GWBUF_EMPTY(readbuf)) + { + packetbuf = NULL; + goto return_packetbuf; + } + totalbuflen = gwbuf_length(readbuf); + data = (uint8_t *)GWBUF_DATA((readbuf)); + packetlen = MYSQL_GET_PACKET_LEN(data)+4; + + /** packet is incomplete */ + if (packetlen > totalbuflen) + { + packetbuf = NULL; + goto return_packetbuf; + } + + packetbuf = gwbuf_alloc(packetlen); + target = GWBUF_DATA(packetbuf); + packetbuf->gwbuf_type = readbuf->gwbuf_type; /*< Copy the type too */ + /** + * Copy first MySQL packet to packetbuf and leave posible other + * packets to read buffer. + */ + while (nbytes_copied < packetlen && totalbuflen > 0) + { + uint8_t* src = GWBUF_DATA((*p_readbuf)); + size_t bytestocopy; + + buflen = GWBUF_LENGTH((*p_readbuf)); + bytestocopy = MIN(buflen,packetlen-nbytes_copied); + + memcpy(target+nbytes_copied, src, bytestocopy); + *p_readbuf = gwbuf_consume((*p_readbuf), bytestocopy); + totalbuflen = gwbuf_length((*p_readbuf)); + nbytes_copied += bytestocopy; + } + ss_dassert(buflen == 0 || nbytes_copied == packetlen); + +return_packetbuf: + return packetbuf; +} diff --git a/server/core/poll.c b/server/core/poll.c index 5b676f17b..f62797787 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -341,19 +341,26 @@ poll_remove_dcb(DCB *dcb) /*< * Set state to NOPOLLING and remove dcb from poll set. */ - if (dcb_set_state(dcb, new_state, &old_state)) { - rc = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, dcb->fd, &ev); + if (dcb_set_state(dcb, new_state, &old_state)) + { + /** + * Only positive fds can be removed from epoll set. + */ + if (dcb->fd > 0) + { + rc = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, dcb->fd, &ev); - if (rc != 0) { - int eno = errno; - errno = 0; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : epoll_ctl failed due %d, %s.", - eno, - strerror(eno)))); - } - ss_dassert(rc == 0); /*< trap in debug */ + if (rc != 0) { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : epoll_ctl failed due %d, %s.", + eno, + strerror(eno)))); + } + ss_dassert(rc == 0); /*< trap in debug */ + } } /*< * This call was redundant, but the end result is correct. @@ -1323,7 +1330,6 @@ void poll_add_epollin_event_to_dcb( } - static void poll_add_event_to_dcb( DCB* dcb, GWBUF* buf, diff --git a/server/core/secrets.c b/server/core/secrets.c index 57325a5ff..32fe59467 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -252,6 +252,7 @@ MAXKEYS key; "Error : failed opening /dev/random. Error %d, %s.", errno, strerror(errno)))); + close(fd); return 1; } @@ -260,6 +261,7 @@ MAXKEYS key; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed to read /dev/random."))); + close(fd); close(randfd); return 1; } 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 478504357..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,28 +326,28 @@ serviceStart(SERVICE *service) SERV_PROTOCOL *port; int listeners = 0; - service->router_instance = service->router->createInstance(service, - service->routerOptions); + if ((service->router_instance = service->router->createInstance(service, + service->routerOptions)) == NULL) + { + 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; + } - if (service->router_instance == NULL) + port = service->ports; + while (!service->svc_do_shutdown && port) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : starting the %s service failed.", - service->name))); - + listeners += serviceStartPort(service, port); + port = port->next; } - else + if (listeners) { - port = service->ports; - while (port) - { - listeners += serviceStartPort(service, port); - port = port->next; - } - if (listeners) - service->stats.started = time(0); + service->state = SERVICE_STATE_STARTED; + service->stats.started = time(0); } + return listeners; } @@ -405,12 +382,21 @@ int serviceStartAll() { SERVICE *ptr; -int n = 0; +int n = 0,i; ptr = allServices; - while (ptr) + while (ptr && !ptr->svc_do_shutdown) { - n += serviceStart(ptr); + n += (i = serviceStart(ptr)); + + if(i == 0) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Error : Failed to start service '%s'.", + ptr->name))); + } + ptr = ptr->next; } return n; @@ -439,6 +425,7 @@ int listeners = 0; port = port->next; } + service->state = SERVICE_STATE_STOPPED; return listeners; } @@ -481,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 */ @@ -503,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) @@ -581,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); } @@ -596,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; @@ -759,7 +758,7 @@ int n = 0; if ((flist = (FILTER_DEF **)malloc(sizeof(FILTER_DEF *))) == NULL) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, - "Out of memory adding filters to service.\n"))); + "Error : Out of memory adding filters to service.\n"))); return; } ptr = strtok_r(filters, "|", &brkt); @@ -770,14 +769,14 @@ int n = 0; (n + 1) * sizeof(FILTER_DEF *))) == NULL) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, - "Out of memory adding filters to service.\n"))); + "Error : Out of memory adding filters to service.\n"))); return; } if ((flist[n-1] = filter_find(trim(ptr))) == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Unable to find filter '%s' for service '%s'\n", + "Warning : Unable to find filter '%s' for service '%s'\n", trim(ptr), service->name ))); n--; @@ -819,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; @@ -832,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) { @@ -900,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; @@ -910,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)); @@ -929,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", @@ -1397,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..b6bf9d25c 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -35,7 +35,7 @@ add_test(TestBuffer test_buffer) add_test(TestDCB test_dcb) add_test(TestModutil test_modutil) add_test(TestPoll test_poll) -add_test(TestService test_service) +add_test(TestService /bin/sh -c " MAXSCALE_HOME=${CMAKE_BINARY_DIR} && ${CMAKE_CURRENT_BINAR_DIR}/test_service") add_test(TestServer test_server) add_test(TestUsers test_users) add_test(TestAdminUsers test_adminusers) diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c index ec2917783..866f7fff3 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); 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..49f6d0e24 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -30,7 +30,7 @@ #include #include #include - +#include #include /** @@ -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); /* 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..2186de335 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,34 @@ 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\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 57c4b0358..abd2cbcb6 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -129,6 +129,8 @@ typedef struct { */ #define GWPROTOCOL_VERSION {1, 0, 0} +#define DCBFD_CLOSED -1 + /** * The statitics gathered on a descriptor control block */ @@ -329,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 a0daf60a9..fac39cbcc 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -41,6 +41,9 @@ extern char *modutil_get_SQL(GWBUF *); 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 809c8c441..c00e7c992 100644 --- a/server/modules/filter/tee.c +++ b/server/modules/filter/tee.c @@ -41,6 +41,7 @@ * Date Who Description * 20/06/2014 Mark Riddoch Initial implementation * 24/06/2014 Mark Riddoch Addition of support for multi-packet queries + * 12/12/2014 Mark Riddoch Add support for otehr packet types * * @endverbatim */ @@ -57,6 +58,33 @@ #include #include #include +#include +#include + +#define MYSQL_COM_QUIT 0x01 +#define MYSQL_COM_INITDB 0x02 +#define MYSQL_COM_FIELD_LIST 0x04 +#define MYSQL_COM_CHANGE_USER 0x11 +#define MYSQL_COM_STMT_PREPARE 0x16 +#define MYSQL_COM_STMT_EXECUTE 0x17 +#define MYSQL_COM_STMT_SEND_LONG_DATA 0x18 +#define MYSQL_COM_STMT_CLOSE 0x19 +#define MYSQL_COM_STMT_RESET 0x1a + +#define REPLY_TIMEOUT_SECOND 5 +#define REPLY_TIMEOUT_MILLISECOND 1 + +static unsigned char required_packets[] = { + MYSQL_COM_QUIT, + MYSQL_COM_INITDB, + MYSQL_COM_FIELD_LIST, + MYSQL_COM_CHANGE_USER, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_CLOSE, + MYSQL_COM_STMT_RESET, + 0 }; /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -80,19 +108,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, }; @@ -120,14 +149,59 @@ typedef struct { */ typedef struct { DOWNSTREAM down; /* The downstream filter */ + UPSTREAM up; /* The upstream filter */ + + FILTER_DEF* dummy_filterdef; int active; /* filter is active? */ + int waiting; /* if the client is waiting for a reply */ + int replies; /* Number of queries received */ + int min_replies; /* Minimum number of replies to receive + * before forwarding the packet to the client*/ 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); +} + /** * Implementation of the mandatory version entry point * @@ -146,6 +220,7 @@ version() void ModuleInit() { + spinlock_init(&orphanLock); } /** @@ -232,7 +307,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)) { @@ -280,27 +356,148 @@ TEE_INSTANCE *my_instance = (TEE_INSTANCE *)instance; TEE_SESSION *my_session; char *remote, *userName; + if (strcmp(my_instance->service->name, session->service->name) == 0) + { + 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; + } + + 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; - if (my_instance->source - && (remote = session_get_remote(session)) != NULL) + spinlock_init(&my_session->tee_lock); + if (my_instance->source && + (remote = session_get_remote(session)) != NULL) { if (strcmp(remote, my_instance->source)) + { my_session->active = 0; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning : Tee filter is not active."))); + } } userName = session_getUser(session); - if (my_instance->userName && userName && strcmp(userName, - my_instance->userName)) + + if (my_instance->userName && + userName && + strcmp(userName, my_instance->userName)) + { my_session->active = 0; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning : Tee filter is not active."))); + } + if (my_session->active) { - my_session->branch_dcb = dcb_clone(session->client); - my_session->branch_session = session_alloc(my_instance->service, my_session->branch_dcb); + DCB* dcb; + SESSION* ses; + FILTER_DEF* dummy; + UPSTREAM* dummy_upstream; + + if ((dcb = dcb_clone(session->client)) == NULL) + { + freeSession(instance, (void *)my_session); + my_session = NULL; + + 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(instance, (void *)my_session); + my_session = NULL; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Creating client session for Tee " + "filter failed. Terminating session."))); + + goto retblock; + } + + 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->min_replies = 2; + my_session->branch_session = ses; + my_session->branch_dcb = dcb; + my_session->dummy_filterdef = dummy; + free(dummy_upstream); } } - +retblock: return my_session; } @@ -325,17 +522,27 @@ SESSION *bsession; { if ((bsession = my_session->branch_session) != NULL) { + CHK_SESSION(bsession); + spinlock_acquire(&bsession->ses_lock); + + if (bsession->state != SESSION_STATE_STOPPING) + { + bsession->state = SESSION_STATE_STOPPING; + } router = bsession->service->router; router_instance = bsession->service->router_instance; rsession = bsession->router_session; + spinlock_release(&bsession->ses_lock); + /** Close router session and all its connections */ router->closeSession(router_instance, rsession); } - dcb_free(my_session->branch_dcb); /* No need to free the session, this is done as * a side effect of closing the client DCB of the * session. */ + + my_session->active = 0; } } @@ -349,11 +556,129 @@ 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); + + 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) + { +#ifdef SS_DEBUG + skygw_log_write(LOGFILE_DEBUG,"tee.c: %d orphans freed.",++o_freed); +#endif + 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); + } + + return; } - /** * Set the downstream filter or router to which queries will be * passed from this filter. @@ -365,9 +690,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; } /** @@ -397,47 +736,133 @@ 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; + } } - free(ptr); - } + 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)) + { + char *dummy; + 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); + } + } /* Pass the query downstream */ + + my_session->replies = 0; rval = my_session->down.routeQuery(my_session->down.instance, - my_session->down.session, queue); + 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; } + +/** + * 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; + TEE_SESSION *my_session = (TEE_SESSION *) session; + + spinlock_acquire(&my_session->tee_lock); + + ss_dassert(my_session->active); + my_session->replies++; + + if (my_session->tee_replybuf == NULL && + instance != NULL) + { + my_session->tee_replybuf = reply; + } + else + { + gwbuf_free(reply); + } + + if((my_session->branch_session == NULL || + my_session->replies >= my_session->min_replies) && + my_session->tee_replybuf != NULL) + { + rc = my_session->up.clientReply ( + my_session->up.instance, + my_session->up.session, + my_session->tee_replybuf); + my_session->replies = 0; + my_session->tee_replybuf = NULL; + } + else + { + rc = 1; + } + + spinlock_release(&my_session->tee_lock); + return rc; +} /** * Diagnostics routine * @@ -477,3 +902,74 @@ TEE_SESSION *my_session = (TEE_SESSION *)fsession; my_session->n_rejected); } } + +/** + * Determine if the packet is a command that must be sent to the branch + * to maintain the session consistancy. These are COM_INIT_DB, + * COM_CHANGE_USER and COM_QUIT packets. + * + * @param queue The buffer to check + * @return non-zero if the packet should be sent to the branch + */ +static int +packet_is_required(GWBUF *queue) +{ +uint8_t *ptr; +int i; + + ptr = GWBUF_DATA(queue); + if (GWBUF_LENGTH(queue) > 4) + for (i = 0; required_packets[i]; i++) + if (ptr[4] == required_packets[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 9c87ee926..d703bc7be 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -13,4 +13,12 @@ target_link_libraries(harness fullcore) execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR}) add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") -add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regextest.expected") \ No newline at end of file +add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/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}) diff --git a/server/modules/filter/test/tee_recursion.sh b/server/modules/filter/test/tee_recursion.sh new file mode 100755 index 000000000..3c689bb15 --- /dev/null +++ b/server/modules/filter/test/tee_recursion.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +function execute_test() +{ + + RVAL=$(mysql --connect-timeout=5 -u $USER -p$PWD -h $HOST -P $PORT -e "select 1;"|grep -i error) + + if [[ ! -e $MAXPID ]] + then + echo "Test failed: $MAXPID was not found." + return 1 + fi + + if [[ "$RVAL" != "" ]] + then + echo "Test failed: Query to backend didn't return an error." + return 1 + fi + + LAST_LOG=$(ls $BINDIR/log -1|grep err|sort|uniq|tail -n 1) + TEST_RESULT=$(cat $BINDIR/log/$LAST_LOG | grep -i recursive) + if [[ "$TEST_RESULT" != "" ]] + then + return 0 + fi + echo "Test failed: Log file didn't mention tee recursion." + return 1 +} + +function reload_conf() +{ + $BINDIR/bin/maxadmin --user=admin --password=skysql reload config + if [[ $? -ne 0 ]] + then + echo "Test failed: maxadmin returned a non-zero value." + return 1 + fi + return 0 +} + +if [[ $# -lt 6 ]] +then + echo "usage: $0 " + 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/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 a7d14446b..9e6c5d78b 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; @@ -304,11 +310,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 05bcfe2b1..19939c9e7 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -323,4 +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)); + #endif /*< _RWSPLITROUTER_H */ diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 533a7fdaa..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,24 +770,31 @@ 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", @@ -777,7 +810,8 @@ int log_no_master = 1; } 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"))); @@ -786,13 +820,21 @@ int log_no_master = 1; } /* 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 d82595507..ca3708c2f 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 648367392..e5f96f088 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -144,7 +144,7 @@ GetModuleObject() int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { uint8_t *outbuf = NULL; - uint8_t mysql_payload_size = 0; + uint32_t mysql_payload_size = 0; uint8_t mysql_packet_header[4]; uint8_t *mysql_payload = NULL; uint8_t field_count = 0; @@ -223,7 +223,7 @@ int MySQLSendHandshake(DCB* dcb) { uint8_t *outbuf = NULL; - uint8_t mysql_payload_size = 0; + uint32_t mysql_payload_size = 0; uint8_t mysql_packet_header[4]; uint8_t mysql_packet_id = 0; uint8_t mysql_filler = GW_MYSQL_HANDSHAKE_FILLER; @@ -283,7 +283,6 @@ MySQLSendHandshake(DCB* dcb) // write packet heder with mysql_payload_size gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size); - //mysql_packet_header[0] = mysql_payload_size; // write packent number, now is 0 mysql_packet_header[3]= mysql_packet_id; @@ -378,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; */ @@ -406,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; @@ -426,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; } @@ -619,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; @@ -658,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( @@ -671,7 +681,7 @@ int gw_read_client_event( dcb_close(dcb); } } - else + else { char* fail_str = NULL; @@ -704,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); @@ -1396,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 87f0b54f1..249759d8f 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; @@ -1661,7 +1639,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 e5864110e..00269cfd8 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, @@ -111,6 +112,21 @@ static backend_ref_t* check_candidate_bref( backend_ref_t* new_bref, select_criteria_t sc); +static skygw_query_type_t is_read_tmp_table( + ROUTER_CLIENT_SES* router_cli_ses, + GWBUF* querybuf, + skygw_query_type_t type); + +static void check_create_tmp_table( + ROUTER_CLIENT_SES* router_cli_ses, + GWBUF* querybuf, + skygw_query_type_t type); + +static bool route_single_stmt( + ROUTER_INSTANCE* inst, + ROUTER_CLIENT_SES* rses, + GWBUF* querybuf); + static uint8_t getCapabilities (ROUTER* inst, void* router_session); @@ -376,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 @@ -542,6 +558,7 @@ createInstance(SERVICE *service, char **options) { ROUTER_INSTANCE* router; SERVER* server; + SERVER_REF* sref; int nservers; int i; CONFIG_PARAMETER* param; @@ -554,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 *)); @@ -574,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 */ @@ -588,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; @@ -597,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; @@ -1011,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) { @@ -1505,14 +1514,12 @@ static route_target_t get_route_target ( /** * Check if the query is a DROP TABLE... query and * if it targets a temporary table, remove it from the hashtable. - * @param instance Router instance - * @param router_session Router client session + * @param router_cli_ses Router client session * @param querybuf GWBUF containing the query * @param type The type of the query resolved so far */ void check_drop_tmp_table( - ROUTER* instance, - void* router_session, + ROUTER_CLIENT_SES* router_cli_ses, GWBUF* querybuf, skygw_query_type_t type) { @@ -1522,7 +1529,6 @@ void check_drop_tmp_table( char *hkey,*dbname; MYSQL_session* data; - ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; DCB* master_dcb = NULL; rses_property_t* rses_prop_tmp; @@ -1567,16 +1573,14 @@ void check_drop_tmp_table( /** * Check if the query targets a temporary table. - * @param instance Router instance - * @param router_session Router client session + * @param router_cli_ses Router client session * @param querybuf GWBUF containing the query * @param type The type of the query resolved so far * @return The type of the query */ -skygw_query_type_t is_read_tmp_table( - ROUTER* instance, - void* router_session, - GWBUF* querybuf, +static skygw_query_type_t is_read_tmp_table( + ROUTER_CLIENT_SES* router_cli_ses, + GWBUF* querybuf, skygw_query_type_t type) { @@ -1586,7 +1590,6 @@ skygw_query_type_t is_read_tmp_table( char *hkey,*dbname; MYSQL_session* data; - ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; DCB* master_dcb = NULL; skygw_query_type_t qtype = type; rses_property_t* rses_prop_tmp; @@ -1656,14 +1659,12 @@ skygw_query_type_t is_read_tmp_table( * the database and table name, create a hashvalue and * add it to the router client session's property. If property * doesn't exist then create it first. - * @param instance Router instance - * @param router_session Router client session + * @param router_cli_ses Router client session * @param querybuf GWBUF containing the query * @param type The type of the query resolved so far */ -void check_create_tmp_table( - ROUTER* instance, - void* router_session, +static void check_create_tmp_table( + ROUTER_CLIENT_SES* router_cli_ses, GWBUF* querybuf, skygw_query_type_t type) { @@ -1673,7 +1674,6 @@ void check_create_tmp_table( char *hkey,*dbname; MYSQL_session* data; - ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; DCB* master_dcb = NULL; rses_property_t* rses_prop_tmp; HASHTABLE* h; @@ -1773,6 +1773,33 @@ 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. @@ -1801,61 +1828,158 @@ static int routeQuery( void* router_session, GWBUF* querybuf) { - skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - mysql_server_cmd_t packet_type; - uint8_t* packet; int ret = 0; - DCB* master_dcb = NULL; - DCB* target_dcb = NULL; - ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; + ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - bool rses_is_closed = false; - route_target_t route_target; bool succp = false; - int rlag_max = MAX_RLAG_UNDEFINED; - backend_type_t btype; /*< target backend type */ CHK_CLIENT_RSES(router_cli_ses); - /** Dirty read for quick check if router is closed. */ - if (router_cli_ses->rses_closed) - { - rses_is_closed = true; - } - ss_dassert(!GWBUF_IS_TYPE_UNDEFINED(querybuf)); - - packet = GWBUF_DATA(querybuf); - packet_type = packet[4]; + /** + * 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 + * incomplete packet to DCB's dcb_readqueue. + */ + if (GWBUF_IS_TYPE_UNDEFINED(querybuf)) + { + GWBUF* tmpbuf = querybuf; + do + { + /** + * Try to read complete MySQL packet from tmpbuf. + * Append leftover to client's read queue. + */ + if ((querybuf = modutil_get_next_MySQL_packet(&tmpbuf)) == NULL) + { + if (GWBUF_LENGTH(tmpbuf) > 0) + { + DCB* dcb = rses_get_client_dcb(router_cli_ses); + + dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, tmpbuf); + } + succp = true; + goto retblock; + } + /** Mark buffer to as MySQL type */ + gwbuf_set_type(querybuf, GWBUF_TYPE_MYSQL); + gwbuf_set_type(querybuf, GWBUF_TYPE_SINGLE_STMT); - if (rses_is_closed) - { - /** - * MYSQL_COM_QUIT may have sent by client and as a part of backend - * closing procedure. - */ - if (packet_type != MYSQL_COM_QUIT) - { - char* query_str = modutil_get_query(querybuf); - - LOGIF(LE, - (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Can't route %s:%s:\"%s\" to " - "backend server. Router is closed.", - STRPACKETTYPE(packet_type), - STRQTYPE(qtype), - (query_str == NULL ? "(empty)" : query_str)))); + /** + * If router is closed, discard the packet + */ + if (router_cli_ses->rses_closed) + { + uint8_t* packet; + mysql_server_cmd_t packet_type; + + packet = GWBUF_DATA(querybuf); + packet_type = packet[4]; + + if (packet_type != MYSQL_COM_QUIT) + { + char* query_str = modutil_get_query(querybuf); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Can't route %s:\"%s\" to " + "backend server. Router is closed.", + STRPACKETTYPE(packet_type), + (query_str == NULL ? "(empty)" : query_str)))); + free(query_str); + } + } + else + { + succp = route_single_stmt(inst, router_cli_ses, querybuf); + } + } + while (tmpbuf != NULL); + } + /** + * If router is closed, discard the packet + */ + else if (router_cli_ses->rses_closed) + { + uint8_t* packet; + mysql_server_cmd_t packet_type; + + packet = GWBUF_DATA(querybuf); + packet_type = packet[4]; + + if (packet_type != MYSQL_COM_QUIT) + { + char* query_str = modutil_get_query(querybuf); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Can't route %s:\"%s\" to " + "backend server. Router is closed.", + STRPACKETTYPE(packet_type), + (query_str == NULL ? "(empty)" : query_str)))); free(query_str); + } + } + else + { + succp = route_single_stmt(inst, router_cli_ses, querybuf); + } + +retblock: +#if defined(SS_DEBUG2) + if (querybuf != NULL) + { + char* canonical_query_str; + + canonical_query_str = skygw_get_canonical(querybuf); + + if (canonical_query_str != NULL) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Canonical version: %s", + canonical_query_str))); + free(canonical_query_str); } - ret = 0; - goto retblock; } - - /** +#endif + if (querybuf != NULL) gwbuf_free(querybuf); + if (succp) ret = 1; + + return ret; +} + + + +static bool route_single_stmt( + ROUTER_INSTANCE* inst, + ROUTER_CLIENT_SES* rses, + GWBUF* querybuf) +{ + skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; + mysql_server_cmd_t packet_type; + uint8_t* packet; + int ret = 0; + DCB* master_dcb = NULL; + DCB* target_dcb = NULL; + route_target_t route_target; + bool succp = false; + int rlag_max = MAX_RLAG_UNDEFINED; + backend_type_t btype; /*< target backend type */ + + + ss_dassert(!GWBUF_IS_TYPE_UNDEFINED(querybuf)); + packet = GWBUF_DATA(querybuf); + packet_type = packet[4]; + + /** * Read stored master DCB pointer. If master is not set, routing must * be aborted */ - if ((master_dcb = router_cli_ses->rses_master_ref->bref_dcb) == NULL) + if ((master_dcb = rses->rses_master_ref->bref_dcb) == NULL) { char* query_str = modutil_get_query(querybuf); CHK_DCB(master_dcb); @@ -1868,137 +1992,137 @@ static int routeQuery( STRQTYPE(qtype), (query_str == NULL ? "(empty)" : query_str)))); free(query_str); - ret = 0; + succp = false; goto retblock; } - /** If buffer is not contiguous, make it such */ + /** If buffer is not contiguous, make it such */ if (querybuf->next != NULL) { querybuf = gwbuf_make_contiguous(querybuf); } - - switch(packet_type) { - case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ - case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ - case MYSQL_COM_REFRESH: /*< 7 - I guess this is session but not sure */ - case MYSQL_COM_DEBUG: /*< 0d all servers dump debug info to stdout */ - case MYSQL_COM_PING: /*< 0e all servers are pinged */ - case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ - case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ - case MYSQL_COM_STMT_SEND_LONG_DATA: /*< send data to column */ - case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ - qtype = QUERY_TYPE_SESSION_WRITE; - break; - - case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ - case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ - qtype = QUERY_TYPE_WRITE; - break; - - case MYSQL_COM_QUERY: - qtype = query_classifier_get_type(querybuf); - break; - - case MYSQL_COM_STMT_PREPARE: - qtype = query_classifier_get_type(querybuf); - qtype |= QUERY_TYPE_PREPARE_STMT; - break; - - case MYSQL_COM_STMT_EXECUTE: - /** Parsing is not needed for this type of packet */ - qtype = QUERY_TYPE_EXEC_STMT; - break; - - case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ - case MYSQL_COM_STATISTICS: /**< 9 ? */ - case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ - case MYSQL_COM_CONNECT: /**< 0b ? */ - case MYSQL_COM_PROCESS_KILL: /**< 0c ? */ - case MYSQL_COM_TIME: /**< 0f should this be run in gateway ? */ - case MYSQL_COM_DELAYED_INSERT: /**< 10 ? */ - case MYSQL_COM_DAEMON: /**< 1d ? */ - default: - break; - } /**< switch by packet type */ - + + switch(packet_type) { + case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ + case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ + case MYSQL_COM_REFRESH: /*< 7 - I guess this is session but not sure */ + case MYSQL_COM_DEBUG: /*< 0d all servers dump debug info to stdout */ + case MYSQL_COM_PING: /*< 0e all servers are pinged */ + case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ + case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ + case MYSQL_COM_STMT_SEND_LONG_DATA: /*< send data to column */ + case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ + qtype = QUERY_TYPE_SESSION_WRITE; + break; + + case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ + case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ + qtype = QUERY_TYPE_WRITE; + break; + + case MYSQL_COM_QUERY: + qtype = query_classifier_get_type(querybuf); + break; + + case MYSQL_COM_STMT_PREPARE: + qtype = query_classifier_get_type(querybuf); + qtype |= QUERY_TYPE_PREPARE_STMT; + break; + + case MYSQL_COM_STMT_EXECUTE: + /** Parsing is not needed for this type of packet */ + qtype = QUERY_TYPE_EXEC_STMT; + break; + + case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ + case MYSQL_COM_STATISTICS: /**< 9 ? */ + case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ + case MYSQL_COM_CONNECT: /**< 0b ? */ + case MYSQL_COM_PROCESS_KILL: /**< 0c ? */ + case MYSQL_COM_TIME: /**< 0f should this be run in gateway ? */ + case MYSQL_COM_DELAYED_INSERT: /**< 10 ? */ + case MYSQL_COM_DAEMON: /**< 1d ? */ + default: + break; + } /**< switch by packet type */ + /** * Check if the query has anything to do with temporary tables. */ - qtype = is_read_tmp_table(instance,router_session,querybuf,qtype); - check_create_tmp_table(instance,router_session,querybuf,qtype); - check_drop_tmp_table(instance,router_session,querybuf,qtype); - - /** - * If autocommit is disabled or transaction is explicitly started - * transaction becomes active and master gets all statements until - * transaction is committed and autocommit is enabled again. - */ - if (router_cli_ses->rses_autocommit_enabled && - QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) - { - router_cli_ses->rses_autocommit_enabled = false; - - if (!router_cli_ses->rses_transaction_active) - { - router_cli_ses->rses_transaction_active = true; - } - } - else if (!router_cli_ses->rses_transaction_active && - QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX)) - { - router_cli_ses->rses_transaction_active = true; - } - /** - * Explicit COMMIT and ROLLBACK, implicit COMMIT. - */ - if (router_cli_ses->rses_autocommit_enabled && - router_cli_ses->rses_transaction_active && - (QUERY_IS_TYPE(qtype,QUERY_TYPE_COMMIT) || - QUERY_IS_TYPE(qtype,QUERY_TYPE_ROLLBACK))) - { - router_cli_ses->rses_transaction_active = false; - } - else if (!router_cli_ses->rses_autocommit_enabled && - QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) - { - router_cli_ses->rses_autocommit_enabled = true; - router_cli_ses->rses_transaction_active = false; + qtype = is_read_tmp_table(rses, querybuf, qtype); + check_create_tmp_table(rses, querybuf, qtype); + check_drop_tmp_table(rses, querybuf,qtype); + + /** + * If autocommit is disabled or transaction is explicitly started + * transaction becomes active and master gets all statements until + * transaction is committed and autocommit is enabled again. + */ + if (rses->rses_autocommit_enabled && + QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) + { + rses->rses_autocommit_enabled = false; + + if (!rses->rses_transaction_active) + { + rses->rses_transaction_active = true; + } + } + else if (!rses->rses_transaction_active && + QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX)) + { + rses->rses_transaction_active = true; + } + /** + * Explicit COMMIT and ROLLBACK, implicit COMMIT. + */ + if (rses->rses_autocommit_enabled && + rses->rses_transaction_active && + (QUERY_IS_TYPE(qtype,QUERY_TYPE_COMMIT) || + QUERY_IS_TYPE(qtype,QUERY_TYPE_ROLLBACK))) + { + rses->rses_transaction_active = false; + } + else if (!rses->rses_autocommit_enabled && + QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) + { + rses->rses_autocommit_enabled = true; + rses->rses_transaction_active = false; } - + if (LOG_IS_ENABLED(LOGFILE_TRACE)) { uint8_t* packet = GWBUF_DATA(querybuf); unsigned char ptype = packet[4]; size_t len = MIN(GWBUF_LENGTH(querybuf), - MYSQL_GET_PACKET_LEN((unsigned char *)querybuf->start)-1); + MYSQL_GET_PACKET_LEN((unsigned char *)querybuf->start)-1); char* data = (char*)&packet[5]; char* contentstr = strndup(data, len); char* qtypestr = skygw_get_qtype_str(qtype); - + skygw_log_write( LOGFILE_TRACE, - "> Autocommit: %s, trx is %s, cmd: %s, type: %s, " - "stmt: %s%s %s", - (router_cli_ses->rses_autocommit_enabled ? "[enabled]" : "[disabled]"), - (router_cli_ses->rses_transaction_active ? "[open]" : "[not open]"), - STRPACKETTYPE(ptype), - (qtypestr==NULL ? "N/A" : qtypestr), - contentstr, - (querybuf->hint == NULL ? "" : ", Hint:"), - (querybuf->hint == NULL ? "" : STRHINTTYPE(querybuf->hint->type))); - + "> Autocommit: %s, trx is %s, cmd: %s, type: %s, " + "stmt: %s%s %s", + (rses->rses_autocommit_enabled ? "[enabled]" : "[disabled]"), + (rses->rses_transaction_active ? "[open]" : "[not open]"), + STRPACKETTYPE(ptype), + (qtypestr==NULL ? "N/A" : qtypestr), + contentstr, + (querybuf->hint == NULL ? "" : ", Hint:"), + (querybuf->hint == NULL ? "" : STRHINTTYPE(querybuf->hint->type))); + free(contentstr); free(qtypestr); } - /** - * Find out where to route the query. Result may not be clear; it is - * possible to have a hint for routing to a named server which can - * be either slave or master. - * If query would otherwise be routed to slave then the hint determines - * actual target server if it exists. - * - * route_target is a bitfield and may include : + /** + * Find out where to route the query. Result may not be clear; it is + * possible to have a hint for routing to a named server which can + * be either slave or master. + * If query would otherwise be routed to slave then the hint determines + * actual target server if it exists. + * + * route_target is a bitfield and may include : * TARGET_ALL * - route to all connected backend servers * TARGET_SLAVE[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] @@ -2007,12 +2131,12 @@ static int routeQuery( * TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] * - route primarily according to the hints and if they failed, * eventually to master - */ - route_target = get_route_target(qtype, - router_cli_ses->rses_transaction_active, - router_cli_ses->rses_config.rw_use_sql_variables_in, - querybuf->hint); - + */ + route_target = get_route_target(qtype, + rses->rses_transaction_active, + rses->rses_config.rw_use_sql_variables_in, + querybuf->hint); + if (TARGET_IS_ALL(route_target)) { /** @@ -2020,27 +2144,37 @@ static int routeQuery( * response. Statement is examined in route_session_write. * Router locking is done inside the function. */ - succp = route_session_write(router_cli_ses, - gwbuf_clone(querybuf), - inst, - packet_type, - qtype); + succp = route_session_write( + rses, + gwbuf_clone(querybuf), + inst, + packet_type, + qtype); if (succp) { atomic_add(&inst->stats.n_all, 1); - ret = 1; } goto retblock; } /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) + if (!rses_begin_locked_router_action(rses)) { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Route query aborted! Routing session is closed <"))); - ret = 0; + if (packet_type != MYSQL_COM_QUIT) + { + char* query_str = modutil_get_query(querybuf); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Can't route %s:%s:\"%s\" to " + "backend server. Router is closed.", + STRPACKETTYPE(packet_type), + STRQTYPE(qtype), + (query_str == NULL ? "(empty)" : query_str)))); + free(query_str); + } + succp = false; goto retblock; } /** @@ -2099,18 +2233,15 @@ static int routeQuery( if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ { - rlag_max = rses_get_max_replication_lag(router_cli_ses); + 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. */ - succp = get_dcb(&target_dcb, - router_cli_ses, - btype, - named_server, - rlag_max); + succp = get_dcb(&target_dcb, rses, btype, named_server,rlag_max); + if (!succp) { if (TARGET_IS_NAMED_SERVER(route_target)) @@ -2136,29 +2267,24 @@ static int routeQuery( else if (TARGET_IS_SLAVE(route_target)) { btype = BE_SLAVE; - + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ { - rlag_max = rses_get_max_replication_lag(router_cli_ses); + rlag_max = rses_get_max_replication_lag(rses); } /** - * Search suitable backend server, get DCB in target_dcb - */ - succp = get_dcb(&target_dcb, - router_cli_ses, - BE_SLAVE, - NULL, - rlag_max); + * Search suitable backend server, get DCB in target_dcb + */ + succp = get_dcb(&target_dcb, rses, BE_SLAVE, NULL,rlag_max); + if (succp) { #if defined(SS_EXTRA_DEBUG) LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Found DCB for slave."))); #endif - ss_dassert(get_bref_from_dcb(router_cli_ses, target_dcb) != - router_cli_ses->rses_master_ref); - ss_dassert(get_root_master_bref(router_cli_ses) == - router_cli_ses->rses_master_ref); + ss_dassert(get_root_master_bref(rses) == + rses->rses_master_ref); atomic_add(&inst->stats.n_slave, 1); } else @@ -2174,11 +2300,11 @@ static int routeQuery( DCB* curr_master_dcb = NULL; succp = get_dcb(&curr_master_dcb, - router_cli_ses, + rses, BE_MASTER, NULL, MAX_RLAG_UNDEFINED); - + if (succp && master_dcb == curr_master_dcb) { atomic_add(&inst->stats.n_master, 1); @@ -2197,52 +2323,61 @@ static int routeQuery( else { LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, - "Was supposed to " - "route to master " - "but couldn't find " - "master in a " - "suitable state."))); + "Was supposed to " + "route to master " + "but couldn't find " + "master in a " + "suitable state."))); } /** * Master has changed. Return with error indicator. */ - rses_end_locked_router_action(router_cli_ses); + rses_end_locked_router_action(rses); succp = false; - ret = 0; goto retblock; } } - + if (succp) /*< Have DCB of the target backend */ { backend_ref_t* bref; sescmd_cursor_t* scur; - bref = get_bref_from_dcb(router_cli_ses, target_dcb); + bref = get_bref_from_dcb(rses, target_dcb); scur = &bref->bref_sescmd_cur; - + + ss_dassert(target_dcb != NULL); + LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "Route query to %s \t%s:%d <", (SERVER_IS_MASTER(bref->bref_backend->backend_server) ? - "master" : "slave"), + "master" : "slave"), bref->bref_backend->backend_server->name, bref->bref_backend->backend_server->port))); /** * Store current stmt if execution of previous session command - * haven't completed yet. Note that according to MySQL protocol + * haven't completed yet. + * + * !!! 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. */ if (sescmd_cursor_is_active(scur)) { ss_dassert(bref->bref_pending_cmd == NULL); bref->bref_pending_cmd = gwbuf_clone(querybuf); - rses_end_locked_router_action(router_cli_ses); - ret = 1; + rses_end_locked_router_action(rses); goto retblock; } - + if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(querybuf))) == 1) { backend_ref_t* bref; @@ -2251,7 +2386,7 @@ static int routeQuery( /** * Add one query response waiter to backend reference */ - bref = get_bref_from_dcb(router_cli_ses, target_dcb); + bref = get_bref_from_dcb(rses, target_dcb); bref_set_state(bref, BREF_QUERY_ACTIVE); bref_set_state(bref, BREF_WAITING_RESULT); } @@ -2260,32 +2395,32 @@ static int routeQuery( LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Routing query failed."))); + succp = false; } } - rses_end_locked_router_action(router_cli_ses); + rses_end_locked_router_action(rses); + retblock: #if defined(SS_DEBUG2) - { - char* canonical_query_str; - - canonical_query_str = skygw_get_canonical(querybuf); - - if (canonical_query_str != NULL) - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Canonical version: %s", - canonical_query_str))); - free(canonical_query_str); - } - } + { + char* canonical_query_str; + + canonical_query_str = skygw_get_canonical(querybuf); + + if (canonical_query_str != NULL) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Canonical version: %s", + canonical_query_str))); + free(canonical_query_str); + } + } #endif - gwbuf_free(querybuf); - return ret; + return succp; } - /** * @node Acquires lock to router client session if it is not closed. * @@ -2412,7 +2547,6 @@ char *weightby; } } - } /** @@ -2593,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); @@ -2974,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, @@ -3096,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 " @@ -3109,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 " @@ -3125,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) { @@ -3701,7 +3842,8 @@ 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); - strncpy(data->db,tmpbuf->start+5,qlen - 1); + if(qlen > 0 && qlen < UINT_MAX) + strncpy(data->db,tmpbuf->start+5,qlen - 1); } /** Fallthrough */ case MYSQL_COM_QUERY: @@ -3915,8 +4057,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, @@ -3929,11 +4079,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; /** @@ -3947,13 +4104,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; } @@ -3975,12 +4129,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); @@ -3990,13 +4143,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; } /** @@ -4016,6 +4172,8 @@ static bool route_session_write( { sescmd_cursor_t* scur; + nbackends += 1; + if (LOG_IS_ENABLED(LOGFILE_TRACE)) { LOGIF(LT, (skygw_log_write( @@ -4043,8 +4201,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.", @@ -4053,10 +4210,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 " @@ -4066,18 +4225,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( @@ -4196,6 +4357,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) { @@ -4220,21 +4382,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 @@ -4262,7 +4436,7 @@ static void handleError ( break; } - default: + default: *succp = false; break; } @@ -4508,15 +4682,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); @@ -4634,13 +4809,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 494f718a2..c1be8c586 100644 --- a/server/test/MaxScale_test.cnf +++ b/server/test/MaxScale_test.cnf @@ -1,27 +1,5 @@ -# -# Example MaxScale.cnf configuration file -# -# -# -# Number of server threads -# Valid options are: -# threads= - [maxscale] -threads=1 - -# Define a monitor that can be used to determine the state and role of -# the servers. -# -# Valid options are: -# -# module= -# servers=,,... -# user = -# passwd= -# monitor_interval= +threads=4 [MySQL Monitor] type=monitor @@ -29,33 +7,14 @@ module=mysqlmon servers=server1,server2,server3,server4 user=maxuser passwd=maxpwd - -# A series of service definition -# -# Valid options are: -# -# router= -# servers=,,... -# user= -# passwd= -# enable_root_user=<0 or 1, default is 0> -# version_string= -# -# Valid router modules currently are: -# readwritesplit, readconnroute and debugcli - +monitor_interval=10000 [RW Split Router] type=service router=readwritesplit servers=server1,server2,server3,server4 -max_slave_connections=90% -write_ses_variables_to_all=Yes -read_ses_variables_from_slaves=Yes user=maxuser passwd=maxpwd -filters=Hint [DBShard Router] type=service @@ -68,9 +27,6 @@ passwd=maxpwd type=service router=readwritesplit servers=server1,server2,server3,server4 -max_slave_connections=90% -write_ses_variables_to_all=Yes -read_ses_variables_from_slaves=Yes user=maxuser passwd=maxpwd filters=Hint @@ -84,31 +40,39 @@ servers=server1 user=maxuser passwd=maxpwd +[Hint] +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 -[HTTPD Router] -type=service -router=testroute -servers=server1,server2,server3 [Debug Interface] type=service router=debugcli +[CLI] +type=service +router=cli -[Hint] -type=filter -module=hintfilter - - -# Listener definitions for the services -# -# Valid options are: -# -# service= -# protocol= -# port= -# address=
-# socket= +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 [RW Split Listener] type=listener @@ -128,28 +92,17 @@ service=RW Split Hint Router protocol=MySQLClient port=4009 -[Read Connection Listener] -type=listener -service=Read Connection Router -protocol=MySQLClient -port=4008 -#socket=/tmp/readconn.sock - [Debug Listener] type=listener service=Debug Interface protocol=telnetd port=4442 -#address=127.0.0.1 -[HTTPD Listener] +[CLI Listener] type=listener -service=HTTPD Router -protocol=HTTPD -port=6444 - -# Definition of the servers - +service=CLI +protocol=maxscaled +port=6603 [server1] type=server address=127.0.0.1 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 4291a04bb..bd869cee2 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") @@ -279,6 +280,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 && \ @@ -535,6 +544,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 */