diff --git a/.gitignore b/.gitignore index 7fe302bd3..fb556eab2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,20 @@ depend.mk # Vi swap files .*.swp -/build/ \ No newline at end of file +/build/ + +# Reviewboard configuration file +*.reviewboardrc + +# Netbeans Project files +nbproject/ + +# CMake directories +build/ +CMakeFiles/* +*/CMakeFiles/* +*/*/CMakeFiles/* +*/*/*/CMakeFiles/* +*/*/*/*/CMakeFiles/* +*.cmake +Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index ddc989b7c..89066dcf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,38 +6,57 @@ endif() message(STATUS "CMake version: ${CMAKE_VERSION}") -include(macros.cmake) - +include(${CMAKE_SOURCE_DIR}/cmake/macros.cmake) enable_testing() + +# Packaging builds install to /usr and other builds to /usr/local +if(PACKAGE) + set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Prefix prepended to install directories.") +endif() + +# Set default values for cache entries and set the MaxScale version set_variables() set_maxscale_version() -set(CMAKE_INSTALL_PREFIX "/usr/local/mariadb-maxscale" CACHE PATH "Prefix prepended to install directories.") set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") project(MaxScale) -#Disabled for now pending evaluation +# Set the installation layout +include(${CMAKE_SOURCE_DIR}/cmake/install_layout.cmake) + +#Do the platform check include(cmake/CheckPlatform.cmake) check_deps() check_dirs() +find_package(OpenSSL) find_package(Valgrind) find_package(MySQLClient) find_package(MySQL) find_package(Pandoc) find_package(TCMalloc) find_package(Jemalloc) +find_package(CURL) # You can find the variables set by this in the FindCURL.cmake file # which is a default module in CMake. -find_package(CURL) + if(NOT CURL_FOUND) message(FATAL_ERROR "Failed to locate dependency: libcurl") endif() -set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/modules) +if(NOT OPENSSL_FOUND) + message(FATAL_ERROR "Failed to locate dependency: OpenSSL") +else() + if(OPENSSL_VERSION VERSION_LESS 1 AND NOT FORCE_OPENSSL100) + add_definitions("-DOPENSSL_0_9") + else() + add_definitions("-DOPENSSL_1_0") + endif() +endif() + +set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/${MAXSCALE_LIBDIR}) # Make sure the release notes for this release are present if it is a stable one if(${MAXSCALE_VERSION} MATCHES "-stable") @@ -49,10 +68,12 @@ if(${MAXSCALE_VERSION} MATCHES "-stable") endif() file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/server/include) -configure_file(${CMAKE_SOURCE_DIR}/server/include/version.h.in ${CMAKE_BINARY_DIR}/server/include/version.h) -configure_file(${CMAKE_SOURCE_DIR}/server/test/maxscale_test.h.in ${CMAKE_BINARY_DIR}/server/include/maxscale_test.h) -configure_file(${CMAKE_SOURCE_DIR}/etc/postinst.in ${CMAKE_BINARY_DIR}/postinst) -configure_file(${CMAKE_SOURCE_DIR}/etc/postrm.in ${CMAKE_BINARY_DIR}/postrm) +configure_file(${CMAKE_SOURCE_DIR}/server/include/version.h.in ${CMAKE_BINARY_DIR}/server/include/version.h @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/server/include/gwdirs.h.in ${CMAKE_BINARY_DIR}/server/include/gwdirs.h @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/server/test/maxscale_test.h.in ${CMAKE_BINARY_DIR}/server/include/maxscale_test.h @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/etc/postinst.in ${CMAKE_BINARY_DIR}/postinst @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/etc/postrm.in ${CMAKE_BINARY_DIR}/postrm @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/server/test/maxscale_test.cnf ${CMAKE_BINARY_DIR}/maxscale.cnf @ONLY) set(FLAGS "-Wall -Wno-unused-variable -Wno-unused-function -fPIC" CACHE STRING "Compilation flags") set(DEBUG_FLAGS "-ggdb -pthread -pipe -Wformat -fstack-protector --param=ssp-buffer-size=4" CACHE STRING "Debug compilation flags") @@ -70,7 +91,7 @@ if(CMAKE_VERSION VERSION_GREATER 2.6) endif() -IF(DEFINED OLEVEL ) +IF(DEFINED OLEVEL) if((OLEVEL GREATER -1) AND (OLEVEL LESS 4) ) set(FLAGS "${FLAGS} -O${OLEVEL}" CACHE STRING "Compilation flags" FORCE) message(STATUS "Optimization level at: ${OLEVEL}") @@ -93,7 +114,13 @@ if(PROFILE) set(FLAGS "${FLAGS} -pg " CACHE STRING "Compilation flags" FORCE) endif() -set(CMAKE_C_FLAGS "${FLAGS}") +if(USE_C99) + message(STATUS "Using C99 standard") + set(CMAKE_C_FLAGS "-std=c99 -D_GNU_SOURCE=1 ${FLAGS}") +else() + set(CMAKE_C_FLAGS "${FLAGS}") +endif() + set(CMAKE_C_FLAGS_DEBUG "${DEBUG_FLAGS} -DSS_DEBUG -DLOG_ASSERT") set(CMAKE_C_FLAGS_RELEASE "") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-ggdb") @@ -128,7 +155,7 @@ if(NOT WITHOUT_MAXADMIN) add_subdirectory(client) endif() - +# Generate text versions of some documents execute_process(COMMAND perl ${CMAKE_SOURCE_DIR}/Documentation/format.pl ${CMAKE_SOURCE_DIR}/Documentation/Changelog.md ${CMAKE_BINARY_DIR}/Changelog.txt) @@ -138,23 +165,29 @@ ${CMAKE_SOURCE_DIR}/Documentation/Release-Notes/MaxScale-1.1-Release-Notes.md execute_process(COMMAND perl ${CMAKE_SOURCE_DIR}/Documentation/format.pl ${CMAKE_SOURCE_DIR}/Documentation/Upgrading-To-MaxScale-1.1.0.md ${CMAKE_BINARY_DIR}/UpgradingToMaxScale110.txt) -install(FILES ${CMAKE_BINARY_DIR}/Changelog.txt DESTINATION .) -install(FILES ${CMAKE_BINARY_DIR}/ReleaseNotes.txt DESTINATION .) -install(FILES ${CMAKE_BINARY_DIR}/UpgradingToMaxScale110.txt DESTINATION .) -message(STATUS "Installing MaxScale to: ${CMAKE_INSTALL_PREFIX}/") -install(FILES server/MaxScale_template.cnf DESTINATION etc) -install(FILES server/MaxScale_BinlogServer_template.cnf DESTINATION etc) -install(FILES ${ERRMSG} DESTINATION mysql) -install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION .) -install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION .) -install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION .) -install(DIRECTORY DESTINATION log) +install(FILES ${CMAKE_BINARY_DIR}/Changelog.txt DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES ${CMAKE_BINARY_DIR}/ReleaseNotes.txt DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES ${CMAKE_BINARY_DIR}/UpgradingToMaxScale110.txt DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES server/maxscale_template.cnf DESTINATION ${MAXSCALE_SHAREDIR}) +if(WITH_MAXSCALE_CNF) + install(FILES server/maxscale_template.cnf DESTINATION ${MAXSCALE_CONFDIR} RENAME maxscale.cnf) +endif() +install(FILES server/maxscale_binlogserver_template.cnf DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES ${ERRMSG} DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES etc/lsyncd_example.conf DESTINATION ${MAXSCALE_SHAREDIR}) +install(FILES Documentation/maxscale.1 DESTINATION ${CMAKE_INSTALL_DATADIR}/man/man1) + # Install startup scripts and ldconfig files if(WITH_SCRIPTS) configure_file(${CMAKE_SOURCE_DIR}/maxscale.conf.in ${CMAKE_BINARY_DIR}/maxscale.conf @ONLY) + configure_file(${CMAKE_SOURCE_DIR}/etc/maxscale.service.in ${CMAKE_BINARY_DIR}/maxscale.service @ONLY) if(DEB_BASED) configure_file(${CMAKE_SOURCE_DIR}/etc/ubuntu/init.d/maxscale.in ${CMAKE_BINARY_DIR}/maxscale @ONLY) else() @@ -163,88 +196,89 @@ if(WITH_SCRIPTS) if(PACKAGE) message(STATUS "maxscale.conf will unpack to: /etc/ld.so.conf.d") message(STATUS "startup scripts will unpack to to: /etc/init.d") + message(STATUS "systemd service files will unpack to to: /usr/lib/systemd/system") + install(FILES ${CMAKE_BINARY_DIR}/maxscale DESTINATION ${MAXSCALE_SHAREDIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION ${MAXSCALE_SHAREDIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(FILES ${CMAKE_BINARY_DIR}/maxscale.service DESTINATION ${MAXSCALE_SHAREDIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) else() install(FILES ${CMAKE_BINARY_DIR}/maxscale DESTINATION /etc/init.d PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION /etc/ld.so.conf.d PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(FILES ${CMAKE_BINARY_DIR}/maxscale.service DESTINATION /usr/lib/systemd/system + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) message(STATUS "Installing maxscale.conf to: /etc/ld.so.conf.d") message(STATUS "Installing startup scripts to: /etc/init.d") + message(STATUS "Installing systemd service files to: /usr/lib/systemd/system") endif() endif() +# Only do packaging if configured if(PACKAGE) - install(FILES ${CMAKE_BINARY_DIR}/maxscale DESTINATION . + + execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE) + # Install the files copied by the postinst script into the share folder + install(FILES ${CMAKE_BINARY_DIR}/maxscale DESTINATION ${MAXSCALE_SHAREDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION . + install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION ${MAXSCALE_SHAREDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - install(FILES ${CMAKE_BINARY_DIR}/postinst DESTINATION . + install(FILES ${CMAKE_BINARY_DIR}/postinst DESTINATION ${MAXSCALE_SHAREDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - install(FILES ${CMAKE_BINARY_DIR}/postrm DESTINATION . + install(FILES ${CMAKE_BINARY_DIR}/postrm DESTINATION ${MAXSCALE_SHAREDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) 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() + # Generic CPack configuration variables + set(CPACK_STRIP_FILES FALSE) 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_FILE_NAME "maxscale-${MAXSCALE_VERSION}-${CPACK_PACKAGE_ARCHITECTURE}-${DISTRIB_SUFFIX}") set(CPACK_PACKAGE_NAME "maxscale") set(CPACK_PACKAGE_VENDOR "MariaDB Corporation Ab") set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_SOURCE_DIR}/etc/DESCRIPTION) set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst;{CMAKE_BINARY_DIR}/postrm") - set(CPACK_RPM_PACKAGE_RELEASE ${MAXSCALE_BUILD_NUMBER}) - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE ${CMAKE_BINARY_DIR}/postinst) - set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE ${CMAKE_BINARY_DIR}/postrm) - 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") + + # 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" ) ) + include(cmake/package_rpm.cmake) + message(STATUS "Generating RPM packages") + endif() + if(NOT ( ${DEBBUILD} STREQUAL "DEBBUILD-NOTFOUND" ) ) + include(cmake/package_deb.cmake) + message(STATUS "Generating DEB packages for ${DEB_ARCHITECTURE}") + endif() + include(CPack) endif() endif() add_custom_target(buildtests - COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} COMMAND make COMMENT "Building test suite..." VERBATIM ) add_custom_target(testall - COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N -DWITH_MAXSCALE_CNF=N -DMAXSCALE_VARDIR=${CMAKE_BINARY_DIR} COMMAND make install - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/server/test/MaxScale_test.cnf ${CMAKE_BINARY_DIR}/etc/MaxScale.cnf COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/testall.cmake COMMENT "Running full test suite..." VERBATIM) add_custom_target(testcore - COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N -DMAXSCALE_VARDIR=${CMAKE_BINARY_DIR} COMMAND make install - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/server/test/MaxScale_test.cnf ${CMAKE_BINARY_DIR}/etc/MaxScale.cnf COMMAND ctest -R Internal COMMENT "Running core test suite..." VERBATIM) @@ -274,7 +308,7 @@ endif() # Testall target with Valgrind if(VALGRIND_FOUND) add_custom_target(testall-valgrind - COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -DBUILD_TESTS=Y -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} -DWITH_SCRIPTS=N ${CMAKE_SOURCE_DIR} COMMAND make install COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/server/test/MaxScale_test.cnf ${CMAKE_BINARY_DIR}/etc/MaxScale.cnf COMMAND /bin/sh -c "valgrind --track-fds=yes --leak-check=full --show-leak-kinds=all --log-file=${CMAKE_BINARY_DIR}/valgrind.log ${CMAKE_BINARY_DIR}/bin/maxscale -c ${CMAKE_BINARY_DIR} &>/dev/null" diff --git a/Documentation/About/About-MaxScale.md b/Documentation/About/About-MaxScale.md index 4477c2907..ef0723914 100644 --- a/Documentation/About/About-MaxScale.md +++ b/Documentation/About/About-MaxScale.md @@ -17,7 +17,7 @@ Bugs can be reported in the MariaDB Jira [https://mariadb.atlassian.net](https://mariadb.atlassian.net) ## Installing MaxScale -Information about installing MaxScale, either from a repository or by building from source code, is included in the guide [Getting Started with MaxScale](/Documentation/Getting-Started/Getting-Started-With-MaxScale.md). +Information about installing MaxScale, either from a repository or by building from source code, is included in the [MariaDB MaxScale Installation Guide](../Getting-Started/MariaDB-MaxScale-Installation-Guide.md). The same guide also provides basic information on running MaxScale. More detailed information about configuring MaxScale is given in the [Configuration Guide](/Documentation/Getting-Started/Configuration-Guide.md). diff --git a/Documentation/About/SETUP.md b/Documentation/About/SETUP.md index 2be316a4d..e28fb0d40 100644 --- a/Documentation/About/SETUP.md +++ b/Documentation/About/SETUP.md @@ -1,37 +1,25 @@ Installation and startup Untar the binary distribution in the desired location, -e.g. /usr/local/mariadb +e.g. /usr/local/mariadb-maxscale Alternatively build from the source code using the instructions -in the README file and execute make install. +in the [Building MaxScale from Source Code](../Getting-Started/Building-MaxScale-from-Source-Code.md) document. -Simply set the environment variable MAXSCALE_HOME to point to the -MaxScale directory, found inside the path into which the files have been copied, -e.g. MAXSCALE_HOME=/usr/local/mariadb-maxscale - -Also you will need to optionally set LD_LIBRARY_PATH to include the 'lib' folder, -found inside the path into which the files have been copied, -e.g. LD_LIBRARY_PATH=/usr/local/mariadb-maxscale/lib - -Because we need the libmysqld library for parsing we must create a -valid my.cnf file to enable the library to be used. Copy the my.cnf -to $MAXSCALE_HOME/mysql/my.cnf. - -To start MaxScale execute the command 'maxscale' from the bin folder, -e.g. /usr/local/mariadb-maxscale/bin/maxscale +You can start MaxScale using `service maxscale start` or `systemctl start maxscale` if you installed the init.d scripts +or by manually starting the process from the bin folder of the installation directory. Configuration -You need to edit the file MaxScale.cnf in $MAXSCALE_HOME/etc, you should -define the set of server definitions you require, with the addresses -and ports of those servers. Also define the listening ports for your -various services. +You need to create or edit the maxscale.cnf file in the /etc folder. +Define the services you wish to provide, the set of server definitions +you require, with the addresses and ports of those servers and also +define the listening ports for your various services. -In order to view the internal activity of the gateway you can telnet to +In order to view the internal activity of MaxScale you can either use +the maxadmin client interface with the cli routing module or telnet to the port defined for the telnet listener. Initially you may login with the user name of "admin" and the password "mariadb". Once connected type help for an overview of the commands and help for the more detailed help on commands. Use the add user command to add a new user, -this will also remove the admin/mariadb user. - +this will also remove the admin/mariadb user. For a detailed guide about using the MaxAdmin interface, take a look at the [MaxAdmin](../Reference/MaxAdmin.md) guide. diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index dfbef26dc..ddfd54ba4 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -1,3 +1,4 @@ + [Search page for MaxScale Documentation](http://mariadb-corporation.github.io/MaxScale/Search/) # Contents @@ -5,7 +6,7 @@ ## About MaxScale - [About MaxScale](About/About-MaxScale.md) - - [Release Notes 1.1](Release-Notes/MaxScale-1.1-Release-Notes.md) + - [MaxScale 1.2.0 Release Notes](Release-Notes/MaxScale-1.2.0-Release-Notes.md) - [Changelog](Changelog.md) - [Limitations](About/Limitations.md) - [COPYRIGHT](About/COPYRIGHT.md) @@ -13,20 +14,23 @@ ## Getting Started - - [Getting Started with MaxScale](Getting-Started/Getting-Started-With-MaxScale.md) + - [MariaDB MaxScale Installation Guide](Getting-Started/MariaDB-MaxScale-Installation-Guide.md) - [Building MaxScale from Source Code](Getting-Started/Building-MaxScale-from-Source-Code.md) - [Configuration Guide](Getting-Started/Configuration-Guide.md) ## Upgrading MaxScale -- [Upgrading MaxScale to 1.1.0](Upgrading-To-MaxScale-1.1.0.md) +- [Upgrading MaxScale from 1.1.1 to 1.2.0](Upgrading/Upgrading-To-MaxScale-1.2.0.md) +- [Upgrading MaxScale from 1.0.5 to 1.1.0](Upgrading/Upgrading-To-MaxScale-1.1.0.md) ## Reference - [MaxAdmin](Reference/MaxAdmin.md) - [MaxScale HA with Corosync-Pacemaker](Reference/MaxScale-HA-with-Corosync-Pacemaker.md) + - [MaxScale HA with Lsyncd](Reference/MaxScale-HA-with-lsyncd.md) - [How Errors are Handled in MaxScale](Reference/How-errors-are-handled-in-MaxScale.md) - [Debug and Diagnostic Support](Reference/Debug-And-Diagnostic-Support.md) + - [Routing Hints](Reference/Hint-Syntax.md) ## Tutorials @@ -41,30 +45,35 @@ - [Replication Proxy with the Binlog Router Tutorial](Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md) - [RabbitMQ Setup and MaxScale Integration Tutorial](Tutorials/RabbitMQ-Setup-And-MaxScale-Integration.md) - [Nagios Plugins for MaxScale Tutorial](Tutorials/Nagios-Plugins.md) + - [Simple Schema Sharding Tutorial](Tutorials/Simple-Sharding-Tutorial.md) ## Routers - - [Read Write Split](routers/ReadWriteSplit.md) - - [Schemarouter](routers/SchemaRouter.md) + - [Read Write Split](Routers/ReadWriteSplit.md) + - [Read Connnection Router](Routers/ReadConnRoute.md) + - [Schemarouter](Routers/SchemaRouter.md) ## Filters Here are detailed documents about the filters MaxScale offers. They contain configuration guides and example use cases. Before reading these,you should have read the filter tutorial so that you know how they work and how to configure them. - - [Query Log All](filters/Query-Log-All-Filter.md) - - [Regex Filter](filters/Regex-Filter.md) - - [Tee Filter](filters/Tee-Filter.md) - - [Top N Filter](filters/Top-N-Filter.md) - - [Database Firewall Filter](filters/Database-Firewall-Filter.md) - - [RabbitMQ Filter](filters/RabbitMQ-Filter.md) + - [Query Log All](Filters/Query-Log-All-Filter.md) + - [Regex Filter](Filters/Regex-Filter.md) + - [Tee Filter](Filters/Tee-Filter.md) + - [Top N Filter](Filters/Top-N-Filter.md) + - [Database Firewall Filter](Filters/Database-Firewall-Filter.md) + - [RabbitMQ Filter](Filters/RabbitMQ-Filter.md) + - [Named Server Filter](Filters/Named-Server-Filter.md) + +## Monitors + - [MySQL Monitor](Monitors/MySQL-Monitor.md) + - [Galera Monitor](Monitors/Galera-Monitor.md) + - [Multi-Master Monitor](Monitors/MM-Monitor.md) + - [MySQL Cluster Monitor](Monitors/NDB-Cluster-Monitor.md) ## Utilities - - [RabbitMQ Consumer Client](filters/RabbitMQ-Consumer-Client.md) - -## Routers - - - [Simple Schema Sharding Router](routers/schemarouter/SchemaRouter.md) + - [RabbitMQ Consumer Client](Filters/RabbitMQ-Consumer-Client.md) ## Design Documents @@ -81,4 +90,7 @@ Here are detailed documents about the filters MaxScale offers. They contain conf - [MaxScale 1.0 Release Notes](Release-Notes/MaxScale-1.0-Release-Notes.md) - [MaxScale 1.0.1 Release Notes](Release-Notes/MaxScale-1.0.1-Release-Notes.md) - [MaxScale 1.0.3 Release Notes](Release-Notes/MaxScale-1.0.3-Release-Notes.md) + - [MaxScale 1.1.0 Release Notes](Release-Notes/MaxScale-1.1-Release-Notes.md) + - [MaxScale 1.1.1 Release Notes](Release-Notes/MaxScale-1.1.1-Release-Notes.md) + - [MaxScale 1.2.0 Release Notes](Release-Notes/MaxScale-1.2.0-Release-Notes.md) diff --git a/Documentation/filters/Database-Firewall-Filter.md b/Documentation/Filters/Database-Firewall-Filter.md similarity index 98% rename from Documentation/filters/Database-Firewall-Filter.md rename to Documentation/Filters/Database-Firewall-Filter.md index b448b65be..58eac1b6c 100644 --- a/Documentation/filters/Database-Firewall-Filter.md +++ b/Documentation/Filters/Database-Firewall-Filter.md @@ -5,7 +5,7 @@ The database firewall filter is used to block queries that match a set of rules. ## Configuration -The database firewall filter only requires minimal configuration in the MaxScale.cnf file. The actual rules of the database firewall filter are located in a separate text file. The following is an example of a database firewall filter configuration in MaxScale.cnf. +The database firewall filter only requires minimal configuration in the maxscale.cnf file. The actual rules of the database firewall filter are located in a separate text file. The following is an example of a database firewall filter configuration in maxscale.cnf. ``` [DatabaseFirewall] diff --git a/Documentation/Filters/Named-Server-Filter.md b/Documentation/Filters/Named-Server-Filter.md new file mode 100644 index 000000000..3e41092de --- /dev/null +++ b/Documentation/Filters/Named-Server-Filter.md @@ -0,0 +1,92 @@ +Named Server Filter + +# Overview + +The **namedserverfilter** is a filter module for MaxScale which is able to route queries to servers based on regular expression matches. + +# Configuration + +The configuration block for the Named Server filter requires the minimal filter options in it’s section within the maxscale.cnf file, stored in /etc/maxscale.cnf. + +``` +[NamedServerFilter] +type=filter +module=namedserverfilter +match=some string +server=server2 + +[MyService] +type=service +router=readwritesplit +servers=server1,server2 +user=myuser +passwd=mypasswd +filters=NamedServerFilter +``` + +## Filter Options + +The named server filter accepts the options ignorecase or case. These define if the pattern text should take the case of the string it is matching against into consideration or not. + +## Filter Parameters + +The named server filter requires two mandatory parameters to be defined. + +### `match` + +A parameter that can be used to match text in the SQL statement which should be replaced. + +``` +match=TYPE[ ]*= +``` + +If the filter option ignorecase is used all regular expressions are evaluated with the option to ignore the case of the text, therefore a match option of select will match both type, TYPE and any form of the word with upper or lowercase characters. + +### `server` + +This is the server where matching queries will be router. The server should be in use by the service which uses this filter. + +``` +server=server2 +``` + +### `source` + +The optional source parameter defines an address that is used to match against the address from which the client connection to MaxScale originates. Only sessions that originate from this address will have the match and replacement applied to them. + +``` +source=127.0.0.1 +``` + +### `user` + +The optional user parameter defines a user name that is used to match against the user from which the client connection to MaxScale originates. Only sessions that are connected using this username will have the match and replacement applied to them. + +``` +user=john +``` + +## Examples + +### Example 1 - Route queries targeting a specific table to a server + +This will route all queries matching the regular expression ` *from *users` to the server named *server2*. The filter will ignore character case in queries. + +A query like `SELECT * FROM users` would be routed to server2 where as a query like `SELECT * FROM accounts` would be routed according to the normal rules of the router. + +``` +[NamedServerFilter] +type=filter +module=namedserverfilter +match= *from *users +options=ignorecase +server=server2 + +[MyService] +type=service +router=readwritesplit +servers=server1,server2 +user=myuser +passwd=mypasswd +filters=NamedServerFilter +``` diff --git a/Documentation/filters/Query-Log-All-Filter.md b/Documentation/Filters/Query-Log-All-Filter.md similarity index 97% rename from Documentation/filters/Query-Log-All-Filter.md rename to Documentation/Filters/Query-Log-All-Filter.md index e0c71e13f..c06b97f6b 100644 --- a/Documentation/filters/Query-Log-All-Filter.md +++ b/Documentation/Filters/Query-Log-All-Filter.md @@ -6,7 +6,7 @@ The Query Log All (QLA) filter is a filter module for MaxScale that is ## Configuration -The configuration block for the QLA filter requires the minimal filter options in it's section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf. +The configuration block for the QLA filter requires the minimal filter options in it's section within the maxscale.cnf file, stored in /etc/maxscale.cnf. ``` [MyLogFilter] type=filter diff --git a/Documentation/filters/RabbitMQ-Consumer-Client.md b/Documentation/Filters/RabbitMQ-Consumer-Client.md similarity index 100% rename from Documentation/filters/RabbitMQ-Consumer-Client.md rename to Documentation/Filters/RabbitMQ-Consumer-Client.md diff --git a/Documentation/filters/RabbitMQ-Filter.md b/Documentation/Filters/RabbitMQ-Filter.md similarity index 87% rename from Documentation/filters/RabbitMQ-Filter.md rename to Documentation/Filters/RabbitMQ-Filter.md index f23313d0d..3d04702ac 100644 --- a/Documentation/filters/RabbitMQ-Filter.md +++ b/Documentation/Filters/RabbitMQ-Filter.md @@ -5,9 +5,9 @@ This filter is designed to extract queries and transform them into a canonical f ## Configuration -The configuration block for the **mqfilter** filter requires the minimal filter options in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf. Although the filter will start, it will use the default values which only work with a freshly installed RabbitMQ server and use its default values. This setup is mostly intended for testing the filter. +The configuration block for the **mqfilter** filter requires the minimal filter options in it’s section within the maxscale.cnf file, stored in /etc/maxscale.cnf. Although the filter will start, it will use the default values which only work with a freshly installed RabbitMQ server and use its default values. This setup is mostly intended for testing the filter. -The following is an example of a mqfilter configuration in the MaxScale.cnf file used for actual logging of queries to a RabbitMQ broker on a different host. +The following is an example of a mqfilter configuration in the maxscale.cnf file used for actual logging of queries to a RabbitMQ broker on a different host. ``` [RabbitMQ] diff --git a/Documentation/filters/Regex-Filter.md b/Documentation/Filters/Regex-Filter.md similarity index 96% rename from Documentation/filters/Regex-Filter.md rename to Documentation/Filters/Regex-Filter.md index 8a6036c46..ce0f54358 100644 --- a/Documentation/filters/Regex-Filter.md +++ b/Documentation/Filters/Regex-Filter.md @@ -6,7 +6,7 @@ The regex filter is a filter module for MaxScale that is able to rewrite query c # Configuration -The configuration block for the Regex filter requires the minimal filter options in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf. +The configuration block for the Regex filter requires the minimal filter options in it’s section within the maxscale.cnf file, stored in /etc/maxscale.cnf. ``` [MyRegexFilter] diff --git a/Documentation/filters/Tee-Filter.md b/Documentation/Filters/Tee-Filter.md similarity index 94% rename from Documentation/filters/Tee-Filter.md rename to Documentation/Filters/Tee-Filter.md index a8a95a5cc..c90fd814f 100644 --- a/Documentation/filters/Tee-Filter.md +++ b/Documentation/Filters/Tee-Filter.md @@ -6,7 +6,7 @@ The tee filter is a filter module for MaxScale is a "plumbing" fitting in the Ma # Configuration -The configuration block for the TEE filter requires the minimal filter parameters in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf, that defines the filter to load and the service to send the duplicates to. Currently the tee filter does not support multi-statements. +The configuration block for the TEE filter requires the minimal filter parameters in it’s section within the maxscale.cnf file, stored in /etc/maxscale.cnf, that defines the filter to load and the service to send the duplicates to. Currently the tee filter does not support multi-statements. ``` [DataMartFilter] diff --git a/Documentation/filters/Top-N-Filter.md b/Documentation/Filters/Top-N-Filter.md similarity index 98% rename from Documentation/filters/Top-N-Filter.md rename to Documentation/Filters/Top-N-Filter.md index d51ca43db..f20d9cc3c 100644 --- a/Documentation/filters/Top-N-Filter.md +++ b/Documentation/Filters/Top-N-Filter.md @@ -6,7 +6,7 @@ The top filter is a filter module for MaxScale that monitors every SQL statement # Configuration -The configuration block for the TOP filter requires the minimal filter options in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf. +The configuration block for the TOP filter requires the minimal filter options in it’s section within the maxscale.cnf file, stored in /etc/maxscale.cnf. ``` [MyLogFilter] diff --git a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md index 30a06fcdb..96147c0f1 100644 --- a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md +++ b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md @@ -4,7 +4,7 @@ You will need a number of tools and libraries in order to achieve this. * cmake version 2.8.12 or later -* gcc recommended version 4.4.7 or later +* gcc recommended version 4.4.7 or later (MariaDB 10 libraries require gcc 4.7 or newer) * libaio @@ -22,13 +22,13 @@ After following the instructions on that site you should have a working MariaDB The full list of dependencies for the most common distributions is provided in this section. If your system is not listed here, MaxScale building isn't guaranteed to be compatible but might still be successful. -## RHEL, CentOS and Fedora +## RHEL and CentOS -You will need to install all of the following packages for all versions of RHEL, CentOS and Fedora. +You will need to install all of the following packages for all versions of RHEL and CentOS. ``` gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool -openssl-devel libaio libaio-devel librabbitmq-devel +openssl-devel libaio libaio-devel librabbitmq-devel libcurl-devel pcre-devel ``` In addition, if you wish to to build an RPM package include: @@ -39,7 +39,7 @@ rpm-build There are also some version specific packages you need to install. -#### RHEL 6, 7, CentOS 6, 7, Fedora: +#### RHEL 6, 7, CentOS 6, 7: ``` libedit-devel @@ -51,24 +51,18 @@ libedit-devel mariadb-devel mariadb-embedded-devel ``` -#### RHEL 5, 6, CentOS 5, 6, Fedora 19, 20 +#### RHEL 5, 6, CentOS 5, 6 ``` MariaDB-devel MariaDB-server ``` -#### Fedora 19, 20 - -``` -systemtap-sdt-devel -``` - ## Ubuntu and Debian These packages are required on all versions of Ubuntu and Debian. ``` build-essential libssl-dev libaio-dev ncurses-dev bison - cmake perl libtool librabbitmq-dev + cmake perl libtool librabbitmq-dev libcurl-dev libpcre3-dev ``` If you want to build a DEB package, you will also need: @@ -87,16 +81,13 @@ You will also need some version specific packages. #### Earlier versions of Ubuntu or Debian -For these, you will need to obtain the MariaDB embedded library. It has to be manually extracted from the tarball. But first ascertain what version of glibc is installed. Run the command: +For these, you will need to obtain the MariaDB embedded library. It has to be manually extracted from the tarballs at the MariaDB site. But first ascertain what version of glibc is installed. Run the command: ``` dpkg -l | grep libc6 ``` -which will show the version number. If the version is less than 2.14 you should obtain the library from: -[https://downloads.mariadb.org/interstitial/mariadb-5.5.41/bintar-linux-x86_64/mariadb-5.5.41-linux-x86_64.tar.gz](https://downloads.mariadb.org/interstitial/mariadb-5.5.41/bintar-linux-x86_64/mariadb-5.5.41-linux-x86_64.tar.gz). -Otherwise, from: -[https://downloads.mariadb.org/interstitial/mariadb-5.5.41/bintar-linux-glibc_214-x86_64/mariadb-5.5.41-linux-glibc_214-x86_64.tar.gz](https://downloads.mariadb.org/interstitial/mariadb-5.5.41/bintar-linux-glibc_214-x86_64/mariadb-5.5.41-linux-glibc_214-x86_64.tar.gz) +which will show the version number. For versions older than 2.14 you should obtain the library which supports GLIBC versions older than 2.14 and for newer versions, the library which supports newer GLIBC versions should be used. The suggested location for extracting the tarball is `/usr` so the operation can be done by the following commands: @@ -107,22 +98,6 @@ The suggested location for extracting the tarball is `/usr` so the operation can where /path/to/mariadb.library.tar.gz is replaced by the actual path and name of the downloaded tarball. -## OpenSUSE - -At the time this guide was written, the MariaDB development packages for OpenSUSE were broken and the build failed. - -The packages required are: - -``` -gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl -make libtool libopenssl-devel libaio libaio-devel -libedit-devel librabbitmq-devel - MariaDB-devel MariaDB-client MariaDB-server -``` - -If zypper ask which MariaDB client should be installed `MariaDB-client` or `mariadb-client` - please select `MariaDB-client`. This is the package provided by the MariaDB repository. - # Obtaining the MaxScale Source Code Now clone the GitHub project to your machine either via the web interface, your favorite graphical interface or the git command line @@ -152,16 +127,15 @@ wipe the build directory clean without the danger of deleting important files wh something goes wrong. Building 'out-of-source' also allows you to have multiple configurations of MaxScale at the same time. -The default values that CMake uses can be found in the 'macros.cmake' file. -If you wish to change these, edit the 'macros.cmake' file or define the -variables manually at configuration time. +The default values that MaxScale uses for CMake can be found in the 'macros.cmake' file under the `cmake` folder. +If you wish to change these, edit the 'macros.cmake' file or define the variables manually at configuration time. To display all CMake variables with their descriptions: ``` cmake .. -LH ``` -This is a useful command if you have your libraries installed in non-standard locations. +This is a useful command if you have your libraries installed in non-standard locations and need to provide them manually. When you are ready to run cmake, provide the following command: @@ -241,7 +215,7 @@ $ make install This will result in an installation being created which is identical to that which would be achieved by installing the binary package. -By default, MaxScale installs to `/usr/local/mariadb-maxscale` and places init.d scripts and ldconfig files into their folders. Change the `CMAKE_INSTALL_PREFIX` variable to your desired installation directory and set `WITH_SCRIPTS=N` to prevent the init.d script and ldconfig file installation. +When building from source, MaxScale installs to `/usr/local/` and places init.d scripts and ldconfig files into their folders. Change the `CMAKE_INSTALL_PREFIX` variable to your desired installation directory and set `WITH_SCRIPTS=N` to prevent the init.d script and ldconfig file installation. Other useful targets for Make are `documentation`, which generates the Doxygen documentation, and `uninstall` which uninstall MaxScale binaries after an install. diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 888432fd2..8077791ff 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -27,16 +27,16 @@ connection failover| When a connection currently being used between MaxScale and The MaxScale configuration is read from a file which can be located in a number of placing, MaxScale will search for the configuration file in a number of locations. -1. If the environment variable `MAXSCALE_HOME` is set then MaxScale will look for a configuration file called `MaxScale.cnf` in the directory `$MAXSCALE_HOME/etc`. +1. Location given with the --configdir= command line argument -2. If `MAXSCALE_HOME` is not set or the configuration file is not in the location above MaxScale will look for a file in `/etc/MaxScale.cnf`. - -Alternatively MaxScale can be started with the `-c` flag and the path of the MaxScale home directory tree. +2. MaxScale will look for a configuration file called `maxscale.cnf` in the directory `/etc/maxscale.cnf` An explicit path to a configuration file can be passed by using the `-f` option to MaxScale. The configuration file itself is based on the ".ini" file format and consists of various sections that are used to build the configuration, these sections define services, servers, listeners, monitors and global settings. +Please see the section about [Protocol Modules](#protocol-modules) for more details about MaxScale and the default directories where modules will be searched for. + ### Global Settings The global settings, in a section named `[MaxScale]`, allow various parameters that affect MaxScale as a whole to be tuned. Currently the only setting that is supported is the number of threads to use to handle the network traffic. MaxScale will also accept the section name of `[gateway]` for global settings. This is for backward compatibility with versions prior to the naming of MaxScale. @@ -101,6 +101,54 @@ log_debug=1 To disable the log use the value 0 and to enable it use the value 1. +#### `logdir` + +Set the directory where the logfiles are stored. The folder needs to be both readable and writable by the user running MaxScale. + +``` +logdir=/tmp/ +``` + +#### `datadir` + +Set the directory where the data files used by MaxScale are stored. Modules can write to this directory and for example the binlogrouter uses this folder as the default location for storing binary logs. + +``` +datadir=/home/user/maxscale_data/ +``` + +#### `libdir` + +Set the directory where MaxScale looks for modules. The library directory is the only directory that MaxScale uses when it searches for modules. If you have custom modules for MaxScale, make sure you have them in this folder. + +``` +libdir=/home/user/lib64/ +``` + +#### `cachedir` + +Configure the directory MaxScale uses to store cached data. An example of cached data is the authentication data fetched from the backend servers. MaxScale stores this in case a connection to the backend server is not possible. + +``` +cachedir=/tmp/maxscale_cache/ +``` + +#### `piddir` + +Configure the directory for the PID file for MaxScale. This file contains the Process ID for the running MaxScale process. + +``` +piddir=/tmp/maxscale_cache/ +``` + +#### `language` + +Set the folder where the errmsg.sys file is located in. MaxScale will look for the errmsg.sys file installed with MaxScale from this folder. + +``` +language=/home/user/lang/ +``` + ### Service A service represents the database service that MaxScale offers to the clients. In general a service consists of a set of backend database servers and a routing algorithm that determines how MaxScale decides to send statements or route connections to those backend servers. @@ -193,7 +241,7 @@ Query OK, 0 rows affected (0.00 sec) #### `passwd` -The passwd parameter provides the password information for the above user and may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the MaxScale.cnf file. This user must be capable of connecting to the backend database and executing these SQL statements to load database names and grants from the backends: +The passwd parameter provides the password information for the above user and may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the maxscale.cnf file. This user must be capable of connecting to the backend database and executing these SQL statements to load database names and grants from the backends: * `SELECT user, host, password,Select_priv FROM mysql.user`. * `SELECT user, host, db FROM mysql.db` @@ -278,6 +326,62 @@ Example: connection_timeout=300 ``` +### Service and SSL + +This section describes configuration parameters for services that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter with either `enabled` or `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this service can be encrypted with SSL. + +#### `ssl` + +This enables SSL connections to the service. If this parameter is set to either `required` or `enabled` and the three certificate files can be found (these are explained afterwards), then client connections will be encrypted with SSL. If the parameter is `enabled` then both SSL and non-SSL connections can connect to this service. If the parameter is set to `required` then only SSL connections can be used for this service and non-SSL connections will get an error when they try to connect to the service. + +#### `ssl_key` + +The SSL private key the service should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled services. + +#### `ssl_cert` + +The SSL certificate the service should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled services. + +#### `ssl_ca_cert` + +This is the Certificate Authority file. It will be used to verify that both the client and the server certificates are valid. This is a required parameter for SSL enabled services. + +### `ssl_version` + +This parameter controls the level of encryption used. Accepted values are: + * SSLv3 + * TLSv10 + * TLSv11 + * TLSv12 + * MAX + +### `ssl_cert_verification_depth` + +The maximum length of the certificate authority chain that will be accepted. Accepted values are positive integers. + +``` +# Example +ssl_cert_verification_depth=10 +``` + +Example SSL enabled service configuration: + +``` +[ReadWriteSplitService] +type=service +router=readwritesplit +servers=server1,server2,server3 +user=myuser +passwd=mypasswd +ssl=required +ssl_cert=/home/markus/certs/server-cert.pem +ssl_key=/home/markus/certs/server-key.pem +ssl_ca_cert=/home/markus/certs/ca.pem +ssl_version=TLSv12 +``` + +This configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided. + ### Server Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters. @@ -318,7 +422,7 @@ The monitor has a username and password that is used to connect to all servers f monitorpw=mymonitorpasswd ``` -The monpasswd parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the MaxScale.cnf file. +The monpasswd parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the maxscale.cnf file. ### Listener @@ -356,6 +460,21 @@ The `socket` option may be included in a listener definition, this configures th If a socket option and an address option is given then the listener will listen on both the specific IP address and the Unix socket. +#### `persistpoolmax` + +The `persistpoolmax` parameter defaults to zero but can be set to an integer value for a back end server. +If it is non zero, then when a DCB connected to a back end server is discarded by the +system, it will be held in a pool for reuse, remaining connected to the back end server. +If the number of DCBs in the pool has reached the value given by `persistpoolmax` then +any further DCB that is discarded will not be retained, but disconnected and discarded. + +#### `persistmaxtime` + +The `persistmaxtime` parameter defaults to zero but can be set to an integer value +indicating a number of seconds. A DCB placed in the persistent pool for a server will +only be reused if the elapsed time since it joined the pool is less than the given +value. Otherwise, the DCB will be discarded and the connection closed. + ### Filter Filters provide a means to manipulate or process requests as they pass through MaxScale between the client side protocol and the query router. A filter should be defined in a section with a type of filter. @@ -467,7 +586,7 @@ Individual servers may define override values for the user and password the moni #### `passwd` -The password parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the `MaxScale.cnf` file. +The password parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the `maxscale.cnf` file. #### `monitor_interval` @@ -540,7 +659,7 @@ Default value is `2`. Write Timeout is the timeout in seconds for each attempt t ## Protocol Modules -The protocols supported by MaxScale are implemented as external modules that are loaded dynamically into the MaxScale core. These modules reside in the directory `$MAXSCALE_HOME/modules`, if the environment variable `$MAXSCALE_HOME` is not set it defaults to `/usr/local/mariadb-maxscale`. It may also be set by passing the `-c` option on the MaxScale command line. +The protocols supported by MaxScale are implemented as external modules that are loaded dynamically into the MaxScale core. These modules reside in the directory `/usr/lib64/maxscale`. The location can be overridden with the `libdir=PATH` parameter under the `[maxscale]` section. It may also be set by passing the `-B PATH` or `--libdir=PATH` option on the MaxScale command line. ### MySQLClient @@ -1024,7 +1143,7 @@ MariaDB [mysql]> grant REPLICATION CLIENT on *.* to 'maxscalemon'@'maxscalehost' Query OK, 0 rows affected (0.00 sec) ``` -MySQL monitor fetches the `@@server_id` variable and other informations from `SHOW SLAVE STATUS` in order to compute the replication topology tree that may include intermediate master servers, called relay servers. +MySQL monitor fetches the `@@server_id` variable and other information from `SHOW SLAVE STATUS` in order to compute the replication topology tree that may include intermediate master servers, called relay servers. The *Master* server used by router modules is the so called "root master": a server that has the `SERVER_MASTER` status bit set and it's at the lowest level of the replication depth. @@ -1036,7 +1155,7 @@ Please note, those two options are not enabled by default. ### galeramon -The Galeramon monitor is a simple router designed for use with MySQL Galera cluster. To execute the galeramon monitor an entry as shown below should be added to the MaxScale configuration file. +The Galeramon monitor is a simple monitor designed for use with MySQL Galera cluster. To execute the galeramon monitor an entry as shown below should be added to the MaxScale configuration file. ``` [Galera Monitor] @@ -1259,7 +1378,7 @@ before being sent to the server. Note that the text in the match string is case- The **tee** filter is a filter module for MaxScale that acts as a "plumbing" fitting in the MaxScale filter toolkit. It can be used in a filter pipeline of a service to make a copy of requests from the client and dispatch a copy of the request to another service within MaxScale. -The configuration block for the **tee** filter requires the minimal filter parameters in its section within the `MaxScale.cnf` file that defines the filter to load and the service to send the duplicates to. +The configuration block for the **tee** filter requires the minimal filter parameters in its section within the `maxscale.cnf` file that defines the filter to load and the service to send the duplicates to. ``` [ArchiveFilter] @@ -1274,7 +1393,7 @@ In addition parameters may be added to define patterns to match against to eithe The top filter is a filter module for MaxScale that monitors every SQL statement that passes through the filter. It measures the duration of that statement, the time between the statement being sent and the first result being returned. The top N times are kept, along with the SQL text itself and a list sorted on the execution times of the query is written to a file upon closure of the client session. -The configuration block for the **top** filter requires the minimal filter options in its section within the `MaxScale.cnf` file, stored in `$MAXSCALE_HOME/etc/MaxScale.cnf`. +The configuration block for the **top** filter requires the minimal filter options in its section within the `maxscale.cnf` file, stored in `/etc/maxscale.cnf`. ``` [MyLogFilter] @@ -1286,31 +1405,6 @@ count=10 In addition parameters may be added to define patterns to match against to either include or exclude particular SQL statements to be duplicated. You may also define that the filter is only active for connections from a particular source or when a particular user is connected. -## Encrypting Passwords - -Passwords stored in the MaxScale.cnf file may optionally be encrypted for added security. This is done by creation of an encryption key on installation of MaxScale. Encryption keys may be created manually by executing the maxkeys utility with the argument of the filename to store the key. - - maxkeys $MAXSCALE_HOME/etc/.secrets - -Changing the encryption key for MaxScale will invalidate any currently encrypted keys stored in the MaxScale.cnf file. - -### Creating Encrypted Passwords - -Encrypted passwords are created by executing the maxpasswd command with the password you require to encrypt as an argument. The environment variable `MAXSCALE_HOME` must be set, or MaxScale must be installed in the default location before maxpasswd can be executed. - - maxpasswd MaxScalePw001 - 61DD955512C39A4A8BC4BB1E5F116705 - -The output of the maxpasswd command is a hexadecimal string, this should be inserted into the MaxScale.cnf file in place of the ordinary, plain text, password. MaxScale will determine this as an encrypted password and automatically decrypt it before sending it the database server. - -``` -[Split Service] -type=service -router=readwritesplit -servers=server1,server2,server3,server4 -user=maxscale -password=61DD955512C39A4A8BC4BB1E5F116705 -``` ## Reloading Configuration @@ -1382,7 +1476,7 @@ and short notations ## Error Reporting -MaxScale is designed to be executed as a service, therefore all error reports, including configuration errors, are written to the MaxScale error log file. MaxScale will log to a set of files in the directory `$MAXSCALE_HOME/log`, the only exception to this is if the log directory is not writable, in which case a message is sent to the standard error descriptor. +MaxScale is designed to be executed as a service, therefore all error reports, including configuration errors, are written to the MaxScale error log file. By default, MaxScale will log to a set of files in the directory `/var/log/maxscale`, the only exception to this is if the log directory is not writable, in which case a message is sent to the standard error descriptor. ### Troubleshooting diff --git a/Documentation/Getting-Started/Getting-Started-With-MaxScale.md b/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md similarity index 66% rename from Documentation/Getting-Started/Getting-Started-With-MaxScale.md rename to Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md index 24ee54b22..bf752b1fd 100644 --- a/Documentation/Getting-Started/Getting-Started-With-MaxScale.md +++ b/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md @@ -1,4 +1,4 @@ -# Getting Started With MariaDB MaxScale +# MariaDB MaxScale Installation Guide ## First Steps With MaxScale @@ -8,23 +8,31 @@ In this introduction to MaxScale the aim is to take the reader from the point of The simplest way to install MaxScale is to use one of the binary packages that are available for download from the MariaDB website. -* Simply go to [www.mariadb.com](http://www.mariadb.com) +* Simply go to [http://www.mariadb.com/my_portal/download](http://www.mariadb.com/my_portal/download) -* Select the Downloads option from the Resources menu +* Sign in to MariaDB.com -* Find and click on the button "Download MariaDB MaxScale Binaries" +* Follow the instructions at the top of the page. -* Find the section on that page entitled MariaDB MaxScale +![image alt text](images/getting_started.png) -* Select your operating system from the drop down box +If you want to install only MaxScale, further down you will find the product specific download pages. Click on the MariaDB MaxScale link and follow the distribution specific instructions. -* Instructions that are specific for your operating system will then appear +![image alt text](images/getting_started2.png) -![image alt text](images/image_1.png) +After you have installed MaxScale, you can start it. -* Follow these instructions to install MaxScale on your machine +``` +systemctl start maxscale.service +``` -Upon successful completion of the installation process you have a version of MaxScale that is missing only a configuration file before it can be started. +If your system does not support systemd you can start MaxScale using the installed init.d script. + +``` +service maxscale start +``` + +An example configuration file is installed into the `/etc/` folder. This file should be changed according to your needs. ## Building MaxScale From Source Code @@ -34,6 +42,8 @@ Alternatively you may download the MaxScale source and build your own binaries. The first step in configuring your MaxScale is to determine what it is you want to achieve with your MaxScale and what environment it will run in. The later is probably the easiest starting point for choosing which configuration route you wish to take. There are two distinct database environments which the first GA release of MaxScale supports; MySQL Master/Slave Replication clusters and Galera Cluster. +For more details, refer to the [Configuration Guide](Configuration-Guide.md). + ### Master/Slave Replication Clusters There are two major configuration options available to use MaxScale with a MySQL Replication cluster; connection routing with separate read and write connections, or read/write splitting with a single connection. A separate tutorial is available for each of these configurations that describes how to build the configuration file for MaxScale that will work with your environment. @@ -54,21 +64,51 @@ It is also possible to use the Read/Write Splitter with Galera. Although it is n As well as the four major configuration choices outlined above there are also other configurations sub-options that may be mixed with those to provide a variety of different configuration and functionality. The MaxScale filter concept allows the basic configurations to be built upon in a large variety of ways. A separate filter tutorial is available that discusses the concept and gives some examples of ways to use filters. +## Encrypting Passwords + +Passwords stored in the maxscale.cnf file may optionally be encrypted for added security. This is done by creation of an encryption key on installation of MaxScale. Encryption keys may be created manually by executing the maxkeys utility with the argument of the filename to store the key. The default location MaxScale stores the keys is `/var/lib/maxscale`. + +``` + # Usage: maxkeys [PATH] +maxkeys /var/lib/maxscale/ +``` + +Changing the encryption key for MaxScale will invalidate any currently encrypted keys stored in the maxscale.cnf file. + +### Creating Encrypted Passwords + +Encrypted passwords are created by executing the maxpasswd command with the location of the .secrets file and the password you require to encrypt as an argument. + +``` +# Usage: maxpasswd PATH PASSWORD +maxpasswd /var/lib/maxscale/ MaxScalePw001 +61DD955512C39A4A8BC4BB1E5F116705 +``` + +The output of the maxpasswd command is a hexadecimal string, this should be inserted into the maxscale.cnf file in place of the ordinary, plain text, password. MaxScale will determine this as an encrypted password and automatically decrypt it before sending it the database server. + +``` +[Split Service] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxscale +password=61DD955512C39A4A8BC4BB1E5F116705 +``` + ## Running MaxScale MaxScale consists of a core executable and a number of modules that implement the different protocols and routing algorithms. These modules are built as shared objects that are loaded on demand. In order for MaxScale to find these -modules it will search using a predescribed search path. The rules are: +modules it will search using a configurable search path. The priority of these paths are: - 1. Look in the current directory for the module - 2. Look in $MAXSCALE_HOME/modules - 3. Look in /usr/local/mariadb-maxscale/modules - -Configuration is read by default from the file $MAXSCALE_HOME/etc/MaxScale.cnf, /etc/MaxScale.cnf. An example file is included in in the installation and can be found in the etc/ folder within the MaxScale installation. The default value of MAXSCALE_HOME can be overridden by using the -c flag on the command line. This should be immediately followed by the path to the MaxScale home directory. The -f flag can be used on the command line to set the name and the location of the configuration file. Without path expression the file is read from \$MAXSCALE_HOME/etc directory. + 1. Look in the directory defined with --libdir=PATH during startup + 2. Look in the directory defined with libdir=PATH in the configuration file under the [maxscale] section + 3. Look in default directory in /usr/lib64/maxscale +Configuration is read by default from the file /etc/maxscale.cnf. An example file is included in in the installation and can be found in the /usr/share/maxscale folder within the MaxScale installation. The -f flag can be used on the command line to set the name and the location of the configuration file. The -C flag can be used to set the directory where the configuration file is searched for. Without the -f or -C flags, the file is read from the /etc directory. ## Administration Of MaxScale -There are various administration tasks that may be done with MaxScale, a client command, maxadmin, is available that will interact with a running MaxScale and allow the status of MaxScale to be monitored and give some control of the MaxScale functionality. There is a separate reference guide for the maxadmin utility and also a short administration tutorial that covers the common administration tasks that need to be done with MaxScale. - +There are various administration tasks that may be done with MaxScale, a client command, maxadmin, is available that will interact with a running MaxScale and allow the status of MaxScale to be monitored and give some control of the MaxScale functionality. There is [a separate reference guide](../Reference/MaxAdmin.md) for the maxadmin utility and also [a short administration tutorial](../Tutorials/Administration-Tutorial.md) that covers the common administration tasks that need to be done with MaxScale. diff --git a/Documentation/Getting-Started/images/getting_started.png b/Documentation/Getting-Started/images/getting_started.png new file mode 100644 index 000000000..d90c3a2dd Binary files /dev/null and b/Documentation/Getting-Started/images/getting_started.png differ diff --git a/Documentation/Getting-Started/images/getting_started2.png b/Documentation/Getting-Started/images/getting_started2.png new file mode 100644 index 000000000..bac4926a5 Binary files /dev/null and b/Documentation/Getting-Started/images/getting_started2.png differ diff --git a/Documentation/Monitors/Galera-Monitor.md b/Documentation/Monitors/Galera-Monitor.md new file mode 100644 index 000000000..4f0fc47c1 --- /dev/null +++ b/Documentation/Monitors/Galera-Monitor.md @@ -0,0 +1,158 @@ +# Galera Monitor + +## Overview + +The Galera Monitor is a monitoring module for MaxScale that monitors a Galera cluster. It detects whether nodes are a part of the cluster and if they are in sync with the rest of the cluster. It can also assign master and slave roles inside MaxScale, allowing Galera clusters to be used with modules designed for traditional master-slave clusters. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## Galera Monitor optional parameters + +These are optional parameters specific to the Galera Monitor. + +### `disable_master_failback` + +If a node marked as master inside MaxScale happens to fail and the master status is assigned to another node MaxScale will normally return the master status to the original node after it comes back up. With this option enabled, if the master status is assigned to a new node it will not be reassigned to the original node for as long as the new master node is running. + +``` +disable_master_failback=true +``` + +### `available_when_donor` + +This option only has an effect if there is a single Galera node being backed up an XtraBackup instance. This causes the initial node to go into Donor state which would normally prevent if from being marked as a valid server inside MaxScale. If this option is enabled, a single node in Donor state where the method is XtraBackup will be kept in Synced state. + +``` +available_when_donor=true +``` + +### `disable_master_role_setting` + +This disables the assignment of master and slave roles to the Galera cluster nodes. If this option is enabled, Synced is the only status assigned by this monitor. + +``` +disable_master_role_setting=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. The user which is used to run MaxScale should have execution rights to the file itself and the directory it resides in. + +``` +script=/home/user/script.sh +``` + +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +### `use_priority` + +Enable interaction with server priorities. This will allow the monitor to deterministically pick the write node for the monitored Galera cluster and will allow for controlled node replacement. + +``` +use_priority=true +``` + +## Interaction with Server Priorities + +If the `use_priority` option is set and a server is configured with the `priority=` parameter, galeramon will use that as the basis on which the master node is chosen. This requires the `disable_master_role_setting` to be undefined or disabled. The server with the lowest value in `priority` will be chosen as the master node when a replacement Galera node is promoted to a master server inside MaxScale. + +Here is an example with two servers. + +``` +[node-1] +type=server +address=192.168.122.101 +port=3306 +priority=1 + +[node-2] +type=server +address=192.168.122.102 +port=3306 +priority=3 + +[node-3] +type=server +address=192.168.122.103 +port=3306 +priority=2 +``` + +In this example `node-1` is always used as the master if available. If `node-1` is not available, then the next node with the highest priority rank is used. In this case it would be `node-3`. If both `node-1` and `node-3` were down, then `node-2` would be used. Nodes without priority are considered as having the lowest priority rank and will be used only if all nodes with priority ranks are not available. + +With priority ranks you can control the order in which MaxScale chooses the master node. This will allow for a controlled failure and replacement of nodes. + +## Script events + +Here is a table of all possible event types and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has gone down +server_up|A server with no assigned role has come up +synced_down|A synced Galera node has come up +synced_up|A synced Galera node has gone down +lost_master|A server lost Master status +lost_slave|A server lost Slave status +lost_synced|A Galera node lost synced status +new_master|A new Master was detected +new_slave|A new Slave was detected +new_synced|A new synced Galera node was detected diff --git a/Documentation/Monitors/MM-Monitor.md b/Documentation/Monitors/MM-Monitor.md new file mode 100644 index 000000000..c2bd5a566 --- /dev/null +++ b/Documentation/Monitors/MM-Monitor.md @@ -0,0 +1,107 @@ +# Multi-Master Monitor + +## Overview + +The Multi-Master Monitor is a monitoring module for MaxScale that monitors Master-Master replication. It assigns master and slave roles inside MaxScale based on whether the read_only parameter on a server is set to off or on. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[Multi-Master Monitor] +type=monitor +module=mmmon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## Multi-Master Monitor optional parameters + +These are optional parameters specific to the Multi-Master Monitor. + +### `detect_stale_master` + +Allow previous master to be available even in case of stopped or misconfigured replication. This allows services that depend on master and slave roles to continue functioning as long as the master server is available. + +This is a situation which can happen if all slave servers are unreachable or the replication breaks for some reason. + +``` +detect_stale_master=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. The user which is used to run MaxScale should have execution rights to the file itself and the directory it resides in. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +## Script events + +Here is a table of all possible event types and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has gone down +server_up|A server with no assigned role has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +new_master|A new Master was detected +new_slave|A new Slave was detected diff --git a/Documentation/Monitors/MySQL-Monitor.md b/Documentation/Monitors/MySQL-Monitor.md new file mode 100644 index 000000000..8e5bdeac5 --- /dev/null +++ b/Documentation/Monitors/MySQL-Monitor.md @@ -0,0 +1,155 @@ +# MySQL Monitor + +## Overview + +The MySQL Monitor is a monitoring module for MaxScale that monitors a Master-Slave replication cluster. It assigns master and slave roles inside MaxScale according to the actual replication tree in the cluster. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## MySQL Monitor optional parameters + +These are optional parameters specific to the MySQL Monitor. + +### `detect_replication_lag` + +Detect replication lag between the master and the slaves. This allows the routers to route read queries to only slaves that are up to date. + +``` +detect_replication_lag=true +``` + +### `detect_stale_master` + +Allow previous master to be available even in case of stopped or misconfigured +replication. This allows services that depend on master and slave roles to continue functioning as long as the master server is available. + +This is a situation which can happen if all slave servers are unreachable or the replication breaks for some reason. + +``` +detect_stale_master=true +``` + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. The user which is used to run MaxScale should have execution rights to the file itself and the directory it resides in. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +### `mysql51_replication` + +Enable support for MySQL 5.1 replication monitoring. This is needed if a MySQL server older than 5.5 is used as a slave in replication. + +``` +mysql51_replication=true +``` + +## Script events + +Here is a table of all possible event types and their descriptions. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has gone down +server_up|A server with no assigned role has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +new_master|A new Master was detected +new_slave|A new Slave was detected + + +## Example 1 - Monitor script + +Here is an example shell script which sends an email to an admin when a server goes down. + +``` +#!/usr/bin/env bash + +#This script assumes that the local mail server is configured properly +#The second argument is the event type +event=${$2/.*=/} +server=${$3/.*=/} +message="A server has gone down at `date`." +echo $message|mail -s "The event was $event for server $server." admin@my.org + +``` + +Here is a monitor configuration that only triggers the script when a master or a slave server goes down. + +``` +[Database Monitor] +type=monitor +module=mysqlmon +servers=server1,server2 +script=mail_to_admin.sh +events=master_down,slave_down +``` + +When a master or a slave server goes down, the script is executed, a mail is sent and the administrator will be immediately notified of any possible problems. +This is just a simple example showing what you can do with MaxScale and monitor scripts. diff --git a/Documentation/Monitors/NDB-Cluster-Monitor.md b/Documentation/Monitors/NDB-Cluster-Monitor.md new file mode 100644 index 000000000..a87ff35a8 --- /dev/null +++ b/Documentation/Monitors/NDB-Cluster-Monitor.md @@ -0,0 +1,102 @@ +# NDB Cluster Monitor + +## Overview + +The MySQL Cluster Monitor is a monitoring module for MaxScale that monitors a MySQL Cluster. It assigns a NDB status for the server if it is a part of a MySQL Cluster. + +## Configuration + +A minimal configuration for a monitor requires a set of servers for monitoring and a username and a password to connect to these servers. The user requires the REPLICATION CLIENT privilege to successfully monitor the state of the servers. + +``` +[MySQL Cluster Monitor] +type=monitor +module=ndbclustermon +servers=server1,server2,server3 +user=myuser +passwd=mypwd + +``` + +## Optional parameters for all monitors + +Here are optional parameters that are common for all the monitors. + +### `monitor_interval` + +This is the time the monitor waits between each cycle of monitoring. The default value of 10000 milliseconds (10 seconds) should be lowered if you want a faster response to changes in the server states. The value is defined in milliseconds and the smallest possible value is 100 milliseconds. + +``` +monitor_interval=2500 +``` + +### `backend_connect_timeout` + +This parameter controls the timeout for connecting to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 3 seconds. + +``` +backend_connect_timeout=6 +``` + +### `backend_write_timeout` + +This parameter controls the timeout for writing to a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 2 seconds. + +``` +backend_write_timeout=4 +``` + +### `backend_read_timeout` + +This parameter controls the timeout for reading from a monitored server. It is in seconds and the minimum value is 1 second. The default value for this parameter is 1 seconds. + +``` +backend_read_timeout=2 +``` + +## MySQL Cluster Monitor optional parameters + +These are optional parameters specific to the MySQL Cluster Monitor. + +### `script` + +This script will be executed when a server changes its state. The parameter should be an absolute path to the script or it should be in the executable path. The user which is used to run MaxScale should have execution rights to the file itself and the directory it resides in. + +``` +script=/home/user/script.sh +``` + +This script will be called with the following command line arguments. + +``` + --event= --initiator= --nodelist= +``` +### `events` + +A list of event names which cause the script to be executed. If this option is not defined, all events cause the script to be executed. The list must contain a comma separated list of event names. + +``` +events=master_down,slave_down +``` + +## Script events + +Here is a table of all possible event types and their descriptions that the MySQL Cluster monitor can be called with. + +Event Name|Description +----------|---------- +master_down|A Master server has gone down +master_up|A Master server has come up +slave_down|A Slave server has gone down +slave_up|A Slave server has come up +server_down|A server with no assigned role has gone down +server_up|A server with no assigned role has come up +ndb_down|A MySQL Cluster node has gone down +ndb_up|A MySQL Cluster node has come up +lost_master|A server lost Master status +lost_slave|A server lost Slave status +lost_ndb|A MySQL Cluster node lost node membership +new_master|A new Master was detected +new_slave|A new Slave was detected +new_ndb|A new MySQL Cluster node was found + diff --git a/Documentation/Reference/Debug-And-Diagnostic-Support.md b/Documentation/Reference/Debug-And-Diagnostic-Support.md index 34fda6653..12298624c 100644 --- a/Documentation/Reference/Debug-And-Diagnostic-Support.md +++ b/Documentation/Reference/Debug-And-Diagnostic-Support.md @@ -1679,7 +1679,7 @@ Loaded 34 users. ## Reload config -The reload config command can be used to force MaxScale to re-read the MaxScale.cnf and update itself to the latest configuration defined in that configuration file. It is also possible to force the reading of the configuration file by sending a HangUp signal (SIGHUP) to the maxscale process. +The reload config command can be used to force MaxScale to re-read the maxscale.cnf and update itself to the latest configuration defined in that configuration file. It is also possible to force the reading of the configuration file by sending a HangUp signal (SIGHUP) to the maxscale process. **MaxScale>** reload config @@ -1711,7 +1711,7 @@ User admin already exists. **MaxScale>**** ** -If you should forget or lose the the account details you may simply remove the passwd file in $MAXSCALE_HOME/etc and the system will revert to the default behavior with admin/mariadb as the account. +If you should forget or lose the the account details you may simply remove the passwd file in /var/cache/maxscale and the system will revert to the default behavior with admin/mariadb as the account. ## Enable/disable log @@ -1745,7 +1745,7 @@ MaxScale generates output of its behavior to four distinct logs, error, messages ## Log contents -By default all log files are located in : $MAXSCALE_HOME/log and named as : +By default all log files are located in : /var/log/maxscale and named as : skygw_errW.log, skygw_msgX.log, skygw_traceY.log and skygw_debugZ.log @@ -1789,7 +1789,7 @@ MariaDB Corporation MaxScale /home/jdoe/bin/develop/log/skygw_msg1.log Tue Dec 2014-12-09 14:47:05 Log directory : /home/jdoe/bin/develop/log -2014-12-09 14:47:05 Configuration file : /home/jdoe/bin/develop/etc/MaxScale.cnf +2014-12-09 14:47:05 Configuration file : /home/jdoe/bin/develop/etc/maxscale.cnf 2014-12-09 14:47:05 Initialise CLI router module V1.0.0. @@ -1837,7 +1837,7 @@ MariaDB Corporation MaxScale /home/jdoe/bin/develop/log/skygw_msg1.log Tue Dec ### Trace log -Trace log includes information about available servers and their states, client sessions, queries being executed, routing decisions and other routing related data. Trace log can be found from the same directory with other logs but it is physically stored elsewhere, to OSs shared memory to reduce the latency caused by logging. The location of physical file is : /dev/shm//skygw_traceX.log where ‘X’ is the same sequence number as in the file name in the $MAXSCALE_HOME/log directory. +Trace log includes information about available servers and their states, client sessions, queries being executed, routing decisions and other routing related data. Trace log can be found from the same directory with other logs but it is physically stored elsewhere, to OSs shared memory to reduce the latency caused by logging. The location of physical file is : /dev/shm//skygw_traceX.log where ‘X’ is the same sequence number as in the file name in the /var/log/maxscale directory. Individual trace log entry looks similar to those in other logs but there is some difference too. Some log entries include a number within square brackets to specify which client session they belong to. For example: @@ -1923,11 +1923,11 @@ In the log, session’s life cycle is covered by annotating its beginning and th The log files are located in -$MAXSCALE_HOME/log +/var/log/maxscale by default. If, however, trace and debug logs are enabled, only a soft link is created there. MaxScale process creates a directory under -/dev/shm/ +/dev/shm/maxscale. where it stores the physical trace and debug log files. Link and physical files share the same name. These logs consume the main memory of the host they run on so it is important to archive or remove them periodically to avoid unnecessary main-memory consumption. diff --git a/Documentation/Reference/Hint-Syntax.md b/Documentation/Reference/Hint-Syntax.md index 0cb7cbaee..a037365e0 100644 --- a/Documentation/Reference/Hint-Syntax.md +++ b/Documentation/Reference/Hint-Syntax.md @@ -1,42 +1,126 @@ # Hint Syntax -Use either ’-- ’ (notice the whitespace) or ’#’ after the semicolon or ’/* .. */’ before -the semicolon. +## Enabling routing hints -The MySQL manual doesn’t specify if comment blocks, i.e. ’/* .. */’, should contain a w -hitespace character before or after the tags. +To enable routing hints for a service, the hintfilter module needs to be configured and the filter needs to be applied to the service. -All hints must start with the ’maxscale tag’: - -- maxscale - -The hints right now have two types, ones that route to a server and others that contain +Here is an example service which has the hint filter configured and applied. + +``` +[Read Service] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxuser +passwd=maxpwd +filter=Hint + +[Hint] +type=filter +module=hintfilter + +``` + +## Comments and comment types + +The client connection will need to have comments enabled. For example the `mysql` command line client has comments disabled by default. + +For comment types, use either `-- ` (notice the whitespace) or `#` after the semicolon or `/* .. */` before the semicolon. All comment types work with routing hints. + +The MySQL manual doesn`t specify if comment blocks, i.e. `/* .. */`, should contain a w +hitespace character before or after the tags, so adding whitespace at both the start and the end is advised. + +## Hint body + +All hints must start with the `maxscale` tag. + +``` +-- maxscale +``` + +The hints have two types, ones that route to a server and others that contain name-value pairs. -Routing queries to a server: +###Routing destination hints + +These hints will instruct the router to route a query to a certain type of a server. +``` -- maxscale route to [master | slave | server ] +``` -The name of the server is the same as in MaxScale.cnf +A `master` value in a routing hint will route the query to a master server. This can be used to direct read queries to a master server for a up-to-date result with no replication lag. A `slave` value will route the query to a slave server. A `server` value will route the query to a named server. The value of needs to be the same as the server section name in maxscale.cnf. -Creating a name-value pair: +### Name-value hints + +These control the behavior and affect the routing decisions made by the router. + +``` -- maxscale = +``` -Currently the only accepted parameter is -’max_slave_replication_lag’ +Currently the only accepted parameter is `max_slave_replication_lag`. This will route the query to a server with lower replication lag then what is defined in the hint value. + +## Hint stack Hints can be either single-use hints, which makes them affect only one query, or named hints, which can be pushed on and off a stack of active hints. Defining named hints: + +``` -- maxscale prepare +``` Pushing a hint onto the stack: + +``` -- maxscale begin +``` Popping the topmost hint off the stack: + +``` -- maxscale end +``` You can define and activate a hint in a single command using the following: + +``` -- maxscale begin +``` You can also push anonymous hints onto the stack which are only used as long as they are on the stack: --- maxscale begin \ No newline at end of file + +``` +-- maxscale begin +``` + +## Examples + +### Example 1 - Routing SELECT queries to master + +In this example, MaxScale is configured with the readwritesplit router and the hint filter. + +``` +[ReadWriteService] +type=service +router=readwritesplit +servers=server1,server2 +user=maxuser +passwd=maxpwd +filters=Hint + +[Hint] +type=filter +module=hintfilter +``` + +Behind MaxScale is a master server and a slave server. If there is replication lag between the master and the slave, read queries sent to the slave might return old data. To guarantee up-to-date data, we can add a routing hint to the query. + +``` +INSERT INTO table1 VALUES ("John","Doe",1); +SELECT * from table1; -- maxscale route to master +``` + +The first INSERT query will be routed to the master. The following SELECT query would normally be routed to the slave but with the added routing hint it will be routed to the master. This way we can do an INSERT and a SELECT right after it and still get up-to-date data. diff --git a/Documentation/Reference/MaxAdmin.md b/Documentation/Reference/MaxAdmin.md index b8889192a..d46d1f61e 100644 --- a/Documentation/Reference/MaxAdmin.md +++ b/Documentation/Reference/MaxAdmin.md @@ -6,9 +6,24 @@ Mark Riddoch -Last Updated: 13th February 2015 +Last Updated: 24th June 2015 +[Overview](#overview) +[Running MaxAdmin](#running) +[Getting Help](#help) +[Working with Services](#services) +[Working with Servers](#servers) +[Working with Sessions](#sessions) +[Descriptor Control Blocks](#dcbs) +[Working with Filters](#filters) +[Working with Monitors](#monitors) +[Working With Administration Interface Users](#interface) +[MaxScale Status Commands](#statuscommands) +[Administration Commands](#admincommands) +[Configuring MaxScale to Accept MaxAdmin Connections](#connections) +[Tuning MaxScale](#tuning) + # Overview MaxAdmin is a simple client interface that can be used to interact with the MaxScale server, it allows the display of internal MaxScale statistics, status and control of MaxScale operations. @@ -21,6 +36,7 @@ MaxAdmin supports * Execution of command scripts + # Running MaxAdmin The MaxAdmin client application may be run in two different modes, either as an interactive command shell for executing commands against MaxScale or by passing commands on the MaxAdmin command line itself. @@ -146,6 +162,7 @@ This mechanism can be used to provide a means of passwords entry into maxadmin o The .maxadmin file may be made read only to protect any passwords written to that file. + # Getting Help A help system is available that describes the commands available via the administration interface. To obtain a list of all commands available simply type the command help. @@ -185,6 +202,7 @@ To see more detail on a particular command, and a list of the sub commands of th sessions List all the active sessions within MaxScale MaxScale> + # Working With Services A service is a very important concept in MaxScale as it defines the mechanism by which clients interact with MaxScale and can attached to the backend databases. A number of commands exist that allow interaction with the services. @@ -282,6 +300,7 @@ A stopped service may be restarted by using the restart service command. MaxScale> restart service "Split Service" MaxScale> + # Working With Servers The server represents each of the instances of MySQL or MariaDB that a service may use. @@ -314,10 +333,25 @@ It is possible to see more details regarding a given server using the show serve Port: 3307 Server Version: 5.5.25-MariaDB-log Node Id: 124 - Number of connections: 0 - Current no. of conns: 0 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 MaxScale> +If the server has a non-zero value set for the server configuration item "persistpoolmax", +then additional information will be shown: + + Persistent pool size: 1 + Persistent measured pool size: 1 + Persistent pool max size: 10 + Persistent max time (secs): 3660 + +The distinction between pool size and measured pool size is that the first is a +counter that is updated when operations affect the persistent connections pool, +whereas the measured size is the result of checking how many persistent connections +are currently in the pool. It can be slightly different, since any expired +connections are removed during the check. + ## Setting The State Of A Server MaxScale maintains a number of status bits for each server that is configured, these status bits are normally maintained by the monitors, there are two commands in the user interface that are used to manually maintain these bits also; the set server and clear server commands. @@ -358,6 +392,14 @@ All status bits, with the exception of the maintenance bit, will be set by the m MaxScale> clear server server3 maintenance MaxScale> +## Viewing the persistent pool of DCB + +The DCBs that are in the pool for a particular server can be displayed (in the +format described below in the DCB section) with a command like: + + MaxScale> show persistent server1 + + # Working With Sessions The MaxScale session represents the state within MaxScale. Sessions are dynamic entities and not named in the configuration file, this means that sessions can not be easily named within the user interface. The sessions are referenced using ID values, these are actually memory address, however the important thing is that no two session have the same ID. @@ -408,6 +450,7 @@ Once the session ID has been determined using one of the above method it is poss Connected: Wed Jun 25 15:27:21 2014 MaxScale> + # Descriptor Control Blocks The Descriptor Control Block or DCB is a very important entity within MaxScale, it represents the state of each connection within MaxScale. A DCB is allocated for every connection from a client, every network listener and every connection to a backend database. Statistics for each of these connections are maintained within these DCB’s. @@ -448,6 +491,10 @@ The details of an individual DCB can be obtained by use of the show dcb command MaxScale> show dcb 0x727900 DCB: 0x727900 DCB state: DCB in the polling loop + Username: somename + Protocol: MySQLBackend + Server Status: Master, running + Role: Request Handler Connected to: 127.0.0.1 Owning Session: 0x727da0 Statistics: @@ -457,8 +504,15 @@ The details of an individual DCB can be obtained by use of the show dcb command No. of Accepts: 0 No. of High Water Events: 0 No. of Low Water Events: 0 + Added to persistent pool: Jun 24 09:09:56 MaxScale> +The information Username, Protocol, Server Status are not +always relevant, and will not be shown when they are null. +The time the DCB was added to the persistent pool is only shown +for a DCB that is in a persistent pool. + + # Working with Filters Filters allow the request contents and result sets from a database to be modified for a client connection, pipelines of filters can be created between the client connection and MaxScale router modules. @@ -553,6 +607,7 @@ The show session command will include details for each of the filters in use wit The data displayed varies from filter to filter, the example above is the top filter. This filter prints a report of the current top queries at the time the show session command is run. + # Working With Monitors Monitors are used to monitor the state of databases within MaxScale in order to supply information to other modules, specifically the routers within MaxScale. @@ -591,7 +646,7 @@ Some monitors provide a replication heartbeat mechanism that monitors the delay MaxScale> enable heartbeat "MySQL Monitor" MaxScale> -Please note that changes made via this interface will not persist across restarts of MaxScale. To make a permanent change edit the MaxScale.cnf file. +Please note that changes made via this interface will not persist across restarts of MaxScale. To make a permanent change edit the maxscale.cnf file. Enabling the replication heartbeat mechanism will add the display of heartbeat information in the show server output @@ -648,6 +703,7 @@ A monitor that has been shutdown may be restarted using the restart monitor comm Monitored servers: 127.0.0.1:3306, 127.0.0.1:3307, 127.0.0.1:3308, 127.0.0.1:3309 MaxScale> + # Working With Administration Interface Users A default installation of MaxScale allows connection to the administration interface using the username of admin and the password mariadb. This username and password stay in effect as long as no other users have been created for the administration interface. As soon as the first user is added the use of admin/mariadb as login credentials will be disabled. @@ -691,6 +747,7 @@ To remove a user the command remove user is used, it must also be called with th User maria has been successfully removed. MaxScale> + # MaxScale Status Commands A number of commands exists that enable the internal MaxScale status to be revealed, these commands give an insight to how MaxScale is using resource internally and are used to allow the tuning process to take place. @@ -738,6 +795,7 @@ Internally MaxScale has a housekeeper thread that is used to perform periodic t Load Average | Repeated | 10 | Wed Nov 19 15:10:51 2014 MaxScale> + # Administration Commands ## What Modules Are In use? @@ -786,19 +844,20 @@ Two commands are provided to change the logging levels within MaxScale, disable MaxScale> disable log debug MaxScale> -Please note that changes made via this interface will not persist across restarts of MaxScale. To make a permanent change edit the MaxScale.cnf file. +Please note that changes made via this interface will not persist across restarts of MaxScale. To make a permanent change edit the maxscale.cnf file. ## Reloading The Configuration -A command, reload config, is available that will cause MaxScale to reload the MaxScale.cnf configuration file. +A command, reload config, is available that will cause MaxScale to reload the maxscale.cnf configuration file. ## Shutting Down MaxScale The MaxScale server may be shutdown using the shutdown maxscale command. + # Configuring MaxScale to Accept MaxAdmin Connections -In order to allow the use of the MaxAdmin client interface the service must be added to the MaxScale.cnf file of the Maxscale server. The CLI service itself must be added and a listener for the maxscaled protocol. +In order to allow the use of the MaxAdmin client interface the service must be added to the maxscale.cnf file of the Maxscale server. The CLI service itself must be added and a listener for the maxscaled protocol. The default entries required are shown below. @@ -815,6 +874,7 @@ The default entries required are shown below. Note that this uses the default port of 6603 and confines the connections to localhost connections only. Remove the address= entry to allow connections from any machine on your network. Changing the port from 6603 will mean that you must allows pass a -p option to the MaxAdmin command. + # Tuning MaxScale The way that MaxScale does it’s polling is that each of the polling threads, as defined by the threads parameter in the configuration file, will call epoll_wait to obtain the events that are to be processed. The events are then added to a queue for execution. Any thread can read from this queue, not just the thread that added the event. diff --git a/Documentation/Reference/MaxScale-HA-with-lsyncd.md b/Documentation/Reference/MaxScale-HA-with-lsyncd.md new file mode 100644 index 000000000..c9e6ac332 --- /dev/null +++ b/Documentation/Reference/MaxScale-HA-with-lsyncd.md @@ -0,0 +1,184 @@ +# MaxScale HA with Lsyncd + +***This guide was written for lsyncd 2.1.5.*** + +This document guides you in setting up multiple MaxScale instances and synchronizing the configuration files with lsyncd. Lsyncd is a rsync wrapper which can synchronize files across the network. The lsyncd daemon uses a configuration file to control the files to synchronize and the remote targets where these files are synchronized to. + +Copying the configuration file and running the lsyncd daemon on all the hosts keeps all the configuration files in sync. Modifications in the configuration file on one of the hosts will be copied on the other hosts. This allows adinistrators to easily provide a highly available, disaster resistant MaxScale installation with up-to-date configuration files on all the hosts. + +### Requirements +You will need: + +* Access to the remote hosts. +* MaxScale installed on all systems +* Configured maxscale.cnf file in /etc +* SSH daemon and clients installed on all hosts + +The installation and configuration of MaxScale is covered in other documents. + +## Creating SSH keys + +For lsyncd to work, we will need to either use an existing set of SSH keys or to create a new set of keys. The creation and copying of keys needs to be repeated on all of the hosts. + +If you already have a SSH key generated, you can skip this next step and go to the Copying Keys part. + +### Generating keys + +To generate a new set of SSH keys, we will use `ssh-keygen`. + +``` +[root@localhost ~]# ssh-keygen +Generating public/private rsa key pair. +Enter file in which to save the key (/root/.ssh/id_rsa): +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in /root/.ssh/id_rsa. +Your public key has been saved in /root/.ssh/id_rsa.pub. +The key fingerprint is: +f4:99:0a:cc:d4:ac:ea:ed:ff:0d:bb:e5:87:3e:38:df root@localhost.localdomain +The key's randomart image is: ++--[ RSA 2048]----+ +| | +| o | +| . + | +| + o . o | +| = S + | +| . . . | +| . . .... | +| . . o*o.. | +| ..o...+==oE | ++-----------------+ + +``` + +The keys will be generated in the .ssh folder and will automatically be used by ssh. + +### Copying keys + +To copy the SSH keys to the remote host we will use `ssh-copy-id`. + +Use the username and host of the remote server you wish to synchronize MaxScale's configuration files to. For example, if the server's address is 192.168.122.100 and the user we use for synchronization us `user` we can use the following command. + +``` +ssh-copy-id user@192.168.122.100 +``` + +Repeat the last command with the usernames and addresses of all the remote hosts you want to synchronize the configuration files to. + +## Installing lsyncd + +You will need to install lsyncd on all of the hosts for changes in the configuration file on one of the nodes to be synchronized to the other nodes. + +You can install lsyncd with either a package manager or by building from source code. This guide demonstrates installation using a package manager and those looking to build lsyncd from source should refer to its documentation: https://github.com/axkibe/lsyncd/wiki/Manual-to-Lsyncd-2.1.x + +Installing with Yum: + +``` +yum install lsyncd +``` + +Installing with Apt: + +``` +apt-get install lsyncd +``` + +## Creating the Lsyncd configuration file + +Lsyncd uses a configuration file to determine where to read files from and where to synchronize them if changes in them occur. Lsyncd is written in Lua and the configuration file is also valid Lua code. + +Here is an example configuration file with descriptions on the meaning of the values in it. + +``` +-- Lsyncd will log to these two files. +settings{ + logfile = "/var/log/maxscale/maxscale-ha.log", + statusFile = "/var/log/maxscale/maxscale-ha-status.log" +} + +-- Copy and paste the sync section and change the host value to add new remote targets. +sync{ +default.rsyncssh, + +-- This is where the maxscale.cnf file is copied from. +source="/etc", + +-- This is the user and host where the maxscale.cnf is copied to. +-- Change this to the user and destination host where you want maxscale.cnf to be synchronized to. +host="user@192.168.122.100", + +-- This is where the maxscale.cnf is copied to on the remote host. +targetdir="/etc", + +-- This is an optional section which defines a custom SSH port. Uncomment to enable. +-- ssh={port=2222}, + +-- These are values passed to rsync. Only change these if you know what you are doing. +rsync={ + compress=true, + _extra = {[[--filter=+ *maxscale.cnf]], + [[--filter=- **]] + } + } +} +``` + +The most important part is the `sync` section which defines a target for synchronization. The `default.rsyncssh` tells lsyncd to synchronize files using SSH. + +The `source` parameter tells lsyncd where to read the files from. This should be the location of the maxscale.cnf file. The `host` parameter defines the host where the files should be synchronized to and the user account lsyncd should use when synchronizing the files. The `targetdir` parameter defines the local directory on the remote target where the files should be synchronized to. This value should be the location on the remote host where the maxscale.cnf file is searched from. By default, this is the `/etc` folder. + +The optional `ssh` parameter and its sub-parameter `port`define a custom port for the SSH connection. Most users do not need this parameterer. The `rsycn` parameter contains an arra of options that are passed to the rsycn executable. These should not be changed unless you specifically know what you are doing. For more information on the options passed to rsync read the rsync(1) manpage. + +You can add multiple remote targets by defining multiple `sync` sections. Here is an example with two sync sections defining different hosts that have MaxScale installed and whose configuration files should be kep in sync. + +``` +settings{ + logfile = "/var/log/maxscale/maxscale-ha.log", + statusFile = "/var/log/maxscale/maxscale-ha-status.log" +} + +sync{ +default.rsyncssh, +source="/etc", +host="maxuser@192.168.0.50", +targetdir="/etc", +rsync={ + compress=true, + _extra = {[[--filter=+ *maxscale.cnf]], + [[--filter=- **]] + } + } +} + + +sync{ +default.rsyncssh, +source="/etc", +host="syncuser@192.168.122.105", +targetdir="/etc", +rsync={ + compress=true, + _extra = {[[--filter=+ *maxscale.cnf]], + [[--filter=- **]] + } + } +} +``` + +## Starting Lsyncd + +Starting lsyncd can be done from the command line or through a init script. To start syncd from the command like, execute the `lsyncd` command and pass the configuration file as the only parameter. + +By default lsyncd will search for the configuration file in `/etc/lsyncd.conf`. By placing the configuration file we created in the `/etc` folder, we can start lsyncd with the following command. + +``` +service lsyncd start +``` + +Here is an example which start lsyncd and reads the configuration options from the `lsyncd.cnf` file. + +``` +lsyncd lsyncd.cnf +``` + +For more information on the lsyncd executable and its options, please see the --help output of lsyncd or the lsyncd(1) manpage. diff --git a/Documentation/Reference/MaxScale-and-SSL.md b/Documentation/Reference/MaxScale-and-SSL.md new file mode 100644 index 000000000..f293085c1 --- /dev/null +++ b/Documentation/Reference/MaxScale-and-SSL.md @@ -0,0 +1,15 @@ +# MaxScale and SSL + +MaxScale supports client side SSL connections. Enabling is done on a per service basis and each service has its own set of certificates. + +## SSL Options + +Here are the options which relate to SSL and certificates. +Parameter|Values |Description +---------|-----------|-------- +ssl | disabled, enabled, required |`disable` disables SSL, `enabled` enables SSL for client connections but still allows non-SSL connections and `required` requires SSL from all client connections. With the `required` option, client connections that do not use SSL will be rejected. +ssl_cert | path to file |Path to server certificate +ssl_key | path to file |Path to server private key +ssl_ca_cert | path to file |Path to Certificate Authority file +ssl_version|SSLV3,TLSV10,TLSV11,TLSV12,MAX| The SSL method level, defaults to highest available encryption level which is TLSv1.2 +ssl_cert_verify_depth|integer|Certificate authority certificate verification depth, default is 100. diff --git a/Documentation/Release-Notes/MaxScale-0.5-Release-Notes.md b/Documentation/Release-Notes/MaxScale-0.5-Release-Notes.md index 3ae56ce9e..5f09d7bfd 100644 --- a/Documentation/Release-Notes/MaxScale-0.5-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-0.5-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 0.5 Alpha Release Notes 0.5 Alpha diff --git a/Documentation/Release-Notes/MaxScale-0.6-Release-Notes.md b/Documentation/Release-Notes/MaxScale-0.6-Release-Notes.md index ff7033e4d..4b2c445c3 100644 --- a/Documentation/Release-Notes/MaxScale-0.6-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-0.6-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 0.6 Alpha Release Notes 0.6 Alpha diff --git a/Documentation/Release-Notes/MaxScale-0.7-Release-Notes.md b/Documentation/Release-Notes/MaxScale-0.7-Release-Notes.md index bc43ee00d..21d9320ec 100644 --- a/Documentation/Release-Notes/MaxScale-0.7-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-0.7-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 0.7 Alpha Release Notes 0.7 Alpha diff --git a/Documentation/Release-Notes/MaxScale-1.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.0-Release-Notes.md index a15757e0f..4ce52f6c3 100644 --- a/Documentation/Release-Notes/MaxScale-1.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.0-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 1.0 Beta Release Notes 1.0 Beta diff --git a/Documentation/Release-Notes/MaxScale-1.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.0.1-Release-Notes.md index 6a34ec3a9..5586696a4 100644 --- a/Documentation/Release-Notes/MaxScale-1.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.0.1-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 1.0.1 Beta Release Notes 1.0.1 Beta @@ -107,11 +107,11 @@ A number of bug fixes have been applied between the 1.0 beta release and this re 479 - Undefined filter reference in MaxScale.cnf causes a crash + Undefined filter reference in maxscale.cnf causes a crash 410 - MaxScale.cnf server option is not parsed for spaces + maxscale.cnf server option is not parsed for spaces 417 diff --git a/Documentation/Release-Notes/MaxScale-1.0.3-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.0.3-Release-Notes.md index 67c5e3ece..db8329366 100644 --- a/Documentation/Release-Notes/MaxScale-1.0.3-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.0.3-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes +# MariaDB MaxScale 1.0.3 Release Notes 1.0.3 GA diff --git a/Documentation/Release-Notes/MaxScale-1.0.4-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.0.4-Release-Notes.md index db341e893..9103c830a 100644 --- a/Documentation/Release-Notes/MaxScale-1.0.4-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.0.4-Release-Notes.md @@ -1,4 +1,4 @@ -# MaxScale Release Notes +# MariaDB MaxScale 1.0.4 Release Notes 1.0.4 GA diff --git a/Documentation/Release-Notes/MaxScale-1.0.5-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.0.5-Release-Notes.md index 4b65fd92f..e979ff6c1 100644 --- a/Documentation/Release-Notes/MaxScale-1.0.5-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.0.5-Release-Notes.md @@ -1,4 +1,4 @@ -MaxScale Release Notes 1.0.5 GA +# MariaDB MaxScale 1.0.5 Release Notes This document details the changes in version 1.0.5 since the release of the 1.0.4 GA of the MaxScale product. diff --git a/Documentation/Release-Notes/MaxScale-1.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.1-Release-Notes.md index c3ed66b73..e196335b9 100644 --- a/Documentation/Release-Notes/MaxScale-1.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.1-Release-Notes.md @@ -1,4 +1,4 @@ -# MaxScale Release Notes +# MariaDB MaxScale 1.1 Release Notes ## 1.1 GA diff --git a/Documentation/Release-Notes/MaxScale-1.1.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.1.1-Release-Notes.md index 113929452..94e0e59e6 100644 --- a/Documentation/Release-Notes/MaxScale-1.1.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-1.1.1-Release-Notes.md @@ -1,6 +1,6 @@ -# MaxScale Release Notes +# MariaDB MaxScale 1.1.1 Release Notes -## 1.1 GA +## 1.1.1 GA MaxScale 1.1 is the current stable (GA) release of MaxScale. Version 1.1.1 is mainly a bug fix release introducing fixes, but also introduces some improvements to existing functionality. diff --git a/Documentation/Release-Notes/MaxScale-1.2.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-1.2.0-Release-Notes.md new file mode 100644 index 000000000..4ffdcba1f --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-1.2.0-Release-Notes.md @@ -0,0 +1,99 @@ +# MariaDB MaxScale 1.2 Release Notes + +## 1.2 GA + +This document details the changes in version 1.2 since the release of the 1.1.1 GA Release of the MaxScale product. + +###***PLEASE NOTICE: MaxScale installation directories have changed in this version*** +The 1.2 version of MaxScale differs from previos versions in its installation layout. Please take great care when upgrading MaxScale from previous versions to version 1.2. An automatic upgrade will not work due to the severe changes in the installation layout. + +## New Features + +### Non-root MaxScale +You can now run MaxScale as any user. The standard installation of a MaxScale package now creates the maxscale user and the maxscale group. + +### FHS-compliant installation +The 1.2 version of MaxScale now complies to the Filesystem Hierarchy Standard. This means that MAXSCALE_HOME is no longer necessary and directories can be moved to different locations. + +A quick list of changes in installation directories and file names: + + * Binaries go into `/usr/bin` + * Configuration files to `/etc` and the configuration file is now lower case: `maxscale.cnf` + * Logs to `/var/log/maxscale` + * The module and library directory have been combined into a single directory in `/usr/lib64/maxscale`. If you have custom modules please make sure they are located there. + * Data directory is `/var/lib/maxscale`. This is the default location for MaxScale-specific data. + * PID file can be found at `/var/run/maxscale` + +### Client side SSL encryption +MaxScale now supports SSL/TLS encrypted connections to MaxScale. + +### Launchable scripts +Now you can configure MaxScale monitor module to automatically launch a script when it detects change in the state of a backend server. The script can be any customer script defined by you to take diagnostic or reporting action. With this you can easily customize MaxScale's behavior. + +### Lsyncd configuration guide +A new tutorial has beed added which helps you keep MaxScale's configuration files in sync across multiple hosts. This allows for easier HA setups with MaxScale and guarantees up-to-date configuration files on all nodes. The tutorial can be found [here](../Reference/MaxScale-HA-with-lsyncd.md). + +## Bug fixes + +Here is a list of bugs fixed since the release of MaxScale 1.1.1. + + * [MXS-24](https://mariadb.atlassian.net/browse/MXS-24): bugzillaId-604: Module load path documentation issues ... + * [MXS-40](https://mariadb.atlassian.net/browse/MXS-40): Display logged in users + * [MXS-113](https://mariadb.atlassian.net/browse/MXS-113): MaxScale seems to fail if built against MariaDB 10.0 libraries + * [MXS-116](https://mariadb.atlassian.net/browse/MXS-116): Do not run maxscale as root. + * [MXS-117](https://mariadb.atlassian.net/browse/MXS-117): Allow configuration of the log file directory + * [MXS-125](https://mariadb.atlassian.net/browse/MXS-125): inconsistency in maxkeys/maxpassword output and parameters + * [MXS-128](https://mariadb.atlassian.net/browse/MXS-128): cyclic dependency utils -> log_manager -> utils + * [MXS-136](https://mariadb.atlassian.net/browse/MXS-136): Check for MaxScale replication heartbeat table existence before creating + * [MXS-137](https://mariadb.atlassian.net/browse/MXS-137): cannot get sql for queries with length >= 0x80 + * [MXS-139](https://mariadb.atlassian.net/browse/MXS-139): Schemarouter authentication for wildcard grants fails without optimize_wildcard + * [MXS-140](https://mariadb.atlassian.net/browse/MXS-140): strip_db_esc does not work without auth_all_servers + * [MXS-162](https://mariadb.atlassian.net/browse/MXS-162): Fix Incorrect info in Configuration Guide + * [MXS-165](https://mariadb.atlassian.net/browse/MXS-165): Concurrency issue while incrementing sessions in qlafilter + * [MXS-166](https://mariadb.atlassian.net/browse/MXS-166): Memory leak when creating a new event + * [MXS-171](https://mariadb.atlassian.net/browse/MXS-171): Allow reads on master for readwritesplit + * [MXS-176](https://mariadb.atlassian.net/browse/MXS-176): Missing dependencies in documentation + * [MXS-179](https://mariadb.atlassian.net/browse/MXS-179): Keep configuration changes in synch across MaxScale Mate Nodes + * [MXS-180](https://mariadb.atlassian.net/browse/MXS-180): MariaDB10 binlog router compatibilty + * [MXS-181](https://mariadb.atlassian.net/browse/MXS-181): Poor performance on TCP connection due to Nagle's algoritm + * [MXS-182](https://mariadb.atlassian.net/browse/MXS-182): SHOW SLAVE STATUS and maxadmin "show services" for binlog router needs updated when used with MariaDB 10 Master + * [MXS-212](https://mariadb.atlassian.net/browse/MXS-212): Stopped services accept connections + * [MXS-225](https://mariadb.atlassian.net/browse/MXS-225): RPM Debug build packages have no debugging symbols + * [MXS-227](https://mariadb.atlassian.net/browse/MXS-227): Memory leak in Galera Monitor + * [MXS-244](https://mariadb.atlassian.net/browse/MXS-244): Memory leak when using prepared statements without arguments + +## Known Issues and Limitations + +There are a number bugs and known limitations within this version of MaxScale, the most serious of this are listed below. + +* MaxScale can not manage authentication that uses wildcard matching in hostnames in the mysql.user table of the backend database. The only wildcards that can be used are in IP address entries. + +* When users have different passwords based on the host from which they connect MaxScale is unable to determine which password it should use to connect to the backend database. This results in failed connections and unusable usernames in MaxScale. + +* LONGBLOB are currently not supported. + +* Galera Cluster variables, such as @@wsrep_node_name, are not resolved by the embedded MariaDB parser. + +* The Database Firewall filter does not support multi-statements. Using them will result in an error being sent to the client. + +## Packaging + +Both RPM and Debian packages are available for MaxScale in addition to the tar based releases previously distributed we now provide + +* CentOS/RedHat 5 + +* CentOS/RedHat 6 + +* CentOS/RedHat 7 + +* Debian 6 + +* Debian 7 + +* Ubuntu 12.04 LTS + +* Ubuntu 14.04 LTS + +* SuSE Linux Enterprise 11 + +* SuSE Linux Enterprise 12 diff --git a/Documentation/Routers/ReadConnRoute.md b/Documentation/Routers/ReadConnRoute.md new file mode 100644 index 000000000..50d5b80ae --- /dev/null +++ b/Documentation/Routers/ReadConnRoute.md @@ -0,0 +1,104 @@ +# Readconnroute + +This document provides anoverview of the **readconnroute** router module and its intended use case scenarios. It also displays all router configuration parameters with their descriptions. + +## Overview + +The readconnroute router provides simple and lightweight load balancing across a set of servers. The router can also be configured to balance connections based on a weighting parameter defined in the server's section. + +## Configuration + +Readconnroute router-specific settings are specified in the configuration file of MaxScale in its specific section. The section can be freely named but the name is used later as a reference from listener section. + +The configuration consists of mandatory and optional parameters. + +## Mandatory parameters + +**`type`** specifies the type of service. For readconnroute module the type is `router`: + + type=router + +**`router`** specifies the router module to be used. For readconnroute the value is `readconnroute`: + + router=readconnroute + +**`servers`** provides a list of servers, which the router will connect to: + + servers=server1,server2,server3 + +**NOTE: Each server on the list must have its own section in the configuration file where it is defined.** + +**`user`** is the username the router session uses for accessing backends in order to load the content of the `mysql.user` table (and `mysql.db` and database names as well) and optionally for creating, and using `maxscale_schema.replication_heartbeat` table. + +**`passwd`** specifies corresponding password for the user. Syntax for user and passwd is: + +``` +user= +passwd= +``` + +## Optional parameters + +The **`weightby`** parameter defines the name of the value which is used to calculate the weights of the servers. Here is an example server configuration with the `serv_weight` parameter used as the weighting parameter. + +``` +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend +serv_weight=3 + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend +serv_weight=1 + +[Read Service] +type=service +router=readconnroute +servers=server1,server2 +weightby=serv_weight +``` + +With this configuration and a heavy query load, the server *server1* will get most of the connections and about a third of the remaining queries are routed to the second server. With server weights, you can assing secondary servers that are only used when the primary server is under heavy load. + +Without the weightby parameter, each connection counts as a single connection. With a weighting parameter, a single connection received its weight from the server's own weighting parameter divided by the sum of all weighting parameters in all the configured servers. + +If we use the previous configuration as an example, the sum of the `serv_weight` parameter is 4. Server1 would receive a weight of `3/4=75%` and server2 would get `1/4=25%`. This means that server1 would get 75% of the connections and server2 would get 25% of the connections. + +**`router_options`** can contain a list of valid server roles. These roles are used as the valid types of servers the router will form connections to when new sessions are created. +``` + router_options=slave +``` +Here is a list of all possible values for the `router_options`. + +Role|Description +------|--------- +master|A server assigned as a master by one of MaxScale monitors. Depending on the monitor implementation, this could be a master server of a Master-Slave replication cluster or a Write-Master of a Galera cluster. +slave|A server assigned as a slave of a master. +synced| A Galera cluster node which is in a synced state with the cluster. +ndb|A MySQL Replication Cluster node +running|A server that is up and running. All servers that MaxScale can connect to are labeled as running. + +If no `router_options` parameter is configured in the service definition, the router will use the default value of `running`. This means that it will load balance connections across all running servers defined in the `servers` parameter of the service. + +## Examples + +The most common use for the readconnroute is to provide either a read or write port for an application. This provides a more lightweight routing solution than the more complex readwritesplit router but requires the application to be able to use distinct write and read ports. + +To configure a read-only service that tolerates master failures, we first need to add a new section in to the configuration file. + +``` +[Read Service] +type=service +router=readconnroute +servers=slave1,slave2,slave3 +router_options=slave +``` + +Here the `router_options`designates slaves as the only valid server type. With this configuration, the queries are load balanced across the slave servers. + +For more complex examples of the readconnroute router, take a look at the examples in the [Tutorials](../Tutorials) folder. diff --git a/Documentation/routers/ReadWriteSplit.md b/Documentation/Routers/ReadWriteSplit.md similarity index 85% rename from Documentation/routers/ReadWriteSplit.md rename to Documentation/Routers/ReadWriteSplit.md index 52b1c11b2..7fb6d5cf0 100644 --- a/Documentation/routers/ReadWriteSplit.md +++ b/Documentation/Routers/ReadWriteSplit.md @@ -4,7 +4,9 @@ This document provides a short overview of the **readwritesplit** router module ## Overview -The **readwritesplit** router is designed to be used with a Master-Slave replication cluster. It automatically detects changes in the master server and will use the current master server of the cluster. With a Galera cluster, one can achieve a resilient setup and easy master failover by using one of the Galera nodes as a Write-Master node, where are write queries are routed, and spreading the read load over all the nodes. +The **readwritesplit** router is designed to increase the read-only processing capability of a cluster while maintaining consistency. This is achieved by splitting the query load into read and write queries. Read queries, which do not modify data, are spread across multiple nodes while all write queries will be sent to a single node. + +The router is designed to be used with a traditional Master-Slave replication cluster. It automatically detects changes in the master server and will use the current master server of the cluster. With a Galera cluster, one can achieve a resilient setup and easy master failover by using one of the Galera nodes as a Write-Master node, where all write queries are routed, and spreading the read load over all the nodes. ## Configuration @@ -18,9 +20,9 @@ The configuration consists of mandatory and optional parameters. type=router -**`service`** specifies the router module to be used. For **readwritesplit** the value is `readwritesplit`: +**`router`** specifies the router module to be used. For **readwritesplit** the value is `readwritesplit`: - service=readwritesplit + router=readwritesplit **`servers`** provides a list of servers, which must include one master and available slaves: @@ -91,6 +93,17 @@ disable_sescmd_history=true disable_slave_recovery=true ``` +**`master_accept_reads`** allows the master server to be used for reads. This is a useful option to enable if you are using a small number of servers and wish to use the master for reads as well. + +``` +# Use the master for reads +master_accept_reads=true +``` + +### Routing hints + +The readwritesplit router supports routing hints. For a detailed guide on hint syntax and functionality, please see [this](../Reference/Hint-Syntax.md) document. + ## Limitations In Master-Slave replication cluster also read-only queries are routed to master too in the following situations: diff --git a/Documentation/routers/SchemaRouter.md b/Documentation/Routers/SchemaRouter.md similarity index 97% rename from Documentation/routers/SchemaRouter.md rename to Documentation/Routers/SchemaRouter.md index d1de66b2b..1d8188889 100644 --- a/Documentation/routers/SchemaRouter.md +++ b/Documentation/Routers/SchemaRouter.md @@ -15,7 +15,7 @@ In almost all the cases these can be avoided by proper server configuration and Here is an example configuration of the schemarouter router: ``` -Shard Router] +[Shard Router] type=service router=schemarouter servers=server1,server2 @@ -64,4 +64,4 @@ The schemarouter router currently has some limitations due to the nature of the ## Examples -[Here](../../Tutorials/Simple-Sharding-Tutorial.md) is a small tutorial on how to set up a sharded database. +[Here](../Tutorials/Simple-Sharding-Tutorial.md) is a small tutorial on how to set up a sharded database. diff --git a/Documentation/Tutorials/Administration-Tutorial.md b/Documentation/Tutorials/Administration-Tutorial.md index 0fb0051b5..48e90952c 100644 --- a/Documentation/Tutorials/Administration-Tutorial.md +++ b/Documentation/Tutorials/Administration-Tutorial.md @@ -1,12 +1,15 @@ # MaxScale Administration Tutorial +Last updated 24th June 2015 + ## Common Administration Tasks The purpose of this tutorial is to introduce the MaxScale Administrator to a few of the common administration tasks that need to be performed with MaxScale. It is not intended as a reference to all the tasks that may be performed, more this is aimed as an introduction for administrators who are new to MaxScale. [Starting MaxScale](#starting) [Stopping MaxScale](#stopping) -[Checking The Status Of The MaxScale Services](#checking) +[Checking The Status Of The MaxScale Services](#checking) +[Persistent Connections](#persistent) [What Clients Are Connected To MaxScale](#clients) [Rotating Log Files](#rotating) [Taking A Database Server Out Of Use](#outofuse) @@ -15,54 +18,33 @@ The purpose of this tutorial is to introduce the MaxScale Administrator to a few ### Starting MaxScale There are several ways to start MaxScale, the most convenient mechanism is probably using the Linux service interface. When a MaxScale package is installed the package manager will also installed a script in /etc/init.d which may be used to start and stop MaxScale either directly or via the service interface. - +``` $ service maxscale start - +``` or - +``` $ /etc/init.d/maxscale start - -It is also possible to start MaxScale by executing the maxscale command itself, in this case you must ensure that the environment is correctly setup or command line options are passed. The major elements to consider are the correct setting of the MAXSCALE\_HOME directory and to ensure that LD\_LIBRARY\_PATH. The LD\_LIBRARY\_PATH should include the lib directory that was installed as part of the MaxScale installation, the MAXSCALE\_HOME should point to /usr/local/mariadb-maxscale if a default installation has been created or to the directory this was relocated to. Running the executable $MAXSCALE\_HOME/bin/maxscale will result in MaxScale running as a daemon process, unattached to the terminal in which it was started and using configuration files that it finds in the $MAXSCALE\_HOME directory. +``` +It is also possible to start MaxScale by executing the maxscale command itself. Running the executable /usr/bin/maxscale will result in MaxScale running as a daemon process, unattached to the terminal in which it was started and using configuration files that it finds in the /etc directory. Options may be passed to the MaxScale binary that alter this default behavior, this options are documented in the table below. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SwitchLong OptionDescription
-d--nodaemonRun MaxScale attached to the terminal rather than as a daemon process. This is useful for debugging purposes.
-c--homedir=Ignore the environment variable MAXSCALE_HOME and use the supplied argument instead.
-f--config=Use the filename passed as an argument instead of looking in $MAXSCALE_HOME/etc/MaxScale.cnf
-l||--log=Control where logs are written for the debug and trace level log messages. the default is to write these to a shared memory device, however using the -lfile or --log=file option will forced these to be written to regular files. Using -lstdout or --log=stdout will use the standard output for all enabled logs.
-v--versionPrint version information for MaxScale
-?--helpPrint usage information for MaxScale
+Switch|Long Option|Description +------|-----------|----------- +`-d`|`--nodaemon`|enable running in terminal process (default:disabled) +`-f FILE`|`--config=FILE`|relative or absolute pathname of MaxScale configuration file (default:/etc/maxscale.cnf) +`-l[file shm]`|`--log=[file shm]`|log to file or shared memory (default: shm) +`-L PATH`|`--logdir=PATH`|path to log file directory (default: /var/log/maxscale) +`-D PATH`|`--datadir=PATH`|path to data directory, stored embedded mysql tables (default: /var/cache/maxscale) +`-C PATH`|`--configdir=PATH`|path to configuration file directory (default: /etc/) +`-B PATH`|`--libdir=PATH`|path to module directory (default: /usr/lib64/maxscale) +`-A PATH`|`--cachedir=PATH`|path to cache directory (default: /var/cache/maxscale) +`P PATH`|`--piddir=PATH`|PID file directory +`-U USER`|`--user=USER`|run MaxScale as another user. The user ID and group ID of this user are used to run MaxScale. +`-s [yes no]`|`--syslog=[yes no]`|log messages to syslog (default:yes) +`-S [yes no]`|`--maxscalelog=[yes no]`|log messages to MaxScale log (default: yes) +`-v`|`--version`|print version info and exit +`-?`|`--help`|show this help ### Stopping MaxScale @@ -70,26 +52,27 @@ Options may be passed to the MaxScale binary that alter this default behavior, t There are numerous ways in which MaxScale can be stopped; using the service interface, killing the process or by use of the maxadmin utility. Stopping MaxScale with the service interface is simply a case of using the service stop command or calling the init.d script with the stop argument. - +``` $ service maxscale stop - +``` or - +``` $ /etc/init.d/maxscale stop - -MaxScale will also stop gracefully if it received a hangup signal, to find the process id of the MaxScale server use the ps command or read the contents of the maxscale.pid file located in the same directory as the logs. - - $ kill -HUP `cat $MAXSCALE_HOME/log/maxscale.pid` - +``` +MaxScale will also stop gracefully if it received a terminate signal, to find the process id of the MaxScale server use the ps command or read the contents of the maxscale.pid file located in the /var/run/maxscale directory. +``` + $ kill `cat /var/run/maxscale/maxscale.pid` +``` In order to shutdown MaxScale using the maxadmin command you may either connect with maxadmin in interactive mode or pass the "shutdown maxscale" command you wish to execute as an argument to maxadmin. - +``` $ maxadmin -pmariadb shutdown maxscale - +``` ### Checking The Status Of The MaxScale Services It is possible to use the maxadmin command to obtain statistics regarding the services that are configured within your MaxScale configuration file. The maxadmin command "list services" will give very basic information regarding the services that are define. This command may be either run in interactive mode or passed on the maxadmin command line. +``` $ maxadmin -pmariadb MaxScale> list services @@ -110,14 +93,46 @@ It is possible to use the maxadmin command to obtain statistics regarding the se --------------------------+----------------------+--------+--------------- MaxScale> +``` It should be noted that network listeners count as a user of the service, therefore there will always be one user per network port in which the service listens. More detail can be obtained by use of the "show service" command which is passed a service name. + +### Persistent Connections + +Where the clients who are accessing a database system through MaxScale make frequent +short connections, there may be a benefit from invoking the MaxScale Persistent +Connection feature. This is controlled by two configuration values that are specified +per server in the relevant server section of the configuration file. The configuration +options are `persistpoolmax` and `persistmaxtime`. + +Normally, when a client connection is terminated, all the related back end database +connections are also terminated. If the `persistpoolmax` options is set to a non-zero +integer, then up to that number of connections will be kept in a pool for that +server. When a new connection is requested by the system to meet a new client request, +then a connection from the pool will be used if possible. + +The connection will only be taken from the pool if it has been there for no more +than `persistmaxtime` seconds. It was also be discarded if it has been disconnected +by the back end server. Connections will be selected that match the user name and +protocol for the new request. + +Please note that because persistent connections have previously been in use, they +may give a different environment from a fresh connection. For example, if the +previous use of the connection issued "use mydatabase" then this setting will be +carried over into the reuse of the same connection. For many applications this will +not be noticeable, since each request will assume that nothing has been set and +will issue fresh requests such as "use" to establish the desired configuration. In +exceptional cases this feature could be a problem. + +It is possible to have pools for as many servers as you wish, with configuration +values in each server section. + ### What Clients Are Connected To MaxScale To determine what client are currently connected to MaxScale you can use the "list clients" command within maxadmin. This will give you IP address and the ID’s of the DCB and session for that connection. As with any maxadmin command this can be passed on the command line or typed interactively in maxadmin. - +``` $ maxadmin -pmariadb list clients Client Connections @@ -133,28 +148,27 @@ To determine what client are currently connected to MaxScale you can use the "li -----------------+------------------+----------------------+------------ $ - +``` ### Rotating Log Files MaxScale write log data into four log files with varying degrees of detail. With the exception of the error log, which can not be disabled, these log files may be enabled and disabled via the maxadmin interface or in the configuration file. The default behavior of MaxScale is to grow the log files indefinitely, the administrator must take action to prevent this. It is possible to rotate either a single log file or all the log files with a single command. When the logfile is rotated, the current log file is closed and a new log file, with an increased sequence number in its name, is created. Log file rotation is achieved by use of the "flush log" or “flush logs” command in maxadmin. - +``` $ maxadmin -pmariadb flush logs - +``` Flushes all of the logs, whereas an individual log may be flushed with the "flush log" command. - +``` $ maxadmin -pmariadb MaxScale> flush log error MaxScale> flush log trace MaxScale> - +``` This may be integrated into the Linux logrotate mechanism by adding a configuration file to the /etc/logrotate.d directory. If we assume we want to rotate the log files once per month and wish to keep 5 log files worth of history, the configuration file would look like the following. - - - - -
/usr/local/mariadb-maxscale/log/*.log { +``` +/var/log/maxscale/*.log { monthly rotate 5 missingok @@ -163,30 +177,26 @@ sharedscripts postrotate \# run if maxscale is running if test -n "`ps acx|grep maxscale`"; then -/usr/local/mariadb-maxscale/bin/maxadmin -pmariadb flush logs +/usr/bin/maxadmin -pmariadb flush logs fi endscript -}
- +} +``` One disadvantage with this is that the password used for the maxadmin command has to be embedded in the log rotate configuration file. MaxScale will also rotate all of its log files if it receives the USR1 signal. Using this the logrotate configuration script can be rewritten as - - - - -
/usr/local/mariadb-maxscale/log/*.log { +``` +/var/log/maxscale/*.log { monthly rotate 5 missingok nocompress sharedscripts postrotate -kill -USR1 `cat /usr/local/mariadb-maxscale/log/maxscale.pid` +kill -USR1 `cat /var/run/maxscale/maxscale.pid` endscript -}
+} +``` ### Taking A Database Server Out Of Use @@ -194,16 +204,16 @@ endscript MaxScale supports the concept of maintenance mode for servers within a cluster, this allows for planned, temporary removal of a database from the cluster within the need to change the MaxScale configuration. To achieve the removal of a database server you can use the set server command in the maxadmin utility to set the maintenance mode flag for the server. This may be done interactively within maxadmin or by passing the command on the command line. - +``` MaxScale> set server dbserver3 maintenance MaxScale> - +``` This will cause MaxScale to stop routing any new requests to the server, however if there are currently requests executing on the server these will not be interrupted. To bring the server back into service use the "clear server" command to clear the maintenance mode bit for that server. - +``` MaxScale> clear server dbserver3 maintenance MaxScale> - +``` Note that maintenance mode is not persistent, if MaxScale restarts when a node is in maintenance mode a new instance of MaxScale will not honour this mode. If multiple MaxScale instances are configured to use the node them maintenance mode must be set within each MaxScale instance. However if multiple services within one MaxScale instance are using the server then you only need set the maintenance mode once on the server for all services to take note of the mode change. diff --git a/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md b/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md index d75c6162a..0ebe57a2b 100644 --- a/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md +++ b/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md @@ -60,7 +60,7 @@ If you wish to use two different usernames for the two different roles of monito ### Creating Your MaxScale Configuration -MaxScale configuration is held in an ini file that is located in the file MaxScale.cnf in the directory $MAXSCALE_HOME/etc, if you have installed in the default location then this file is available in /usr/local/mariadb-maxscale/etc/MaxScale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within this directory that may be use as a basis for your configuration. +MaxScale configuration is held in an ini file that is located in the file maxscale.cnf in the directory /etc, if you have installed in the default location then this file is available in /etc/maxscale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within the /usr/share/maxscale directory that may be use as a basis for your configuration. A global, maxscale, section is included within every MaxScale configuration file; this is used to set the values of various MaxScale wide parameters, perhaps the most important of these is the number of threads that MaxScale will use to execute the code that forwards requests and handles responses for clients. @@ -89,7 +89,7 @@ In order to instruct the router to which servers it should route we must add rou The final step in the service section is to add the username and password that will be used to populate the user data from the database cluster. There are two options for representing the password, either plain text or encrypted passwords may be used. In order to use encrypted passwords a set of keys must be generated that will be used by the encryption and decryption process. To generate the keys use the maxkeys command and pass the name of the secrets file in which the keys are stored. - % maxkeys /usr/local/mariadb-maxscale/etc/.secrets + % maxkeys /var/lib/maxscale/.secrets % Once the keys have been created the maxpasswd command can be used to generate the encrypted password. @@ -178,7 +178,7 @@ or % service maxscale start -Check the error log in /usr/local/mariadb-maxscale/log to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +Check the error log in /var/log/maxscale to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. % maxadmin -pmariadb list services diff --git a/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md b/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md index df57e9390..4da69f27e 100644 --- a/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md +++ b/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md @@ -62,7 +62,7 @@ If you wish to use two different usernames for the two different roles of monito ### Creating Your MaxScale Configuration -MaxScale configuration is held in an ini file that is located in the file MaxScale.cnf in the directory $MAXSCALE_HOME/etc, if you have installed in the default location then this file is available in /usr/local/mariadb-maxscale/etc/MaxScale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within this directory that may be use as a basis for your configuration. +MaxScale configuration is held in an ini file that is located in the file maxscale.cnf in the directory /etc, if you have installed in the default location then this file is available in /etc/maxscale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within the /usr/share/maxscale directory that may be use as a basis for your configuration. A global, maxscale, section is included within every MaxScale configuration file; this is used to set the values of various MaxScale wide parameters, perhaps the most important of these is the number of threads that MaxScale will use to execute the code that forwards requests and handles responses for clients. @@ -83,7 +83,7 @@ The router for we need to use for this configuration is the readwritesplit modul The final step in the service sections is to add the username and password that will be used to populate the user data from the database cluster. There are two options for representing the password, either plain text or encrypted passwords may be used. In order to use encrypted passwords a set of keys must be generated that will be used by the encryption and decryption process. To generate the keys use the maxkeys command and pass the name of the secrets file in which the keys are stored. - % maxkeys /usr/local/mariadb-maxscale/etc/.secrets + % maxkeys /var/lib/maxscale/.secrets % Once the keys have been created the maxpasswd command can be used to generate the encrypted password. @@ -183,7 +183,7 @@ or % service maxscale start -Check the error log in /usr/local/mariadb-maxscale/log to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +Check the error log in /var/log/maxscale to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. % maxadmin -pmariadb list services diff --git a/Documentation/Tutorials/MaxScale-Information-Schema.md b/Documentation/Tutorials/MaxScale-Information-Schema.md index c8ce5c08a..370960287 100644 --- a/Documentation/Tutorials/MaxScale-Information-Schema.md +++ b/Documentation/Tutorials/MaxScale-Information-Schema.md @@ -5,54 +5,65 @@ The plugin is capable of returning data in one of two ways, either as MySQL resu # Configuration -The plugin is configured in the MaxScale.cnf plugin in much the same way as any other router service is configured, there needs to be a service section in the configuration file and also listeners defined for that service. The service does not however require any backend servers to be associated with it, or any monitors. +The plugin is configured in the maxscale.cnf plugin in much the same way as any other router service is configured, there needs to be a service section in the configuration file and also listeners defined for that service. The service does not however require any backend servers to be associated with it, or any monitors. The service entry needs to define the service name, the type as service and the router module to load. The specified user, with the password (plain or encrypted via maxpassword utility) is allowed to connect via MySQL protocol. Currently the user can connect to maxinfo from any remote IP and to localhost as well. +``` [MaxInfo] type=service router=maxinfo user=monitor passwd=EBD2F49C3B375812A8CDEBA632ED8BBC +``` The listener section defines the protocol, port and other information needed to create a listener for the service. To listen on a port using the MySQL protocol a section as shown below should be added to the configuration file. +``` [MaxInfo Listener] type=listener service=MaxInfo protocol=MySQLClient port=9003 +``` To listen with the HTTP protocol and hence return JSON documents a section as should below is required. +``` [MaxInfo JSON Listener] type=listener service=MaxInfo protocol=HTTPD port=8003 +``` + If both the MySQL and JSON responses are required then a single service can be configured with both types of listener. As with any other listeners within MaxScale the listeners can be bound to a particular interface by use of the address= parameter. This allows the access to the maxinfo data to be limited to the localhost by adding an address=localhost parameter in the configuration file. +``` [MaxInfo Listener] type=listener service=MaxInfo protocol=MySQLClient address=localhost port=9003 +``` # MySQL Interface to maxinfo The maxinfo supports a small subset of SQL statements in addition to the MySQL status and ping requests. These may be used for simple monitoring of MaxScale. +``` % mysqladmin -hmaxscale.mariadb.com -P9003 -umonitor -pxyz ping mysqld is alive % mysqladmin -hmaxscale.mariadb.com -P9003 -umonitor -pxyz status Uptime: 72 Threads: 1 Sessions: 11 % +``` The SQL command used to interact with maxinfo is the show command, a variety of show commands are available and will be described in the following sections. @@ -60,6 +71,7 @@ The SQL command used to interact with maxinfo is the show command, a variety of The show variables command will display a set of name and value pairs for a number of MaxScale system variables. +``` mysql> show variables; +--------------------+-------------------------+ | Variable_name | Value | @@ -77,9 +89,11 @@ The show variables command will display a set of name and value pairs for a numb 9 rows in set (0.02 sec) mysql> +``` The show variables command can also accept a limited like clause. This like clause must either be a literal string to match, a pattern starting with a %, a pattern ending with a % or a string with a % at both the start and the end. +``` mysql> show variables like 'version'; +---------------+----------------+ | Variable_name | Value | @@ -116,11 +130,13 @@ The show variables command can also accept a limited like clause. This like clau 3 rows in set (0.02 sec) mysql> +``` ## Show status The show status command displays a set of status counters, as with show variables the show status command can be passed a simplified like clause to limit the values returned. +``` mysql> show status; +---------------------------+-------+ | Variable_name | Value | @@ -151,11 +167,13 @@ The show status command displays a set of status counters, as with show variable 22 rows in set (0.02 sec) mysql> +``` ## Show services The show services command will return a set of basic statistics regarding each of the configured services within MaxScale. +``` mysql> show services; +----------------+----------------+--------------+----------------+ | Service Name | Router Module | No. Sessions | Total Sessions | @@ -172,6 +190,7 @@ The show services command will return a set of basic statistics regarding each o 8 rows in set (0.02 sec) mysql> +``` The show services command does not accept a like clause and will ignore any like clause that is given. @@ -179,6 +198,7 @@ The show services command does not accept a like clause and will ignore any like The show listeners command will return a set of status information for every listener defined within the MaxScale configuration file. +``` mysql> show listeners; +----------------+-----------------+-----------+------+---------+ | Service Name | Protocol Module | Address | Port | State | @@ -196,6 +216,7 @@ The show listeners command will return a set of status information for every lis 9 rows in set (0.02 sec) mysql> +``` The show listeners command will ignore any like clause passed to it. @@ -203,6 +224,7 @@ The show listeners command will ignore any like clause passed to it. The show sessions command returns information on every active session within MaxScale. It will ignore any like clause passed to it. +``` mysql> show sessions; +-----------+---------------+----------------+---------------------------+ | Session | Client | Service | State | @@ -222,11 +244,13 @@ The show sessions command returns information on every active session within Max 11 rows in set (0.02 sec) mysql> +``` ## Show clients The show clients command reports a row for every client application connected to MaxScale. Like clauses are not available of the show clients command. +``` mysql> show clients; +-----------+---------------+---------+---------------------------+ | Session | Client | Service | State | @@ -237,11 +261,13 @@ The show clients command reports a row for every client application connected to 2 rows in set (0.02 sec) mysql> +``` ## Show servers The show servers command returns data for each backend server configured within the MaxScale configuration file. This data includes the current number of connections MaxScale has to that server and the state of that server as monitored by MaxScale. +``` mysql> show servers; +---------+-----------+------+-------------+---------+ | Server | Address | Port | Connections | Status | @@ -254,11 +280,13 @@ The show servers command returns data for each backend server configured within 4 rows in set (0.02 sec) mysql> +``` ## Show modules The show modules command reports the information on the modules currently loaded into MaxScale. This includes the name type and version of each module. It also includes the API version the module has been written against and the current release status of the module. +``` mysql> show modules; +----------------+-------------+---------+-------------+----------------+ | Module Name | Module Type | Version | API Version | Status | @@ -277,12 +305,13 @@ The show modules command reports the information on the modules currently loaded 10 rows in set (0.02 sec) mysql> - +``` ## Show monitors The show monitors command reports each monitor configured within the system and the state of that monitor. +``` mysql> show monitors; +---------------+---------+ | Monitor | Status | @@ -292,12 +321,13 @@ The show monitors command reports each monitor configured within the system and 1 row in set (0.02 sec) mysql> - +``` ## Show eventTimes The show eventTimes command returns a table of statistics that reflect the performance of the event queuing and execution portion of the MaxScale core. +``` mysql> show eventTimes; +---------------+-------------------+---------------------+ | Duration | No. Events Queued | No. Events Executed | @@ -336,6 +366,7 @@ The show eventTimes command returns a table of statistics that reflect the perfo 30 rows in set (0.02 sec) mysql> +``` Each row represents a time interval, in 100ms increments, with the counts representing the number of events that were in the event queue for the length of time that row represents and the number of events that were executing of the time indicated by the row. @@ -347,6 +378,7 @@ The simplified JSON interface takes the URL of the request made to maxinfo and m The /variables URL will return the MaxScale variables, these variables can not be filtered via this interface. +``` $ curl http://maxscale.mariadb.com:8003/variables [ { "Variable_name" : "version", "Value" : "1.0.6-unstable"}, { "Variable_name" : "version_comment", "Value" : "MariaDB MaxScale"}, @@ -358,11 +390,13 @@ The /variables URL will return the MaxScale variables, these variables can not b { "Variable_name" : "MAXSCALE_UPTIME", "Value" : 3948}, { "Variable_name" : "MAXSCALE_SESSIONS", "Value" : 12}] $ +``` ## Status Use of the /status URI will return the status information that would normally be returned by the show status command. No filtering of the status information is available via this interface +``` $ curl http://maxscale.mariadb.com:8003/status [ { "Variable_name" : "Uptime", "Value" : 3831}, { "Variable_name" : "Uptime_since_flush_status", "Value" : 3831}, @@ -387,11 +421,13 @@ Use of the /status URI will return the status information that would normally be { "Variable_name" : "Max_event_queue_time", "Value" : 0}, { "Variable_name" : "Max_event_execution_time", "Value" : 1}] $ +``` ## Services The /services URI returns the data regarding the services defined within the configuration of MaxScale. Two counters are returned, the current number of sessions attached to this service and the total number connected since the service started. +``` $ curl http://maxscale.mariadb.com:8003/services [ { "Service Name" : "Test Service", "Router Module" : "readconnroute", "No. Sessions" : 1, "Total Sessions" : 1}, { "Service Name" : "Split Service", "Router Module" : "readwritesplit", "No. Sessions" : 1, "Total Sessions" : 1}, @@ -402,11 +438,13 @@ The /services URI returns the data regarding the services defined within the con { "Service Name" : "CLI", "Router Module" : "cli", "No. Sessions" : 1, "Total Sessions" : 1}, { "Service Name" : "MaxInfo", "Router Module" : "maxinfo", "No. Sessions" : 5, "Total Sessions" : 20}] $ +``` ## Listeners The /listeners URI will return a JSON array with one entry per listener, each entry is a JSON object that describes the configuration and state of that listener. +``` $ curl http://maxscale.mariadb.com:8003/listeners [ { "Service Name" : "Test Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4006, "State" : "Running"}, { "Service Name" : "Split Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4007, "State" : "Running"}, @@ -418,11 +456,13 @@ The /listeners URI will return a JSON array with one entry per listener, each en { "Service Name" : "MaxInfo", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 9003, "State" : "Running"}, { "Service Name" : "MaxInfo", "Protocol Module" : "HTTPD", "Address" : "*", "Port" : 8003, "State" : "Running"}] $ +``` ## Modules The /modules URI returns data for each plugin that has been loaded into MaxScale. The plugin name, type and version are returned as is the version of the plugin API that the plugin was built against and the release status of the plugin. +``` $ curl http://maxscale.mariadb.com:8003/modules [ { "Module Name" : "HTTPD", "Module Type" : "Protocol", "Version" : "V1.0.1", "API Version" : "1.0.0", "Status" : "In Development"}, { "Module Name" : "maxscaled", "Module Type" : "Protocol", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "GA"}, @@ -435,11 +475,13 @@ The /modules URI returns data for each plugin that has been loaded into MaxScale { "Module Name" : "cli", "Module Type" : "Router", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "GA"}, { "Module Name" : "maxinfo", "Module Type" : "Router", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "Alpha"}] $ +``` ## Sessions The /sessions URI returns a JSON array with an object for each active session within MaxScale. +``` $ curl http://maxscale.mariadb.com:8003/sessions [ { "Session" : "0x1a8e9a0", "Client" : "80.176.79.245", "Service" : "MaxInfo", "State" : "Session ready for routing"}, { "Session" : "0x1a8e6d0", "Client" : "80.240.130.35", "Service" : "MaxInfo", "State" : "Session ready for routing"}, @@ -453,32 +495,38 @@ The /sessions URI returns a JSON array with an object for each active session wi { "Session" : "0x1a5c530", "Client" : , "Service" : "Split Service", "State" : "Listener Session"}, { "Session" : "0x19ac1c0", "Client" : , "Service" : "Test Service", "State" : "Listener Session"}] $ +``` ## Clients The /clients URI is a limited version of the /sessions, in this case it only returns an entry for a session that represents a client connection. +``` $ curl http://maxscale.mariadb.com:8003/clients [ { "Session" : "0x1a90be0", "Client" : "80.176.79.245", "Service" : "MaxInfo", "State" : "Session ready for routing"}, { "Session" : "0x1a8e9a0", "Client" : "127.0.0.1", "Service" : "MaxInfo", "State" : "Session ready for routing"}, { "Session" : "0x1a8e6d0", "Client" : "80.240.130.35", "Service" : "MaxInfo", "State" : "Session ready for routing"}] $ +``` ## Servers The /servers URI is used to retrieve information for each of the servers defined within the MaxScale configuration. This information includes the connection count and the current status as monitored by MaxScale. The connection count is only those connections made by MaxScale to those servers. +``` $ curl http://maxscale.mariadb.com:8003/servers [ { "Server" : "server1", "Address" : "127.0.0.1", "Port" : 3306, "Connections" : 0, "Status" : "Running"}, { "Server" : "server2", "Address" : "127.0.0.1", "Port" : 3307, "Connections" : 0, "Status" : "Down"}, { "Server" : "server3", "Address" : "127.0.0.1", "Port" : 3308, "Connections" : 0, "Status" : "Down"}, { "Server" : "server4", "Address" : "127.0.0.1", "Port" : 3309, "Connections" : 0, "Status" : "Down"}] $ +``` ## Event Times The /event/times URI returns an array of statistics that reflect the performance of the event queuing and execution portion of the MaxScale core. Each element is an object that represents a time bucket, in 100ms increments, with the counts representing the number of events that were in the event queue for the length of time that row represents and the number of events that were executing of the time indicated by the object. +``` $ curl http://maxscale.mariadb.com:8003/event/times [ { "Duration" : "< 100ms", "No. Events Queued" : 64, "No. Events Executed" : 63}, { "Duration" : " 100 - 200ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, @@ -510,3 +558,4 @@ The /event/times URI returns an array of statistics that reflect the performance { "Duration" : "2700 - 2800ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, { "Duration" : "2800 - 2900ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, { "Duration" : "> 3000ms", "No. Events Queued" : 0, "No. Events Executed" : 0}] +``` diff --git a/Documentation/Tutorials/MySQL-Cluster-Setup.md b/Documentation/Tutorials/MySQL-Cluster-Setup.md index fd6c038ca..a168fcfd4 100644 --- a/Documentation/Tutorials/MySQL-Cluster-Setup.md +++ b/Documentation/Tutorials/MySQL-Cluster-Setup.md @@ -207,7 +207,7 @@ The SQL node is referenced here as [mysqld(API)], which reflects the fact that t ## Configuring MaxScale for connection load balancing of SQL nodes -Add these sections in MaxScale.cnf config file: +Add these sections in maxscale.cnf config file: [Cluster Service] type=service @@ -249,7 +249,7 @@ Add these sections in MaxScale.cnf config file: Assuming MaxScale is installed in server1, start it - [root@server1 ~]# cd /usr/local/mariadb-maxscale/bin + [root@server1 ~]# cd /usr/bin [root@server1 bin]# ./maxscale -c ../ diff --git a/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md b/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md index aac084f7b..45de0a791 100644 --- a/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md +++ b/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md @@ -36,6 +36,7 @@ The first user required must be able to select data from the table mysql.user, t 2. Create the user, substituting the username, password and host on which maxscale runs within your environment +``` MariaDB [(none)]> create user '*username*'@'*maxscalehost*' identified by '*password*'; **Query OK, 0 rows affected (0.00 sec)** @@ -45,9 +46,11 @@ MariaDB [(none)]> create user '*username*'@'*maxscalehost*' identified by '*pass MariaDB [(none)]> grant SELECT on mysql.user to '*username*'@'*maxscalehost*'; **Query OK, 0 rows affected (0.03 sec)** +``` Additionally, GRANT SELECT on the mysql.db table and SHOW DATABASES privileges are required in order to load databases name and grants suitable for database name authorization. +``` MariaDB [(none)]> GRANT SELECT ON mysql.db TO 'username'@'maxscalehost'; **Query OK, 0 rows affected (0.00 sec)** @@ -55,9 +58,11 @@ MariaDB [(none)]> GRANT SELECT ON mysql.db TO 'username'@'maxscalehost'; MariaDB [(none)]> GRANT SHOW DATABASES ON *.* TO 'username'@'maxscalehost'; **Query OK, 0 rows affected (0.00 sec)** +``` The second user is used to monitored the state of the cluster. This user, which may be the same username as the first, requires permissions to access the various sources of monitoring data. In order to monitor a replication cluster this user must be granted the roles REPLICATION SLAVE and REPLICATION CLIENT +``` MariaDB [(none)]> grant REPLICATION SLAVE on *.* to '*username*'@'*maxscalehost*'; **Query OK, 0 rows affected (0.00 sec)** @@ -65,220 +70,174 @@ MariaDB [(none)]> grant REPLICATION SLAVE on *.* to '*username*'@'*maxscalehost* MariaDB [(none)]> grant REPLICATION CLIENT on *.* to '*username*'@'*maxscalehost*'; **Query OK, 0 rows affected (0.00 sec)** +``` If you wish to use two different usernames for the two different roles of monitoring and collecting user information then create a different username using the first two steps from above. ## Creating Your MaxScale Configuration -MaxScale configuration is held in an ini file that is located in the file MaxScale.cnf in the directory $MAXSCALE_HOME/etc, if you have installed in the default location then this file is available in /usr/local/mariadb/maxscle/etc/MaxScale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within this directory that may be use as a basis for your configuration. +MaxScale configuration is held in an ini file that is located in the file maxscale.cnf in the directory /etc. This is not created as part of the installation process and must be manually created. A template file does exist in the `/usr/share/maxscale` folder that can be use as a basis for your configuration. A global, maxscale, section is included within every MaxScale configuration file; this is used to set the values of various MaxScale wide parameters, perhaps the most important of these is the number of threads that MaxScale will use to execute the code that forwards requests and handles responses for clients. +``` [maxscale] - threads=4 +``` Since we are using MySQL Replication and connection routing we want two different ports to which the client application can connect; one that will be directed to the current master within the replication cluster and another that will load balance between the slaves. To achieve this within MaxScale we need to define two services in the ini file; one for the read/write operations that should be executed on the master server and another for connections to one of the slaves. Create a section for each in your MaxScale.ini file and set the type to service, the section names are the names of the services themselves and should be meaningful to the administrator. Names may contain whitespace. +``` [Write Service] - type=service [Read Service] - type=service +``` The router for these two sections is identical, the readconnroute module, also the services should be provided with the list of servers that will be part of the cluster. The server names given here are actually the names of server sections in the configuration file and not the physical hostnames or addresses of the servers. +``` [Write Service] - type=service - router=readconnroute - servers=dbserv1, dbserv2, dbserv3 [Read Service] - type=service - router=readconnroute - servers=dbserv1, dbserv2, dbserv3 +``` In order to instruct the router to which servers it should route we must add router options to the service. The router options are compared to the status that the monitor collects from the servers and used to restrict the eligible set of servers to which that service may route. In our case we use the two options master and slave for our two services. +``` [Write Service] - type=service - router=readconnroute - router_options=master - servers=dbserv1, dbserv2, dbserv3 [Read Service] - type=service - router=readconnroute - router_options=slave - servers=dbserv1, dbserv2, dbserv3 +``` The final step in the service sections is to add the username and password that will be used to populate the user data from the database cluster. There are two options for representing the password, either plain text or encrypted passwords may be used. In order to use encrypted passwords a set of keys must be generated that will be used by the encryption and decryption process. To generate the keys use the maxkeys command and pass the name of the secrets file in which the keys are stored. -% maxkeys /usr/local/mariadb-maxscale/etc/.secrets - -% +``` +maxkeys /var/lib/maxscale/.secrets +``` Once the keys have been created the maxpasswd command can be used to generate the encrypted password. -% maxpasswd plainpassword - +``` +maxpasswd plainpassword 96F99AA1315BDC3604B006F427DD9484 - -% +``` The username and password, either encrypted or plain text, are stored in the service section using the user and passwd parameters. +``` [Write Service] - type=service - router=readconnroute - router_options=master - servers=dbserv1, dbserv2, dbserv3 - user=maxscale - passwd=96F99AA1315BDC3604B006F427DD9484 [Read Service] - type=service - router=readconnroute - router_options=slave - servers=dbserv1, dbserv2, dbserv3 - user=maxscale - passwd=96F99AA1315BDC3604B006F427DD9484 +``` This completes the definitions required by the services, however listening ports must be associated with the services in order to allow network connections. This is done by creating a series of listener sections. These sections again are named for the convenience of the administrator and should be of type listener with an entry labeled service which contains the name of the service to associate the listener with. Each service may have multiple listeners. +``` [Write Listener] - type=listener - service=Write Service [Read Listener] - type=listener - service=Read Service +``` A listener must also define the protocol module it will use for the incoming network protocol, currently this should be the MySQLClient protocol for all database listeners. The listener may then supply a network port to listen on and/or a socket within the file system. +``` [Write Listener] - type=listener - service=Write Service - protocol=MySQLClient - port=4306 - socket=/tmp/ClusterMaster [Read Listener] - type=listener - service=Read Service - protocol=MySQLClient - port=4307 +``` An address parameter may be given if the listener is required to bind to a particular network address when using hosts with multiple network addresses. The default behavior is to listen on all network interfaces. The next stage is the configuration is to define the server information. This defines how to connect to each of the servers within the cluster, again a section is created for each server, with the type set to server, the network address and port to connect to and the protocol to use to connect to the server. Currently the protocol for all database connections in MySQLBackend. +``` [dbserv1] - type=server - address=192.168.2.1 - port=3306 - protocol=MySQLBackend [dbserv2] - type=server - address=192.168.2.2 - port=3306 - protocol=MySQLBackend [dbserv3] - type=server - address=192.168.2.3 - port=3306 - protocol=MySQLBackend +``` In order for MaxScale to monitor the servers using the correct monitoring mechanisms a section should be provided that defines the monitor to use and the servers to monitor. Once again a section is created with a symbolic name for the monitor, with the type set to monitor. Parameters are added for the module to use, the list of servers to monitor and the username and password to use when connecting to the the servers with the monitor. +``` [Replication Monitor] - type=monitor - module=mysqlmon - servers=dbserv1, dbserv2, dbserv3 - user=maxscale - passwd=96F99AA1315BDC3604B006F427DD9484 +``` As with the password definition in the server either plain text or encrypted passwords may be used. The final stage in the configuration is to add the option service which is used by the maxadmin command to connect to MaxScale for monitoring and administration purposes. This creates a service section and a listener section. +``` [CLI] - type=service - router=cli [CLI Listener] - type=listener - service=CLI - protocol=maxscaled - address=localhost - port=6603 +``` In the case of the example above it should be noted that an address parameter has been given to the listener, this limits connections to maxadmin commands that are executed on the same machine that hosts MaxScale. @@ -286,14 +245,19 @@ In the case of the example above it should be noted that an address parameter ha Upon completion of the configuration process MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface. -% maxscale +``` +maxscale +``` or -% service maxscale start +``` +service maxscale start +``` -Check the error log in /usr/local/mariadb-maxscale/log to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +Check the error log in /var/log/lomaxscale/ to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +``` % maxadmin -pmariadb list services Services. @@ -349,6 +313,7 @@ CLI | maxscaled | localhost | 6603 | Running ---------------------+--------------------+-----------------+-------+-------- % +``` -MaxScale is now ready to start accepting client connections and routing them to the master or slaves within your cluster. Other configuration options are available that can alter the criteria used for routing, these include monitoring the replication lag within the cluster and routing only to slaves that are within a predetermined delay from the current master or using weights to obtain unequal balancing operations. These options may be found in the MaxScale Configuration Guide. More detail on the use of maxadmin can be found in the document "MaxAdmin - The MaxScale Administration & Monitoring Client Application". +MaxScale is now ready to start accepting client connections and routing them to the master or slaves within your cluster. Other configuration options are available that can alter the criteria used for routing, these include monitoring the replication lag within the cluster and routing only to slaves that are within a predetermined delay from the current master or using weights to obtain unequal balancing operations. These options may be found in the MaxScale Configuration Guide. More detail on the use of maxadmin can be found in the document [MaxAdmin - The MaxScale Administration & Monitoring Client Application](Administration-Tutorial.md). diff --git a/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md b/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md index 54e5412be..93ecdd931 100644 --- a/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md +++ b/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md @@ -70,7 +70,7 @@ If you wish to use two different usernames for the two different roles of monito ## Creating Your MaxScale Configuration -MaxScale configuration is held in an ini file that is located in the file MaxScale.cnf in the directory $MAXSCALE_HOME/etc, if you have installed in the default location then this file is available in /usr/local/mariadb-maxscale/etc/MaxScale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within this directory that may be use as a basis for your configuration. +MaxScale configuration is held in an ini file that is located in the file maxscale.cnf in the directory /etc, if you have installed in the default location then this file is available in /etc/maxscale.cnf. This is not created as part of the installation process and must be manually created. A template file does exist within the /usr/share/maxscale directory that may be use as a basis for your configuration. A global, maxscale, section is included within every MaxScale configuration file; this is used to set the values of various MaxScale wide parameters, perhaps the most important of these is the number of threads that MaxScale will use to execute the code that forwards requests and handles responses for clients. @@ -96,7 +96,7 @@ servers=dbserv1, dbserv2, dbserv3 The final step in the service sections is to add the username and password that will be used to populate the user data from the database cluster. There are two options for representing the password, either plain text or encrypted passwords may be used. In order to use encrypted passwords a set of keys must be generated that will be used by the encryption and decryption process. To generate the keys use the maxkeys command and pass the name of the secrets file in which the keys are stored. -% maxkeys /usr/local/mariadb-maxscale/etc/.secrets +% maxkeys /var/lib/maxscale/.secrets % @@ -226,7 +226,7 @@ or % service maxscale start -Check the error log in /usr/local/mariadb-maxscale/log to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +Check the error log in /var/log/maxscale to see if any errors are detected in the configuration file and to confirm MaxScale has been started. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. % maxadmin -pmariadb list services diff --git a/Documentation/Tutorials/Nagios-Plugins.md b/Documentation/Tutorials/Nagios-Plugins.md index 9ee3a87c7..6c8cef9c2 100644 --- a/Documentation/Tutorials/Nagios-Plugins.md +++ b/Documentation/Tutorials/Nagios-Plugins.md @@ -44,7 +44,7 @@ In order to use these scripts on your Nagios Server, you need to copy them from MaxScale must be configured with 'maxscaled' protocol for the administration interface: -Example of MaxScale.cnf file: +Example of maxscale.cnf file: [AdminInterface] type=service @@ -73,9 +73,9 @@ and add (just after localhost.cfg or commnads.cfg) - modify server IP address in server1.cfg, pointing to MaxScale server - maxadmin executable must be in the nagios server - default MaxScale AdminInterface port is 6603 -- default maxadmin executable path is /usr/local/mariadb-maxscale/bin/maxadmin +- default maxadmin executable path is /usr/bin/maxadmin It can be changed by -m option -- maxadmin executable could be copied from an existing maxscale installation (default location is /usr/local/mariadb-maxscale/bin/maxadmin) +- maxadmin executable could be copied from an existing maxscale installation (default location is /usr/bin/maxadmin) This example shows configuration that needs to be done on Nagios server in order to communicate to MaxScale server that is running on host server1. In this example we are using the check_maxscale_resource as the check command diff --git a/Documentation/Tutorials/RabbitMQ-Setup-And-MaxScale-Integration.md b/Documentation/Tutorials/RabbitMQ-Setup-And-MaxScale-Integration.md index 2f2903861..c66d8c1cb 100644 --- a/Documentation/Tutorials/RabbitMQ-Setup-And-MaxScale-Integration.md +++ b/Documentation/Tutorials/RabbitMQ-Setup-And-MaxScale-Integration.md @@ -193,7 +193,7 @@ This generates RPM or DEB packages based on your system. These packages can then ## Step 5 - Configure new applications -The new filter needs to be configured in MaxScale.cnf. +The new filter needs to be configured in maxscale.cnf. [Test Service] type=service diff --git a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md index f07fd13a3..cc9b40061 100644 --- a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md +++ b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md @@ -28,7 +28,7 @@ Using MaxScale as a replication proxy is much the same as using MaxScale as a pr ## Service Configuration -As with any MaxScale configuration a good starting point is with the service definition with the MaxScale.cnf file. The service requires a name which is the section name in the ini file, a type parameter with a value of service and the name of the router plugin that should be loaded. In the case of replication proxies this router name is binlogrouter. +As with any MaxScale configuration a good starting point is with the service definition with the maxscale.cnf file. The service requires a name which is the section name in the ini file, a type parameter with a value of service and the name of the router plugin that should be loaded. In the case of replication proxies this router name is binlogrouter. [Replication] @@ -84,7 +84,7 @@ This optional parameter allows for the administrator to define the number of the ### binlogdir -This parameter allows the location that MaxScale uses to store binlog files to be set. If this parameter is not set to a directory name then MaxScale will store the binlog files in the directory $MAXSCALE_HOME/. +This parameter allows the location that MaxScale uses to store binlog files to be set. If this parameter is not set to a directory name then MaxScale will store the binlog files in the directory /var/cache/maxscale/. ### heartbeat @@ -101,7 +101,7 @@ A complete example of a service entry for a binlog router service would be as fo router=binlogrouter servers=masterdb version_string=5.6.17-log - router_options=uuid=f12fcb7f-b97b-11e3-bc5e-0401152c4c22,server-id=3,user=repl,password=slavepass,master-id=1,filestem=mybin,heartbeat=30,binlogdir=/home/mriddoch/binlogs + router_options=uuid=f12fcb7f-b97b-11e3-bc5e-0401152c4c22,server-id=3,user=repl,password=slavepass,master-id=1,filestem=mybin,heartbeat=30,binlogdir=/var/binlogs user=maxscale passwd=Mhu87p2D @@ -211,6 +211,62 @@ The binlog router module of MaxScale produces diagnostic output that can be view +# Binlog router compatibility + +Binlog Router Plugin is compatible with MySQL 5.6, MariaDB 5.5, the current default. + +In order to use it with MySQL 5.6, the GTID_MODE setting must be OFF and connecting slaves mustn't use MASTER_AUTO_POSITION = 1 option. + +It’s also works with a MariaDB 10.0 setup (master and slaves) but slave connection must not include any GTID feature. + +Binlog Router currently does not work for MySQL 5.5 due to missing @@global.binlog_checksum var. + +# Slave servers setup + +Examples of CHANGE MASTER TO command issued on a slave server that wants to gets replication events from MaxScale binlog router: + + CHANGE MASTER TO MASTER_HOST=‘$maxscale_IP’, MASTER_PORT=5308, MASTER_USER='repl', MASTER_PASSWORD=‘somepasswd’, + MASTER_LOG_FILE=‘mysql-bin.000001' + + CHANGE MASTER TO MASTER_HOST=‘$maxscale_IP’, MASTER_PORT=5308, MASTER_USER='repl', MASTER_PASSWORD=‘somepasswd’, + MASTER_LOG_FILE=‘mysql-bin.000159', MASTER_LOG_POS=245 + +The latter example specifies a MASTER_LOG_POS for the selected MASTER_LOG_FILE + +Note: + + - MASTER_LOG_FILE must be set to one of existing binlog files in MaxScale binlogdir + + - If MASTER_LOG_POS is not set with CHANGE MASTER TO it defaults to 4 + + - Latest binlog file name and pos in MaxScale could be find via maxadmin output or from mysql client connected to MaxScale: + +Example: + + -bash-4.1$ mysql -h 127.0.0.1 -P 5308 -u$user -p$pass + + MySQL [(none)]> show master status\G + *************************** 1. row *************************** + File: mysql-bin.000181 + Position: 2569 + +# Enabling MariaDB 10 compatibility + +MariaDB 10 has different slave registration phase so an option is required: + + router_options=...., mariadb10-compatibility=1 + +version_string should be modified in order to present MariaDB 10 version when MaxScale sends server handshake packet. + + version_string=10.0.17-log +# New MariaDB events in Diagnostics + +With a MariaDB 10 setups new events are displayed when master server is MariaDB 10. + + MariaDB 10 Annotate Rows Event 0 + MariaDB 10 Binlog Checkpoint Event 0 + MariaDB 10 GTID Event 0 + MariaDB 10 GTID List Event 0 diff --git a/Documentation/Tutorials/Simple-Sharding-Tutorial.md b/Documentation/Tutorials/Simple-Sharding-Tutorial.md index 6c16fa9f6..70b972de2 100644 --- a/Documentation/Tutorials/Simple-Sharding-Tutorial.md +++ b/Documentation/Tutorials/Simple-Sharding-Tutorial.md @@ -32,7 +32,7 @@ Upon successful completion of the installation command you will have MaxScale in ### Creating Your MaxScale Configuration -The first step in the creation of your MaxScale.cnf file is to define the global maxscale section. This section configures the number of threads MaxScale uses. A good rule of thumb is to use at most as may threads as you have CPUs. MaxScale uses few threads for internal operations so one or two threads less than the maximum should be enough. +The first step in the creation of your maxscale.cnf file is to define the global maxscale section. This section configures the number of threads MaxScale uses. A good rule of thumb is to use at most as may threads as you have CPUs. MaxScale uses few threads for internal operations so one or two threads less than the maximum should be enough. ``` [maxscale] @@ -92,7 +92,7 @@ After this we have a fully working configuration and we can move on to starting Upon completion of the configuration process MaxScale is ready to be started . This may either be done manually by running the maxscale command or via the service interface. The service scripts are located in the `/etc/init.d/` folder and are accessible through both the `service` and `systemctl` commands. -After starting MaxScale check the error log in /usr/local/mariadb-maxscale/log to see if any errors are detected in the configuration file. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. +After starting MaxScale check the error log in /var/log/maxscale to see if any errors are detected in the configuration file. Also the maxadmin command may be used to confirm that MaxScale is running and the services, listeners etc have been correctly configured. MaxScale is now ready to start accepting client connections and routing them. Queries are routed to the right servers based on the database they target and switching between the shards is seamless since MaxScale keeps the session state intact between servers. diff --git a/Documentation/Upgrading-To-MaxScale-1.1.0.md b/Documentation/Upgrading/Upgrading-To-MaxScale-1.1.0.md similarity index 100% rename from Documentation/Upgrading-To-MaxScale-1.1.0.md rename to Documentation/Upgrading/Upgrading-To-MaxScale-1.1.0.md diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-1.2.0.md b/Documentation/Upgrading/Upgrading-To-MaxScale-1.2.0.md new file mode 100644 index 000000000..a48750076 --- /dev/null +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-1.2.0.md @@ -0,0 +1,24 @@ +# Upgrading MaxScale from 1.1 to 1.2 + +This document describes upgrading MaxScale from version 1.1.1 to 1.2 and the major differences in the new version compared to the old version. The major changes can be found in the `Changelog.txt` file in the installation directory and the official release notes in the `ReleaseNotes.txt` file. + +## Installation + +Before starting the upgrade, we recommend you back up your configuration, log and binary log files in `/usr/local/mariadb-maxscale/`. + +Upgrading MaxScale will copy the `MaxScale.cnf` file in `/usr/local/mariadb-maxscale/etc/` to `/etc/` and renamed to `maxscale.cnf`. Binary log files are not automatically copied and should be manually moved from `/usr/local/mariadb-maxscale` to `/var/lib/maxscale/`. + +## File location changes + +MaxScale 1.2 follows the [FHS-standard](http://www.pathname.com/fhs/) and installs to `/usr/` and `/var/` subfolders. Here are the major changes and file locations. + +* Configuration files are located in `/etc/` and use lowercase letters: `/etc/maxscale.cnf` +* Binary files are in `/usr/bin/` +* Libraries and modules are in `/usr/lib64/maxscale/`. If you are using custom modules, please make sure they are in this directory before starting MaxScale. +* Log files are in the `var/log/maxscale/` folder +* MaxScale's PID file is located in `/var/run/maxscale/maxscale.pid` +* Data files and other persistent files are in `/var/lib/maxscale/` + +## Running MaxScale without root permissions + +MaxScale can run as a non-root user with the 1.2 version. RPM and DEB packages install the `maxscale` user and `maxscale` group which are used by the init scripts and systemd configuration files. If you are installing from a binary tarball, you can run the `postinst` script included in it to manually create these groups. diff --git a/Documentation/maxscale.1 b/Documentation/maxscale.1 new file mode 100644 index 000000000..104eaa235 --- /dev/null +++ b/Documentation/maxscale.1 @@ -0,0 +1,69 @@ +.TH maxscale 1 +.SH NAME +maxscale - The intelligent proxy +.SH SYNOPSIS +.B maxscale +[\fIOPTIONS...\fR] +.SH DESCRIPTION +The MariaDB Corporation MaxScale is an intelligent proxy that allows forwarding of +database statements to one or more database servers using complex rules, +a semantic understanding of the database statements and the roles of +the various servers within the backend cluster of databases. + +MaxScale is designed to provide load balancing and high availability +functionality transparently to the applications. In addition it provides +a highly scalable and flexible architecture, with plugin components to +support different protocols and routing decisions. + +.SH OPTIONS +.TP +.BR "-d, --nodaemon" +Run MaxScale in the terminal process +.TP +.BR -f " \fIFILE\fB, --config=\fIFILE\fR" +Relative or absolute pathname of MaxScale configuration file to load. +.TP +.BR -l "[\fIfile|shm\fB], --log=[\fIfile|shm\fB]" +Log trace and debug logs to file or shared memory. The debug and trace logs are disabled by default and if enabled, will log to shared memory. +.TP +.BR -L " \fIPATH\fB, --logdir=\fIPATH\fB" +Path to log file directory. +.TP +.BR -D " \fIPATH\fB, --datadir=\fIPATH\fB" +Path to data directory. This is where the embedded mysql tables are stored in addition to other MaxScale specific data. +.TP +.BR -C " \fIPATH\fB, --configdir=\fIPATH\fB" +Path to configuration file directory. MaxScale will look for the \fImaxscale.cnf\fR file from this folder. +.TP +.BR -B " \fIPATH\fB, --libdir=\fIPATH\fB" +Path to module directory. Modules are only searched from this folder. +.TP +.BR -A " \fIPATH\fB, --cachedir=\fIPATH\fB" +Path to cache directory. This is where MaxScale stores cached authentication data. +.TP +.BR -P " \fIPATH\fB, --piddir=\fIPATH\fB" +Location of MaxScale's PID file. +.TP +.BR -U " \fIUSER\fB, --user=\fIUSER\fB" +Run MaxScale as another user. The user ID and group ID of this user are used to run MaxScale. +.TP +.BR -s " [\fIyes\fB|\fIno\fB], --syslog=[\fIyes\fB|\fIno\fB]" +Log messages to syslog. +.TP +.BR -S " [\fIyes\fB|\fIno\fB], \fB--maxscalelog=[\fIyes\fB|\fIno\fB]" +Log messages to MaxScale's own log files. +.TP +.BR "-v, --version" +Print version information and exit. +.TP +.BR "-?, --help" +Show the help information for MaxScale and exit. + +.SH EXAMPLES +Tutorials on GitHub: +.UR https://github.com/mariadb-corporation/MaxScale/blob/master/Documentation/Documentation-Contents.md#tutorials +.UE +.SH SEE ALSO +The MaxScale documentation on GitHub: +.UR https://github.com/mariadb-corporation/MaxScale/blob/master/Documentation/Documentation-Contents.md +.UE diff --git a/README.md b/README.md index 263134569..d2ac42128 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ issues and communicate with the MaxScale community. or use the [forum](http://groups.google.com/forum/#!forum/maxscale) interface Bugs can be reported in the MariaDB Corporation bugs database - [https://mariadb.atlassian.net](https://mariadb.atlassian.net) under project MXS. + [https://mariadb.atlassian.net/projects/MXS/issues](https://mariadb.atlassian.net/projects/MXS/issues) # Documentation diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 755f2eac8..32ab702ea 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -7,4 +7,4 @@ if(HIST) else() message(STATUS "Could not find editline library. MaxAdmin will be built without it.") endif() -install(TARGETS maxadmin DESTINATION bin) +install(TARGETS maxadmin DESTINATION ${MAXSCALE_BINDIR}) diff --git a/client/maxadmin.c b/client/maxadmin.c index 8a4d7cb43..6262bd34b 100644 --- a/client/maxadmin.c +++ b/client/maxadmin.c @@ -105,7 +105,6 @@ EditLine *el = NULL; Tokenizer *tok; History *hist; HistEvent ev; -const LineInfo *li; #else char buf[1024]; #endif @@ -199,7 +198,7 @@ char c; for (i = optind +1; i < argc; i++) { strcat(cmd, " "); - /* Arguments after the seconf are quoted to allow for names + /* Arguments after the second are quoted to allow for names * that contain white space */ if (i - optind > 1) @@ -266,7 +265,7 @@ char c; buf[i] = 0; #ifdef HISTORY - li = el_line(el); + el_line(el); history(hist, &ev, H_ENTER, buf); #endif @@ -433,10 +432,10 @@ char buf[20]; } /** - * Send a comamnd using the MaxScaled protocol, display the return data + * Send a command using the MaxScaled protocol, display the return data * on standard output. * - * Input terminates with a lien containing just the text OK + * Input terminates with a line containing just the text OK * * @param so The socket connect to MaxScale * @param cmd The command to send diff --git a/cmake/install_layout.cmake b/cmake/install_layout.cmake new file mode 100644 index 000000000..a14ef05c5 --- /dev/null +++ b/cmake/install_layout.cmake @@ -0,0 +1,11 @@ +# Set the install layout +include(GNUInstallDirs) + +set(MAXSCALE_LIBDIR ${CMAKE_INSTALL_LIBDIR}/maxscale CACHE PATH "Library installation path") +set(MAXSCALE_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Executable installation path") +set(MAXSCALE_SHAREDIR ${CMAKE_INSTALL_DATADIR}/maxscale CACHE PATH "Share file installation path, includes licence and readme files") +set(MAXSCALE_DOCDIR ${CMAKE_INSTALL_DOCDIR}/maxscale CACHE PATH "Documentation installation path, text versions only") + +# These are the only hard-coded absolute paths +set(MAXSCALE_VARDIR /var CACHE PATH "Data file path (usually /var/)") +set(MAXSCALE_CONFDIR /etc CACHE PATH "Configuration file installation path (/etc/)") diff --git a/macros.cmake b/cmake/macros.cmake similarity index 65% rename from macros.cmake rename to cmake/macros.cmake index b7edfed14..848b3290f 100644 --- a/macros.cmake +++ b/cmake/macros.cmake @@ -9,17 +9,23 @@ macro(set_maxscale_version) # MaxScale version number set(MAXSCALE_VERSION_MAJOR "1") - set(MAXSCALE_VERSION_MINOR "1") - set(MAXSCALE_VERSION_PATCH "1") + set(MAXSCALE_VERSION_MINOR "2") + set(MAXSCALE_VERSION_PATCH "0") 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}") # This should be incremented each time a package is rebuilt - set(MAXSCALE_BUILD_NUMBER 2) + set(MAXSCALE_BUILD_NUMBER 1) endmacro() macro(set_variables) + # Use C99 + set(USE_C99 TRUE CACHE BOOL "Use C99 standard") + + # Install the template maxscale.cnf file + set(WITH_MAXSCALE_CNF TRUE CACHE BOOL "Install the template maxscale.cnf file") + # hostname or IP address of MaxScale's host set(TEST_HOST "127.0.0.1" CACHE STRING "hostname or IP address of MaxScale's host") @@ -132,29 +138,9 @@ macro(check_deps) endif() - # set(MAXSCALE_DEPS aio ssl crypt crypto z m dl rt pthread) - # foreach(lib ${MAXSCALE_DEPS}) - # find_library(lib${lib} ${lib}) - # if((DEFINED lib${lib}) AND (${lib${lib}} MATCHES "NOTFOUND")) - # set(DEPS_ERROR TRUE) - # set(FAILED_DEPS "${FAILED_DEPS} lib${lib}") - # elseif(DEBUG_OUTPUT) - # message(STATUS "Library was found at: ${lib${lib}}") - # endif() - # endforeach() - - # if(DEPS_ERROR) - # set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") - # message(FATAL_ERROR "Cannot find dependencies: ${FAILED_DEPS}") - # endif() - endmacro() macro(check_dirs) - - # This variable is used to prevent redundant checking of dependencies - set(DEPS_OK TRUE CACHE BOOL "If all the dependencies were found.") - # Find the MySQL headers if they were not defined if(DEFINED MYSQL_DIR) @@ -168,7 +154,6 @@ macro(check_dirs) debugmsg("Search returned: ${MYSQL_DIR_LOC}") if(${MYSQL_DIR_LOC} MATCHES "NOTFOUND") - set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") message(FATAL_ERROR "Fatal Error: MySQL headers were not found.") else() set(MYSQL_DIR ${MYSQL_DIR_LOC} CACHE PATH "Path to MySQL headers" FORCE) @@ -193,7 +178,6 @@ debugmsg("Search returned: ${MYSQL_DIR_LOC}") else() find_file(ERRMSG_FILE errmsg.sys PATHS /usr/share /usr/share/mysql /usr/local/share/mysql PATH_SUFFIXES english mysql/english) if(${ERRMSG_FILE} MATCHES "NOTFOUND") - set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") message(FATAL_ERROR "Fatal Error: The errmsg.sys file was not found, please define the path to it by using -DERRMSG=") else() message(STATUS "Using errmsg.sys found at: ${ERRMSG_FILE}") @@ -202,75 +186,12 @@ debugmsg("Search returned: ${MYSQL_DIR_LOC}") set(ERRMSG ${ERRMSG_FILE} CACHE FILEPATH "Path to the errmsg.sys file." FORCE) unset(ERRMSG_FILE) - # Find the embedded mysql library - - # if (DEFINED EMBEDDED_LIB) - # if( NOT (IS_DIRECTORY ${EMBEDDED_LIB}) ) - # debugmsg("EMBEDDED_LIB is not a directory: ${EMBEDDED_LIB}") - # if(${CMAKE_VERSION} VERSION_LESS 2.8.12 ) - # set(COMP_VAR PATH) - # else() - # set(COMP_VAR DIRECTORY) - # endif() - # get_filename_component(EMBEDDED_LIB ${EMBEDDED_LIB} ${COMP_VAR}) - # debugmsg("EMBEDDED_LIB directory component: ${EMBEDDED_LIB}") - # endif() - # debugmsg("Searching for the embedded library at: ${EMBEDDED_LIB}") - # endif() - - # if(STATIC_EMBEDDED) - - # debugmsg("Using the static embedded library...") - # set(OLD_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - # set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - # if (DEFINED EMBEDDED_LIB) - # debugmsg("Searching for libmysqld.a at: ${EMBEDDED_LIB}") - # find_library(EMBEDDED_LIB_STATIC libmysqld.a PATHS ${EMBEDDED_LIB} PATH_SUFFIXES mysql mariadb NO_DEFAULT_PATH) - # else() - # find_library(EMBEDDED_LIB_STATIC libmysqld.a PATH_SUFFIXES mysql mariadb) - # endif() - # debugmsg("Search returned: ${EMBEDDED_LIB_STATIC}") - - # set(EMBEDDED_LIB ${EMBEDDED_LIB_STATIC} CACHE FILEPATH "Path to libmysqld" FORCE) - # set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_SUFFIXES}) - - # else() - # debugmsg("Using the dynamic embedded library...") - # set(OLD_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - # set(CMAKE_FIND_LIBRARY_SUFFIXES ".so") - # if (DEFINED EMBEDDED_LIB) - # debugmsg("Searching for libmysqld.so at: ${EMBEDDED_LIB}") - # find_library(EMBEDDED_LIB_DYNAMIC mysqld PATHS ${EMBEDDED_LIB} PATH_SUFFIXES mysql mariadb NO_DEFAULT_PATH) - # else() - # find_library(EMBEDDED_LIB_DYNAMIC mysqld PATH_SUFFIXES mysql mariadb) - # endif() - # debugmsg("Search returned: ${EMBEDDED_LIB_DYNAMIC}") - # set(EMBEDDED_LIB ${EMBEDDED_LIB_DYNAMIC} CACHE FILEPATH "Path to libmysqld" FORCE) - # set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_SUFFIXES}) - - # endif() - - # unset(EMBEDDED_LIB_DYNAMIC) - # unset(EMBEDDED_LIB_STATIC) - # unset(OLD_SUFFIXES) - - # # Inform the user about the embedded library - # if( (${EMBEDDED_LIB} MATCHES "NOTFOUND") OR (${EMBEDDED_LIB} MATCHES "NOTFOUND")) - # set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") - # message(FATAL_ERROR "Library not found: libmysqld. If your install of MySQL is in a non-default location, please provide the location with -DEMBEDDED_LIB=") - # else() - # get_filename_component(EMBEDDED_LIB ${EMBEDDED_LIB} REALPATH) - # message(STATUS "Using embedded library: ${EMBEDDED_LIB}") - # endif() - - # Check which init.d script to install if(WITH_SCRIPTS) find_file(RPM_FNC functions PATHS /etc/rc.d/init.d) if(${RPM_FNC} MATCHES "RPM_FNC-NOTFOUND") find_file(DEB_FNC init-functions PATHS /lib/lsb) if(${DEB_FNC} MATCHES "DEB_FNC-NOTFOUND") - set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") message(FATAL_ERROR "Cannot find required init-functions in /lib/lsb/ or /etc/rc.d/init.d/, please confirm that your system files are OK.") else() set(DEB_BASED TRUE CACHE BOOL "If init.d script uses /lib/lsb/init-functions instead of /etc/rc.d/init.d/functions.") diff --git a/cmake/package_deb.cmake b/cmake/package_deb.cmake new file mode 100644 index 000000000..90d66d4bc --- /dev/null +++ b/cmake/package_deb.cmake @@ -0,0 +1,6 @@ +# DEB specific CPack configuration parameters +set(CPACK_GENERATOR "${CPACK_GENERATOR};DEB") +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst;{CMAKE_BINARY_DIR}/postrm") +execute_process(COMMAND dpgk --print-architecture OUTPUT_VARIABLE DEB_ARCHITECTURE) +set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEB_ARCHITECTURE}) +set (CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) diff --git a/cmake/package_rpm.cmake b/cmake/package_rpm.cmake new file mode 100644 index 000000000..7f11587a7 --- /dev/null +++ b/cmake/package_rpm.cmake @@ -0,0 +1,13 @@ +# RPM specific CPack configuration parameters +set(CPACK_GENERATOR "${CPACK_GENERATOR};RPM") +set(CPACK_RPM_PACKAGE_RELEASE ${MAXSCALE_BUILD_NUMBER}) +set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE ${CMAKE_BINARY_DIR}/postinst) +set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE ${CMAKE_BINARY_DIR}/postrm) +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") diff --git a/cmake/testall.cmake b/cmake/testall.cmake index fedbdf273..839029312 100644 --- a/cmake/testall.cmake +++ b/cmake/testall.cmake @@ -1,4 +1,5 @@ -execute_process(COMMAND /bin/sh -c "${CMAKE_BINARY_DIR}/bin/maxscale -c ${CMAKE_BINARY_DIR} &>/dev/null 2> /dev/null > /dev/null") +execute_process(COMMAND /bin/sh -c "${CMAKE_BINARY_DIR}/bin/maxscale -f ${CMAKE_BINARY_DIR}/maxscale.cnf --logdir=${CMAKE_BINARY_DIR}/ --datadir=${CMAKE_BINARY_DIR}/ --cachedir=${CMAKE_BINARY_DIR}/ --piddir=${CMAKE_BINARY_DIR}/ &> ${CMAKE_BINARY_DIR}/maxscale.output" +OUTPUT_VARIABLE MAXSCALE_OUT) execute_process(COMMAND make test RESULT_VARIABLE RVAL) execute_process(COMMAND killall maxscale) if(NOT RVAL EQUAL 0) diff --git a/etc/init.d/maxscale.in b/etc/init.d/maxscale.in index 88f026e18..38203dc11 100755 --- a/etc/init.d/maxscale.in +++ b/etc/init.d/maxscale.in @@ -18,12 +18,11 @@ ### END INIT INFO ############################################# -# MaxScale HOME, PIDFILE, LIB +# MaxScale PIDFILE and LIB ############################################# -export MAXSCALE_HOME=@CMAKE_INSTALL_PREFIX@ -export MAXSCALE_PIDFILE=$MAXSCALE_HOME/log/maxscale.pid -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MAXSCALE_HOME/lib +export MAXSCALE_PIDFILE=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@/maxscale ############################### # LSB Exit codes (non-Status) @@ -38,8 +37,11 @@ _RETVAL_NOT_RUNNING=7 _RETVAL_STATUS_OK=0 _RETVAL_STATUS_NOT_RUNNING=3 +# Create PID directory if not exists +/usr/bin/install -d -o maxscale -g maxscale @MAXSCALE_VARDIR@/run/maxscale + # Sanity checks. -[ -x $MAXSCALE_HOME/bin/maxscale ] || exit $_RETVAL_NOT_INSTALLED +[ -x @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale ] || exit $_RETVAL_NOT_INSTALLED # Source function library. . /etc/rc.d/init.d/functions @@ -52,23 +54,23 @@ RETVAL=0 start() { echo -n $"Starting MaxScale: " - my_check=`status -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale` + my_check=`status -p $MAXSCALE_PIDFILE @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale` CHECK_RET=$? [ $CHECK_RET -eq 0 ] && echo -n " found $my_check" && success && CHECK_RET=0 - daemon --pidfile $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale >& /dev/null + daemon --pidfile $MAXSCALE_PIDFILE @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale >& /dev/null RETVAL=$? [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename if [ $CHECK_RET -ne 0 ]; then sleep 2 - my_check=`status -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale` + my_check=`status -p $MAXSCALE_PIDFILE @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale` CHECK_RET=$? [ $CHECK_RET -eq 0 ] && echo -n $my_check && success || failure fi - # Return rigth code + # Return right code if [ $RETVAL -ne 0 ]; then failure RETVAL=$_RETVAL_NOT_RUNNING @@ -100,7 +102,7 @@ stop() { reload() { echo -n $"Reloading MaxScale: " - killproc -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale -HUP + killproc -p $MAXSCALE_PIDFILE @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale -HUP RETVAL=$? echo } diff --git a/etc/lsyncd_example.conf b/etc/lsyncd_example.conf new file mode 100644 index 000000000..40e860675 --- /dev/null +++ b/etc/lsyncd_example.conf @@ -0,0 +1,30 @@ + +-- Lsyncd will log to these two files. +settings{ + logfile = "/var/log/maxscale/maxscale-ha.log", + statusFile = "/var/log/maxscale/maxscale-ha-status.log" +} + +-- Copy and paste the sync section and change the host value to add new remote targets. +sync{ +default.rsyncssh, + +-- This is where the maxscale.cnf file is copied from. +source="/etc", + +-- This is the user and host where the maxscale.cnf is copied to. +host="user@127.0.0.1", + +-- This is where the maxscale.cnf is copied to on the remote host. +targetdir="/etc", + +-- This is an optional section which defines a custom SSH port. Uncomment to enable. +-- ssh={port=2222}, + +-- These are values passed to rsync. Only change these if you know what you are doing. +rsync={ +compress=true, +_extra = {[[--filter=+ *maxscale.cnf]], + [[--filter=- **]]} +} +} diff --git a/etc/maxscale.service.in b/etc/maxscale.service.in new file mode 100644 index 000000000..055016f8d --- /dev/null +++ b/etc/maxscale.service.in @@ -0,0 +1,14 @@ +[Unit] +Description=MariaDB MaxScale Database Proxy +After=network.target + +[Service] +Type=forking +Restart=on-failure +PIDFile=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid +ExecStartPre=/usr/bin/install -d @MAXSCALE_VARDIR@/run/maxscale -o maxscale -g maxscale +ExecStart=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale +LimitNOFILE=65535 + +[Install] +WantedBy=multi-user.target diff --git a/etc/postinst.in b/etc/postinst.in index 4bd193958..dd2b0c11e 100755 --- a/etc/postinst.in +++ b/etc/postinst.in @@ -1,5 +1,63 @@ #!/bin/sh -cp @CMAKE_INSTALL_PREFIX@/maxscale /etc/init.d/ -cp @CMAKE_INSTALL_PREFIX@/maxscale.conf /etc/ld.so.conf.d/ + +# Create directories +mkdir -p @CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@ +mkdir -p @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@ +mkdir -p @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@ +mkdir -p @CMAKE_INSTALL_PREFIX@/@MAXSCALE_DOCDIR@ + +# MAXSCALE_VARDIR is an absolute path to /var by default +mkdir -p @MAXSCALE_VARDIR@/log/maxscale +mkdir -p @MAXSCALE_VARDIR@/lib/maxscale +mkdir -p @MAXSCALE_VARDIR@/cache/maxscale +mkdir -p @MAXSCALE_VARDIR@/run/maxscale + +# Create MaxScale user +if [ -f "/etc/passwd" -a "$(grep -c 'maxscale' /etc/passwd)" -eq 0 ] +then + useradd -r -s /bin/false maxscale + groupadd maxscale +fi + +# Change the owner of the directories to maxscale:maxscale +chown maxscale:maxscale @MAXSCALE_VARDIR@/log/maxscale +chown maxscale:maxscale @MAXSCALE_VARDIR@/lib/maxscale +chown maxscale:maxscale @MAXSCALE_VARDIR@/cache/maxscale +chown maxscale:maxscale @MAXSCALE_VARDIR@/run/maxscale +chmod 0755 @MAXSCALE_VARDIR@/log/maxscale +chmod 0755 @MAXSCALE_VARDIR@/lib/maxscale +chmod 0755 @MAXSCALE_VARDIR@/cache/maxscale +chmod 0755 @MAXSCALE_VARDIR@/run/maxscale + +# Copy init.d script and ldconfig file +if [ -f "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale" ] +then + cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale /etc/init.d/ +fi + +if [ -f "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.conf" ] +then + cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.conf /etc/ld.so.conf.d/ +fi + +if [ -d "/usr/lib/systemd/system" -a -f @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.service ] +then + cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.service /usr/lib/systemd/system + systemctl daemon-reload +fi /sbin/ldconfig + +cat <& 2 +********** Notice: MaxScale 1.2 Changes ************** + +MaxScale 1.2 has changed the default installation locations +and various files have changed locations. The configuration +file is now read from /etc/maxscale.cnf (note the lower case name) +and MaxScale data is in /var/lib/maxscale/. + +The default location of binary log files and the authentication cache changed from +/usr/local/mariadb-maxscale/ to /var/lib/maxscale/. + +****************************************************** +EOF diff --git a/etc/postrm.in b/etc/postrm.in index b1e86fca5..7488ebe46 100755 --- a/etc/postrm.in +++ b/etc/postrm.in @@ -3,4 +3,11 @@ if [ "$1" -eq 0 ] then rm -f /etc/init.d/maxscale rm -f /etc/ld.so.conf.d/maxscale.conf + rm -f /usr/lib/systemd/system/maxscale.service +else + # Copy and rename config from old location + if [ -f "/usr/local/mariadb-maxscale/etc/MaxScale.cnf" ] + then + cp "/usr/local/mariadb-maxscale/etc/MaxScale.cnf" "/etc/maxscale.cnf" + fi fi diff --git a/etc/ubuntu/init.d/maxscale.in b/etc/ubuntu/init.d/maxscale.in index 4d9ac1bed..d17cea4ca 100755 --- a/etc/ubuntu/init.d/maxscale.in +++ b/etc/ubuntu/init.d/maxscale.in @@ -21,9 +21,8 @@ # MaxScale HOME, PIDFILE, LIB ############################################# -export MAXSCALE_HOME=@CMAKE_INSTALL_PREFIX@ -export MAXSCALE_PIDFILE=$MAXSCALE_HOME/log/maxscale.pid -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MAXSCALE_HOME/lib +export MAXSCALE_PIDFILE=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@/maxscale ############################### # LSB Exit codes (non-Status) @@ -39,14 +38,14 @@ _RETVAL_STATUS_OK=0 _RETVAL_STATUS_NOT_RUNNING=3 # Sanity checks. -[ -x $MAXSCALE_HOME/bin/maxscale ] || exit $_RETVAL_NOT_INSTALLED +[ -x @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale ] || exit $_RETVAL_NOT_INSTALLED ################################# # stop/start/status related vars ################################# NAME=maxscale -DAEMON=$MAXSCALE_HOME/bin/maxscale - +DAEMON=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale +DAEMON_OPTS=--user=maxscale # Source function library. . /lib/lsb/init-functions @@ -58,11 +57,11 @@ RETVAL=0 start() { log_daemon_msg "Starting MaxScale" - start_daemon -p $MAXSCALE_PIDFILE $DAEMON 2> /dev/null > /dev/null + start_daemon -p "$MAXSCALE_PIDFILE" "$DAEMON" "$DAEMON_OPTS" 2> /dev/null > /dev/null sleep 2 - status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME + status_of_proc -p "$MAXSCALE_PIDFILE" "$DAEMON" $NAME log_end_msg $? } @@ -78,13 +77,13 @@ stop() { reload() { log_daemon_msg "Reloading MaxScale" - kill -HUP $(cat $MAXSCALE_PIDFILE) + kill -HUP $(cat "$MAXSCALE_PIDFILE") log_end_msg $? } maxscale_wait_stop() { - PIDTMP=$(pidofproc -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale) + PIDTMP=$(pidofproc -p "$MAXSCALE_PIDFILE" "$DAEMON") kill -TERM "${PIDTMP:-}" 2> /dev/null; if [ -n "${PIDTMP:-}" ] && kill -0 "${PIDTMP:-}" 2> /dev/null; then local i=0 @@ -116,7 +115,7 @@ case "$1" in # return 3 on any error log_daemon_msg "Checking MaxScale" - status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME + status_of_proc -p "$MAXSCALE_PIDFILE" "$DAEMON" $NAME RETVAL=$? if [ $RETVAL -ne 0 ]; then diff --git a/log_manager/CMakeLists.txt b/log_manager/CMakeLists.txt index 3a2aad56c..3dfd883de 100644 --- a/log_manager/CMakeLists.txt +++ b/log_manager/CMakeLists.txt @@ -2,8 +2,8 @@ if(LOG_DEBUG) add_definitions(-DSS_LOG_DEBUG) endif() add_library(log_manager SHARED log_manager.cc) -target_link_libraries(log_manager pthread aio stdc++ utils) -install(TARGETS log_manager DESTINATION lib) +target_link_libraries(log_manager pthread aio stdc++) +install(TARGETS log_manager DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_TESTS) add_subdirectory(test) endif() diff --git a/log_manager/log_manager.cc b/log_manager/log_manager.cc index a46395e4a..6baf668ec 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -80,9 +81,6 @@ ssize_t log_ses_count[LOGFILE_LAST] = {0}; */ #define MAX_LOGSTRLEN BUFSIZ -/** Use the skygw_ prefix, only for 1.1 compatible builds*/ -#define OLD_LOGNAMES - /** * Path to directory in which all files are stored to shared memory * by the OS. @@ -311,11 +309,7 @@ const char* get_suffix_default(void) const char* get_debug_prefix_default(void) { -#ifdef OLD_LOGNAMES - return "skygw_debug"; -#else return "debug"; -#endif } const char* get_debug_suffix_default(void) @@ -325,11 +319,7 @@ const char* get_debug_suffix_default(void) const char* get_trace_prefix_default(void) { -#ifdef OLD_LOGNAMES - return "skygw_trace"; -#else return "trace"; -#endif } const char* get_trace_suffix_default(void) @@ -339,11 +329,7 @@ const char* get_trace_suffix_default(void) const char* get_msg_prefix_default(void) { -#ifdef OLD_LOGNAMES - return "skygw_msg"; -#else return "messages"; -#endif } const char* get_msg_suffix_default(void) @@ -353,11 +339,7 @@ const char* get_msg_suffix_default(void) const char* get_err_prefix_default(void) { -#ifdef OLD_LOGNAMES - return "skygw_err"; -#else return "error"; -#endif } const char* get_err_suffix_default(void) @@ -367,7 +349,7 @@ const char* get_err_suffix_default(void) const char* get_logpath_default(void) { - return "/tmp"; + return "/var/log/maxscale"; } static bool logmanager_init_nomutex( @@ -1413,7 +1395,7 @@ int skygw_log_write_flush( /** * Write log string to buffer and add to file write list. */ - for(i = LOGFILE_FIRST;i<=LOGFILE_LAST;i <<=1) + for (i = LOGFILE_FIRST; ifn_logpath; /** Set identity string for syslog if it is not set in config.*/ + if(do_syslog) + { syslog_ident_str = (syslog_ident_str == NULL ? (argv == NULL ? strdup(program_invocation_short_name) : strdup(*argv)) : syslog_ident_str); - + } /* ss_dfprintf(stderr, "\n\n\tCommand line : "); for (i=0; iname != NULL && - strcmp(item->name, "last_insert_id()") == 0) + + if (((Item_func*)item)->func_name () != NULL && + strcmp((char*)((Item_func*)item)->func_name (), "last_insert_id") == 0) { func_qtype |= QUERY_TYPE_MASTER_READ; } @@ -1485,7 +1486,8 @@ void parsing_info_done( void* ptr) { parsing_info_t* pi; - + THD* thd; + if (ptr) { pi = (parsing_info_t *)ptr; @@ -1496,6 +1498,8 @@ void parsing_info_done( if (mysql->thd != NULL) { + thd = (THD*)mysql->thd; + thd->end_statement (); (*mysql->methods->free_embedded_thd)(mysql); mysql->thd = NULL; } diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 4a9828ea3..a2e9208e8 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_TESTS OR BUILD_TOOLS) - add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c) + add_library(fullcore STATIC adminusers.c atomic.c config.c buffer.c dbusers.c dcb.c filter.c gwbitmask.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c memlog.c modutil.c monitor.c poll.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c gwdirs.c externcmd.c) if(WITH_JEMALLOC) target_link_libraries(fullcore ${JEMALLOC_LIBRARIES}) elseif(WITH_TCMALLOC) @@ -12,7 +12,7 @@ add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c monitor.c adminusers.c secrets.c filter.c modutil.c hint.c - housekeeper.c memlog.c resultset.c) + housekeeper.c memlog.c resultset.c gwdirs.c externcmd.c) if(WITH_JEMALLOC) target_link_libraries(maxscale ${JEMALLOC_LIBRARIES}) @@ -21,15 +21,15 @@ elseif(WITH_TCMALLOC) endif() target_link_libraries(maxscale ${EMBEDDED_LIB} ${PCRE_LINK_FLAGS} ${CURL_LIBRARIES} log_manager utils ssl aio pthread crypt dl crypto inih z rt m stdc++) -install(TARGETS maxscale DESTINATION bin) +install(TARGETS maxscale DESTINATION ${MAXSCALE_BINDIR}) -add_executable(maxkeys maxkeys.c secrets.c utils.c) +add_executable(maxkeys maxkeys.c secrets.c utils.c gwdirs.c) target_link_libraries(maxkeys log_manager utils pthread crypt crypto) -install(TARGETS maxkeys DESTINATION bin) +install(TARGETS maxkeys DESTINATION ${MAXSCALE_BINDIR}) -add_executable(maxpasswd maxpasswd.c secrets.c utils.c) +add_executable(maxpasswd maxpasswd.c secrets.c utils.c gwdirs.c) target_link_libraries(maxpasswd log_manager utils pthread crypt crypto) -install(TARGETS maxpasswd DESTINATION bin) +install(TARGETS maxpasswd DESTINATION ${MAXSCALE_BINDIR}) if(BUILD_TESTS) add_subdirectory(test) diff --git a/server/core/adminusers.c b/server/core/adminusers.c index 64ea5f224..4b3f9af21 100644 --- a/server/core/adminusers.c +++ b/server/core/adminusers.c @@ -19,13 +19,16 @@ #include #include #include -#define _XOPEN_SOURCE +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif #include #include #include #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -119,12 +122,7 @@ char fname[1024], *home; char uname[80], passwd[80]; initialise(); - if ((home = getenv("MAXSCALE_HOME")) != NULL && strlen(home) < 1024){ - sprintf(fname, "%s/etc/passwd", home); - } - else{ - sprintf(fname, "/usr/local/mariadb-maxscale/etc/passwd"); - } + sprintf(fname, "%s/passwd", get_datadir()); if ((fp = fopen(fname, "r")) == NULL) return NULL; if ((rval = users_alloc()) == NULL) @@ -155,12 +153,7 @@ FILE *fp; char fname[1024], *home, *cpasswd; initialise(); - if ((home = getenv("MAXSCALE_HOME")) != NULL && strlen(home) < 1024){ - sprintf(fname, "%s/etc/passwd", home); - } - else{ - sprintf(fname, "/usr/local/mariadb-maxscale/etc/passwd"); - } + sprintf(fname, "%s/passwd", get_datadir()); if (users == NULL) { @@ -253,15 +246,8 @@ char* admin_remove_user( /** * Open passwd file and remove user from the file. */ - if ((home = getenv("MAXSCALE_HOME")) != NULL && - strnlen(home,PATH_MAX) < PATH_MAX && - strnlen(home,PATH_MAX) > 0) { - sprintf(fname, "%s/etc/passwd", home); - sprintf(fname_tmp, "%s/etc/passwd_tmp", home); - } else { - sprintf(fname, "/usr/local/mariadb-maxscale/etc/passwd"); - sprintf(fname_tmp, "/usr/local/mariadb-maxscale/etc/passwd_tmp"); - } + sprintf(fname, "%s/passwd", get_datadir()); + sprintf(fname_tmp, "%s/passwd_tmp", get_datadir()); /** * Rewrite passwd file from memory. */ diff --git a/server/core/buffer.c b/server/core/buffer.c index 4c788cdd2..c3adacd34 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -197,6 +197,7 @@ GWBUF *rval; rval->gwbuf_info = buf->gwbuf_info; rval->gwbuf_bufobj = buf->gwbuf_bufobj; rval->tail = rval; + rval->next = NULL; CHK_GWBUF(rval); return rval; } diff --git a/server/core/config.c b/server/core/config.c index a2414ea7a..b41309943 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -41,9 +41,9 @@ * 30/10/14 Massimiliano Pinto Added disable_master_failback parameter * 07/11/14 Massimiliano Pinto Addition of monitor timeouts for connect/read/write * 20/02/15 Markus Mäkelä Added connection_timeout parameter for services - * 05/03/15 Massimiliano Pinto Added notification_feedback support + * 05/03/15 Massimiliano Pinto Added notification_feedback support * 20/04/15 Guillaume Lefranc Added available_when_donor parameter - * 22/04/15 Martin Brampton Added disable_master_role_setting parameter + * 22/04/15 Martin Brampton Added disable_master_role_setting parameter * * @endverbatim */ @@ -85,6 +85,7 @@ static int process_config_context(CONFIG_CONTEXT *); static int process_config_update(CONFIG_CONTEXT *); static void free_config_context(CONFIG_CONTEXT *); static char *config_get_value(CONFIG_PARAMETER *, const char *); +static const char *config_get_value_string(CONFIG_PARAMETER *, const char *); static int handle_global_item(const char *, const char *); static int handle_feedback_item(const char *, const char *); static void global_defaults(); @@ -300,18 +301,18 @@ int rval; static int process_config_context(CONFIG_CONTEXT *context) { -CONFIG_CONTEXT *obj; -int error_count = 0; -HASHTABLE* monitorhash; + CONFIG_CONTEXT *obj; + int error_count = 0; + HASHTABLE* monitorhash; -if((monitorhash = hashtable_alloc(5,simple_str_hash,strcmp)) == NULL) -{ - skygw_log_write(LOGFILE_ERROR,"Error: Failed to allocate ,onitor configuration check hashtable."); - return 0; -} + if((monitorhash = hashtable_alloc(5,simple_str_hash,strcmp)) == NULL) + { + skygw_log_write(LOGFILE_ERROR,"Error: Failed to allocate ,monitor configuration check hashtable."); + return 0; + } + hashtable_memory_fns(monitorhash,(HASHMEMORYFN)strdup,NULL,(HASHMEMORYFN)free,NULL); -hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); - /** + /** * Process the data and create the services and servers defined * in the data. */ @@ -345,6 +346,8 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); char *weightby; char *version_string; char *subservices; + char *ssl,*ssl_cert,*ssl_key,*ssl_ca_cert,*ssl_version; + char* ssl_cert_verify_depth; bool is_rwsplit = false; bool is_schemarouter = false; char *allow_localhost_match_wildcard_host; @@ -353,6 +356,12 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); user = config_get_value(obj->parameters, "user"); auth = config_get_value(obj->parameters, "passwd"); subservices = config_get_value(obj->parameters, "subservices"); + ssl = config_get_value(obj->parameters, "ssl"); + ssl_cert = config_get_value(obj->parameters, "ssl_cert"); + ssl_key = config_get_value(obj->parameters, "ssl_key"); + ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert"); + ssl_version = config_get_value(obj->parameters, "ssl_version"); + ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth"); enable_root_user = config_get_value( obj->parameters, "enable_root_user"); @@ -443,7 +452,84 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); max_slave_rlag_str = config_get_value(obj->parameters, "max_slave_replication_lag"); - + + if(ssl) + { + if(ssl_cert == NULL) + { + error_count++; + skygw_log_write(LE,"Error: Server certificate missing for service '%s'." + "Please provide the path to the server certificate by adding the ssl_cert= parameter", + obj->object); + } + if(ssl_ca_cert == NULL) + { + error_count++; + skygw_log_write(LE,"Error: CA Certificate missing for service '%s'." + "Please provide the path to the certificate authority certificate by adding the ssl_ca_cert= parameter", + obj->object); + } + if(ssl_key == NULL) + { + error_count++; + skygw_log_write(LE,"Error: Server private key missing for service '%s'. " + "Please provide the path to the server certificate key by adding the ssl_key= parameter" + ,obj->object); + } + + if(access(ssl_ca_cert,F_OK) != 0) + { + skygw_log_write(LE,"Error: Certificate authority file for service '%s' not found: %s", + obj->object, + ssl_ca_cert); + error_count++; + } + if(access(ssl_cert,F_OK) != 0) + { + skygw_log_write(LE,"Error: Server certificate file for service '%s' not found: %s", + obj->object, + ssl_cert); + error_count++; + } + if(access(ssl_key,F_OK) != 0) + { + skygw_log_write(LE,"Error: Server private key file for service '%s' not found: %s", + obj->object, + ssl_key); + error_count++; + } + + if(error_count == 0) + { + if(serviceSetSSL(obj->element,ssl) != 0) + { + skygw_log_write(LE,"Error: Unknown parameter for service '%s': %s",obj->object,ssl); + error_count++; + } + else + { + serviceSetCertificates(obj->element,ssl_cert,ssl_key,ssl_ca_cert); + if(ssl_version) + { + if(serviceSetSSLVersion(obj->element,ssl_version) != 0) + { + skygw_log_write(LE,"Error: Unknown parameter value for 'ssl_version' for service '%s': %s",obj->object,ssl_version); + error_count++; + } + } + if(ssl_cert_verify_depth) + { + if(serviceSetSSLVerifyDepth(obj->element,atoi(ssl_cert_verify_depth)) != 0) + { + skygw_log_write(LE,"Error: Invalid parameter value for 'ssl_cert_verify_depth' for service '%s': %s",obj->object,ssl_cert_verify_depth); + error_count++; + } + } + } + } + + } + if (enable_root_user) serviceEnableRootUser( obj->element, @@ -678,6 +764,9 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); } if (obj->element) { + SERVER *server = obj->element; + server->persistpoolmax = strtol(config_get_value_string(obj->parameters, "persistpoolmax"), NULL, 0); + server->persistmaxtime = strtol(config_get_value_string(obj->parameters, "persistmaxtime"), NULL, 0); CONFIG_PARAMETER *params = obj->parameters; while (params) { @@ -691,6 +780,10 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); "monitorpw") && strcmp(params->name, "type") + && strcmp(params->name, + "persistpoolmax") + && strcmp(params->name, + "persistmaxtime") ) { serverAddParameter(obj->element, @@ -847,62 +940,69 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); /* if id is not set, do it now */ if (gateway.id == 0) { setipaddress(&serv_addr.sin_addr, (address == NULL) ? "0.0.0.0" : address); - gateway.id = (unsigned long) (serv_addr.sin_addr.s_addr + port != NULL ? atoi(port) : 0 + getpid()); - } - - if (service && socket && protocol) { - CONFIG_CONTEXT *ptr = context; - while (ptr && strcmp(ptr->object, service) != 0) - ptr = ptr->next; - if (ptr && ptr->element) - { - serviceAddProtocol(ptr->element, - protocol, - socket, - 0); - } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Listener '%s', " - "service '%s' not found. " - "Listener will not execute for socket %s.", - obj->object, service, socket))); - error_count++; - } + gateway.id = (unsigned long) (serv_addr.sin_addr.s_addr + (port != NULL ? atoi(port) : 0 + getpid())); } - if (service && port && protocol) { + if(service && protocol && (socket || port)) + { + if (socket) + { CONFIG_CONTEXT *ptr = context; while (ptr && strcmp(ptr->object, service) != 0) - ptr = ptr->next; + ptr = ptr->next; if (ptr && ptr->element) { - serviceAddProtocol(ptr->element, - protocol, - address, - atoi(port)); + serviceAddProtocol(ptr->element, + protocol, + socket, + 0); + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Listener '%s', " + "service '%s' not found. " + "Listener will not execute for socket %s.", + obj->object, service, socket))); + error_count++; + } + } + + if (port) + { + CONFIG_CONTEXT *ptr = context; + while (ptr && strcmp(ptr->object, service) != 0) + ptr = ptr->next; + if (ptr && ptr->element) + { + serviceAddProtocol(ptr->element, + protocol, + address, + atoi(port)); } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Listener '%s', " - "service '%s' not found. " - "Listener will not execute.", - obj->object, service))); - error_count++; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Listener '%s', " + "service '%s' not found. " + "Listener will not execute.", + obj->object, service))); + error_count++; } + } } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Listener '%s' is misisng a " - "required " - "parameter. A Listener must have a " - "service, port and protocol defined.", - obj->object))); - error_count++; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Listener '%s' is missing a " + "required " + "parameter. A Listener must have a " + "service, port and protocol defined.", + obj->object))); + error_count++; } } else if (!strcmp(type, "monitor")) @@ -1081,6 +1181,25 @@ config_get_value(CONFIG_PARAMETER *params, const char *name) return NULL; } +/** + * Get the value of a config parameter as a string + * + * @param params The linked list of config parameters + * @param name The parameter to return + * @return the parameter value or null string if not found + */ +static const char * +config_get_value_string(CONFIG_PARAMETER *params, const char *name) +{ + while (params) + { + if (!strcmp(params->name, name)) + return (const char *)params->value; + params = params->next; + } + return ""; +} + CONFIG_PARAMETER* config_get_param( CONFIG_PARAMETER* params, @@ -1339,7 +1458,7 @@ int i; } else if (strcmp(name, "ms_timestamp") == 0) { - skygw_set_highp(config_truth_value(value)); + skygw_set_highp(config_truth_value((char*)value)); } else { @@ -1347,7 +1466,7 @@ int i; { if (strcasecmp(name, lognames[i].logname) == 0) { - if (config_truth_value(value)) + if (config_truth_value((char*)value)) skygw_log_enable(lognames[i].logfile); else skygw_log_disable(lognames[i].logfile); @@ -1645,9 +1764,6 @@ SERVER *server; char *enable_root_user; char *connection_timeout; char *allow_localhost_match_wildcard_host; - char *auth_all_servers; - char *optimize_wildcard; - char *strip_db_esc; enable_root_user = config_get_value(obj->parameters, @@ -1655,16 +1771,6 @@ SERVER *server; connection_timeout = config_get_value(obj->parameters, "connection_timeout"); - - auth_all_servers = - config_get_value(obj->parameters, - "auth_all_servers"); - optimize_wildcard = - config_get_value(obj->parameters, - "optimize_wildcard"); - strip_db_esc = - config_get_value(obj->parameters, - "strip_db_esc"); allow_localhost_match_wildcard_host = config_get_value(obj->parameters, "localhost_match_wildcard_host"); @@ -1735,6 +1841,9 @@ SERVER *server; obj->element = server_alloc(address, protocol, atoi(port)); + + server_set_unique_name(obj->element, obj->object); + if (obj->element && monuser && monpw) { serverAddMonUser(obj->element, @@ -1922,6 +2031,12 @@ static char *service_params[] = "version_string", "filters", "weightby", + "ssl_cert", + "ssl_ca_cert", + "ssl", + "ssl_key", + "ssl_version", + "ssl_cert_verify_depth", NULL }; @@ -1943,6 +2058,9 @@ static char *monitor_params[] = "servers", "user", "passwd", + "script", + "events", + "mysql51_replication", "monitor_interval", "detect_replication_lag", "detect_stale_master", diff --git a/server/core/dbusers.c b/server/core/dbusers.c index fd005bfb2..d929fa68d 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -210,7 +210,7 @@ HASHTABLE *oldresources; oldusers = service->users; /* digest compare */ - if (memcmp(oldusers->cksum, newusers->cksum, SHA_DIGEST_LENGTH) == 0) { + if (oldusers != NULL && memcmp(oldusers->cksum, newusers->cksum, SHA_DIGEST_LENGTH) == 0) { /* same data, nothing to do */ LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, @@ -234,7 +234,7 @@ HASHTABLE *oldresources; spinlock_release(&service->spin); - if (i) { + if (i && oldusers) { /* free the old table */ users_free(oldusers); } @@ -974,7 +974,7 @@ getAllUsers(SERVICE *service, USERS *users) } } - if(service->optimize_wildcard && havedb && wildcard_db_grant(dbnm)) + if(havedb && wildcard_db_grant(dbnm) && service->optimize_wildcard) { rc = add_wildcard_users(users, row[0], row[1], password, row[4], dbnm, service->resources); skygw_log_write(LOGFILE_DEBUG|LOGFILE_TRACE,"%s: Converted '%s' to %d individual database grants.",service->name,dbnm,rc); @@ -984,9 +984,9 @@ getAllUsers(SERVICE *service, USERS *users) rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], havedb ? dbnm : NULL); } - skygw_log_write(LOGFILE_DEBUG,"%s: Adding user:%s host:%s anydb:%s db:%s.", + LOGIF(LD,(skygw_log_write(LOGFILE_DEBUG,"%s: Adding user:%s host:%s anydb:%s db:%s.", service->name,row[0],row[1],row[4], - havedb ? dbnm : NULL); + havedb ? dbnm : NULL))); } else { /* we don't have dbgrants, simply set ANY DB for the user */ rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); @@ -1033,8 +1033,8 @@ getAllUsers(SERVICE *service, USERS *users) } else if(rc == -1) { /** Duplicate user*/ - LOGIF(LE,(skygw_log_write(LT|LE, - "Warning: Duplicate MySQL user found for service [%s]: %s@%s%s%s", + LOGIF(LT,(skygw_log_write(LT, + "Duplicate MySQL user found for service [%s]: %s@%s%s%s", service->name, row[0],row[1],havedb?" for database: ":"", havedb ?dbnm:""))); @@ -1118,6 +1118,7 @@ getUsers(SERVICE *service, USERS *users) MYSQL_DATABASE_MAXLEN; int dbnames = 0; int db_grants = 0; + char dbnm[MYSQL_DATABASE_MAXLEN+1]; if (serviceGetUser(service, &service_user, &service_passwd) == 0) { @@ -1474,16 +1475,40 @@ getUsers(SERVICE *service, USERS *users) */ if (db_grants) { - /* we have dbgrants, store them */ + bool havedb = false; + /* we have dbgrants, store them */ + if(row[5]){ + unsigned long *rowlen = mysql_fetch_lengths(result); + memcpy(dbnm,row[5],rowlen[5]); + memset(dbnm + rowlen[5],0,1); + havedb = true; + if(service->strip_db_esc) { + strip_escape_chars(dbnm); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "[%s]: %s -> %s", + service->name, + row[5], + dbnm))); + } + } - if(service->optimize_wildcard && wildcard_db_grant(row[5])) + if(havedb && wildcard_db_grant(row[5])) { - rc = add_wildcard_users(users, row[0], row[1], password, row[4], row[5], service->resources); - skygw_log_write(LOGFILE_DEBUG|LOGFILE_TRACE,"%s: Converted '%s' to %d individual database grants.",service->name,row[5],rc); + if(service->optimize_wildcard) + { + rc = add_wildcard_users(users, row[0], row[1], password, row[4], dbnm, service->resources); + skygw_log_write(LOGFILE_DEBUG|LOGFILE_TRACE,"%s: Converted '%s' to %d individual database grants.",service->name,row[5],rc); + } + else + { + /** Use ANYDB for wildcard grants */ + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); + } } else { - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], havedb ? dbnm : NULL); } } else { @@ -1683,6 +1708,41 @@ static int uh_cmpfun( void* v1, void* v2) { return 0; } + if(hu2->resource && strlen(hu2->resource) && strchr(hu2->resource,'%') != NULL) + { + regex_t re; + char db[MYSQL_DATABASE_MAXLEN*2 +1]; + strcpy(db,hu2->resource); + int len = strlen(db); + char* ptr = strrchr(db,'%'); + + if(ptr == NULL) + { + return 1; + } + + while(ptr) + { + memmove(ptr+1,ptr,(len - (ptr - db)) + 1); + *ptr = '.'; + *(ptr + 1) = '*'; + len = strlen(db); + ptr = strrchr(db,'%'); + } + + if((regcomp(&re,db,REG_ICASE|REG_NOSUB))) + { + return 1; + } + + if(regexec(&re,hu1->resource,0,NULL,0) == 0) + { + regfree(&re); + return 0; + } + regfree(&re); + } + /* no matches, deny auth */ return 1; } @@ -1792,18 +1852,6 @@ char *mysql_format_user_entry(void *data) return mysql_user; } -/* - * The hash function we use for storing MySQL database names. - * - * @param key The key value - * @return The hash key - */ -int -resource_hash(char *key) -{ - return (*key + *(key + 1)); -} - /** * Remove the resources table * @@ -1827,7 +1875,7 @@ resource_alloc() { HASHTABLE *resources; - if ((resources = hashtable_alloc(10, resource_hash, strcmp)) == NULL) + if ((resources = hashtable_alloc(10, simple_str_hash, strcmp)) == NULL) { return NULL; } diff --git a/server/core/dcb.c b/server/core/dcb.c index 6717aea41..889178fd5 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -49,6 +49,12 @@ * backend * 07/05/2014 Mark Riddoch Addition of callback mechanism * 20/06/2014 Mark Riddoch Addition of dcb_clone + * 29/05/2015 Markus Makela Addition of dcb_write_SSL + * 11/06/2015 Martin Brampton Persistent connnections and tidy up + * 07/07/2015 Martin Brampton Merged add to zombieslist into dcb_close, + * fixes for various error situations, + * remove dcb_set_state etc, simplifications. + * 10/07/2015 Martin Brampton Simplify, merge dcb_read and dcb_read_n * * @endverbatim */ @@ -56,6 +62,8 @@ #include #include #include +#include +#include #include #include #include @@ -83,31 +91,29 @@ static SPINLOCK dcbspin = SPINLOCK_INIT; static SPINLOCK zombiespin = SPINLOCK_INIT; static void dcb_final_free(DCB *dcb); -static bool dcb_set_state_nomutex( - DCB* dcb, - const dcb_state_t new_state, - dcb_state_t* old_state); static void dcb_call_callback(DCB *dcb, DCB_REASON reason); -static DCB* dcb_get_next (DCB* dcb); +static DCB * dcb_get_next (DCB *dcb); static int dcb_null_write(DCB *dcb, GWBUF *buf); static int dcb_null_close(DCB *dcb); static int dcb_null_auth(DCB *dcb, SERVER *server, SESSION *session, GWBUF *buf); -static int dcb_isvalid_nolock(DCB *dcb); +static inline int dcb_isvalid_nolock(DCB *dcb); +static inline DCB * dcb_find_in_list(DCB *dcb); +static inline void dcb_process_victim_queue(DCB *listofdcb); +static void dcb_close_finish(DCB *); +static bool dcb_maybe_add_persistent(DCB *); +static inline bool dcb_write_parameter_check(DCB *dcb, GWBUF *queue); +#if defined(FAKE_CODE) +static inline void dcb_write_fake_code(DCB *dcb); +#endif +static inline void dcb_write_when_already_queued(DCB *dcb, GWBUF *queue); +static int dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno); +static inline void dcb_write_tidy_up(DCB *dcb, bool below_water); +static int dcb_write_SSL_error_report (DCB *dcb, int ret); size_t dcb_get_session_id( - DCB* dcb) + DCB *dcb) { - size_t rval; - - if (dcb != NULL && dcb->session != NULL) - { - rval = dcb->session->ses_id; - } - else - { - rval = 0; - } - return rval; + return (dcb && dcb->session) ? dcb->session->ses_id : 0; } /** @@ -123,27 +129,17 @@ size_t dcb_get_session_id( *parameters was NULL. */ bool dcb_get_ses_log_info( - DCB* dcb, - size_t* sesid, - int* enabled_logs) + DCB *dcb, + size_t *sesid, + int *enabled_logs) { - bool succp; - - if (dcb == NULL || - dcb->session == NULL || - sesid == NULL || - enabled_logs == NULL) - { - succp = false; - } - else - { - *sesid = dcb->session->ses_id; - *enabled_logs = dcb->session->ses_enabled_logs; - succp = true; - } - - return succp; + if (sesid && enabled_logs && dcb && dcb->session) + { + *sesid = dcb->session->ses_id; + *enabled_logs = dcb->session->ses_enabled_logs; + return true; + } + return false; } /** @@ -169,63 +165,67 @@ dcb_get_zombies(void) DCB * dcb_alloc(dcb_role_t role) { -DCB *rval; +DCB *newdcb; - if ((rval = calloc(1, sizeof(DCB))) == NULL) - { - return NULL; - } -#if defined(SS_DEBUG) - rval->dcb_chk_top = CHK_NUM_DCB; - rval->dcb_chk_tail = CHK_NUM_DCB; -#endif - rval->dcb_errhandle_called = false; - rval->dcb_role = role; - spinlock_init(&rval->dcb_initlock); - spinlock_init(&rval->writeqlock); - spinlock_init(&rval->delayqlock); - spinlock_init(&rval->authlock); - spinlock_init(&rval->cb_lock); - spinlock_init(&rval->pollinlock); - spinlock_init(&rval->polloutlock); - rval->pollinbusy = 0; - rval->readcheck = 0; - rval->polloutbusy = 0; - rval->writecheck = 0; - rval->fd = DCBFD_CLOSED; + if ((newdcb = calloc(1, sizeof(DCB))) == NULL) + { + return NULL; + } + newdcb->dcb_chk_top = CHK_NUM_DCB; + newdcb->dcb_chk_tail = CHK_NUM_DCB; + + newdcb->dcb_errhandle_called = false; + newdcb->dcb_role = role; + spinlock_init(&newdcb->dcb_initlock); + spinlock_init(&newdcb->writeqlock); + spinlock_init(&newdcb->delayqlock); + spinlock_init(&newdcb->authlock); + spinlock_init(&newdcb->cb_lock); + spinlock_init(&newdcb->pollinlock); + spinlock_init(&newdcb->polloutlock); + newdcb->pollinbusy = 0; + newdcb->readcheck = 0; + newdcb->polloutbusy = 0; + newdcb->writecheck = 0; + newdcb->fd = DCBFD_CLOSED; - rval->evq.next = NULL; - rval->evq.prev = NULL; - rval->evq.pending_events = 0; - rval->evq.processing = 0; - spinlock_init(&rval->evq.eventqlock); + newdcb->evq.next = NULL; + newdcb->evq.prev = NULL; + newdcb->evq.pending_events = 0; + newdcb->evq.processing = 0; + spinlock_init(&newdcb->evq.eventqlock); - memset(&rval->stats, 0, sizeof(DCBSTATS)); // Zero the statistics - rval->state = DCB_STATE_ALLOC; - bitmask_init(&rval->memdata.bitmask); - rval->writeqlen = 0; - rval->high_water = 0; - rval->low_water = 0; - rval->next = NULL; - rval->callbacks = NULL; - rval->data = NULL; + memset(&newdcb->stats, 0, sizeof(DCBSTATS)); // Zero the statistics + newdcb->state = DCB_STATE_ALLOC; + bitmask_init(&newdcb->memdata.bitmask); + newdcb->writeqlen = 0; + newdcb->high_water = 0; + newdcb->low_water = 0; + newdcb->session = NULL; + newdcb->server = NULL; + newdcb->service = NULL; + newdcb->next = NULL; + newdcb->nextpersistent = NULL; + newdcb->persistentstart = 0; + newdcb->callbacks = NULL; + newdcb->data = NULL; - rval->remote = NULL; - rval->user = NULL; - rval->flags = 0; + newdcb->remote = NULL; + newdcb->user = NULL; + newdcb->flags = 0; - spinlock_acquire(&dcbspin); - if (allDCBs == NULL) - allDCBs = rval; - else - { - DCB *ptr = allDCBs; - while (ptr->next) - ptr = ptr->next; - ptr->next = rval; - } - spinlock_release(&dcbspin); - return rval; + spinlock_acquire(&dcbspin); + if (allDCBs == NULL) + allDCBs = newdcb; + else + { + DCB *ptr = allDCBs; + while (ptr->next) + ptr = ptr->next; + ptr->next = newdcb; + } + spinlock_release(&dcbspin); + return newdcb; } @@ -237,62 +237,16 @@ DCB *rval; void dcb_free(DCB *dcb) { - if (dcb->fd == DCBFD_CLOSED) - { - dcb_final_free(dcb); - } - else - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Attempt to free a DCB via dcb_free " - "that has been associated with a descriptor."))); - } -} - -/** - * Add the DCB to the end of zombies list. - * - * Adding to list occurs once per DCB. This is ensured by changing the - * state of DCB to DCB_STATE_ZOMBIE after addition. Prior insertion, DCB state - * is checked and operation proceeds only if state differs from DCB_STATE_ZOMBIE. - * @param dcb The DCB to add to the zombie list - * @return none - */ -void -dcb_add_to_zombieslist(DCB *dcb) -{ - bool succp = false; - dcb_state_t prev_state = DCB_STATE_UNDEFINED; - - CHK_DCB(dcb); - - /*< - * Protect zombies list access. - */ - spinlock_acquire(&zombiespin); - /*< - * If dcb is already added to zombies list, return. - */ - if (dcb->state != DCB_STATE_NOPOLLING) { - ss_dassert(dcb->state != DCB_STATE_POLLING && - dcb->state != DCB_STATE_LISTENING); - spinlock_release(&zombiespin); - return; - } - /*< - * Add closing dcb to the top of the list. - */ - dcb->memdata.next = zombies; - zombies = dcb; - /*< - * Set state which indicates that it has been added to zombies - * list. - */ - succp = dcb_set_state(dcb, DCB_STATE_ZOMBIE, &prev_state); - ss_info_dassert(succp, "Failed to set DCB_STATE_ZOMBIE"); - - spinlock_release(&zombiespin); + if (dcb->fd != DCBFD_CLOSED) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Attempt to free a DCB via dcb_free " + "that has been associated with a descriptor."))); + } + raise(SIGABRT); + /* Another statement to avoid a compiler warning */ + dcb_final_free(dcb); } /* @@ -305,31 +259,28 @@ dcb_add_to_zombieslist(DCB *dcb) DCB * dcb_clone(DCB *orig) { -DCB *clone; +DCB *clonedcb; - if ((clone = dcb_alloc(DCB_ROLE_REQUEST_HANDLER)) == NULL) - { - return NULL; - } + if ((clonedcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER))) + { + clonedcb->fd = DCBFD_CLOSED; + clonedcb->flags |= DCBF_CLONE; + clonedcb->state = orig->state; + clonedcb->data = orig->data; + if (orig->remote) + clonedcb->remote = strdup(orig->remote); + if (orig->user) + clonedcb->user = strdup(orig->user); + clonedcb->protocol = orig->protocol; - clone->fd = DCBFD_CLOSED; - clone->flags |= DCBF_CLONE; - clone->state = orig->state; - clone->data = orig->data; - if (orig->remote) - clone->remote = strdup(orig->remote); - if (orig->user) - clone->user = strdup(orig->user); - clone->protocol = orig->protocol; - - clone->func.write = dcb_null_write; - /** - * 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; + clonedcb->func.write = dcb_null_write; + /** + * Close triggers closing of router session as well which is needed. + */ + clonedcb->func.close = orig->func.close; + clonedcb->func.auth = dcb_null_auth; + } + return clonedcb; } /** @@ -343,7 +294,7 @@ DCB *clone; static void dcb_final_free(DCB *dcb) { -DCB_CALLBACK *cb; + DCB_CALLBACK *cb; CHK_DCB(dcb); ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED || @@ -408,7 +359,9 @@ DCB_CALLBACK *cb; } if (dcb->protocol && (!DCB_IS_CLONE(dcb))) - free(dcb->protocol); + free(dcb->protocol); + if (dcb->protoname) + free(dcb->protoname); if (dcb->remote) free(dcb->remote); if (dcb->user) @@ -432,7 +385,8 @@ DCB_CALLBACK *cb; free(cb); } spinlock_release(&dcb->cb_lock); - + if(dcb->ssl) + SSL_free(dcb->ssl); bitmask_free(&dcb->memdata.bitmask); free(dcb); } @@ -452,9 +406,9 @@ DCB_CALLBACK *cb; DCB * dcb_process_zombies(int threadid) { -DCB *ptr, *lptr; -DCB* dcb_list = NULL; -DCB* dcb = NULL; +DCB *zombiedcb, *previousdcb; +DCB *listofdcb = NULL; +DCB *dcb = NULL; bool succp = false; /** @@ -477,147 +431,165 @@ bool succp = false; * spinlock. */ spinlock_acquire(&zombiespin); - ptr = zombies; - lptr = NULL; - while (ptr) + zombiedcb = zombies; + previousdcb = NULL; + while (zombiedcb) { - CHK_DCB(ptr); + CHK_DCB(zombiedcb); /* * Skip processing of DCB's that are * in the event queue waiting to be processed. */ - if (ptr->evq.next || ptr->evq.prev) + if (zombiedcb->evq.next || zombiedcb->evq.prev) { - lptr = ptr; - ptr = ptr->memdata.next; + previousdcb = zombiedcb; + zombiedcb = zombiedcb->memdata.next; } else { - bitmask_clear(&ptr->memdata.bitmask, threadid); + bitmask_clear(&zombiedcb->memdata.bitmask, threadid); - if (bitmask_isallclear(&ptr->memdata.bitmask)) + if (bitmask_isallclear(&zombiedcb->memdata.bitmask)) { /** * Remove the DCB from the zombie queue * and call the final free routine for the * DCB * - * ptr is the DCB we are processing - * lptr is the previous DCB on the zombie queue - * or NULL if the DCB is at the head of the - * queue tptr is the DCB after the one we are - * processing on the zombie queue + * zombiedcb is the DCB we are processing + * previousdcb is the previous DCB on the zombie + * queue or NULL if the DCB is at the head of the + * queue. Remove zombiedcb from the zombies list. */ - DCB *tptr = ptr->memdata.next; - if (lptr == NULL) - zombies = tptr; + if (previousdcb == NULL) + zombies = zombiedcb->memdata.next; else - lptr->memdata.next = tptr; + previousdcb->memdata.next = zombiedcb->memdata.next; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [dcb_process_zombies] Remove dcb " - "%p fd %d " "in state %s from the " + "%p fd %d in state %s from the " "list of zombies.", pthread_self(), - ptr, - ptr->fd, - STRDCBSTATE(ptr->state)))); - ss_info_dassert(ptr->state == DCB_STATE_ZOMBIE, - "dcb not in DCB_STATE_ZOMBIE state."); + zombiedcb, + zombiedcb->fd, + STRDCBSTATE(zombiedcb->state)))); + ss_info_dassert(zombiedcb->state == DCB_STATE_ZOMBIE, + "dcb not in DCB_STATE_ZOMBIE state."); /*< - * Move dcb to linked list of victim dcbs. + * Move zombie dcb to linked list of victim dcbs. + * The variable dcb is used to hold the last DCB + * to have been added to the linked list, or NULL + * if none has yet been added. If the list + * (listofdcb) is not NULL, then it follows that + * dcb will also not be null. */ - if (dcb_list == NULL) { - dcb_list = ptr; - dcb = dcb_list; - } else { - dcb->memdata.next = ptr; - dcb = dcb->memdata.next; + if (listofdcb == NULL) + { + listofdcb = zombiedcb; + } + else + { + dcb->memdata.next = zombiedcb; } + /* Set dcb for next iteration of loop */ + dcb = zombiedcb; + zombiedcb = zombiedcb->memdata.next; + /* After we've moved zombiedcb forward, set + link to null as dcb is last of the new list */ dcb->memdata.next = NULL; - ptr = tptr; } else { - lptr = ptr; - ptr = ptr->memdata.next; + /* Since we didn't remove this dcb from the zombies + list, we need to advance the previous pointer */ + previousdcb = zombiedcb; + zombiedcb = zombiedcb->memdata.next; } } } spinlock_release(&zombiespin); - /* - * Process the victim queue. These are DCBs that are not in - * use by any thread. - * The corresponding file descriptor is closed, the DCB marked - * as disconnected and the DCB itself is fianlly freed. - */ - dcb = dcb_list; - while (dcb != NULL) { - DCB* dcb_next = NULL; - int rc = 0; - - 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)))); - } - 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; -#endif /* FAKE_CODE */ - } - } - LOGIF_MAYBE(LT, (dcb_get_ses_log_info( - dcb, - &tls_log_info.li_sesid, - &tls_log_info.li_enabled_logs))); - - succp = dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); - ss_dassert(succp); - dcb_next = dcb->memdata.next; - dcb_final_free(dcb); - dcb = dcb_next; - } - /** Reset threads session data */ - LOGIF(LT, tls_log_info.li_sesid = 0); - + dcb_process_victim_queue(listofdcb); + return zombies; +} + +/** + * Process the victim queue, selected from the list of zombies + * + * These are the DCBs that are not in use by any thread. The corresponding + * file descriptor is closed, the DCB marked as disconnected and the DCB + * itself is finally freed. + * + * @param listofdcb The first victim DCB + */ +static inline void +dcb_process_victim_queue(DCB *listofdcb) +{ + DCB *dcb; + + dcb = listofdcb; + while (dcb != NULL) + { + DCB *nextdcb = NULL; + if (dcb->fd > 0) + { + /*< + * Close file descriptor and move to clean-up phase. + */ + if (close(dcb->fd) < 0) + { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [dcb_process_victim_queue] Error : Failed to close " + "socket %d on dcb %p due error %d, %s.", + pthread_self(), + dcb->fd, + dcb, + eno, + strerror(eno)))); + } + else + { + dcb->fd = DCBFD_CLOSED; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [dcb_process_victim_queue] Closed socket " + "%d on dcb %p.", + pthread_self(), + dcb->fd, + dcb))); +#if defined(FAKE_CODE) + conn_open[dcb->fd] = false; +#endif /* FAKE_CODE */ + } + } + LOGIF_MAYBE(LT, (dcb_get_ses_log_info( + dcb, + &tls_log_info.li_sesid, + &tls_log_info.li_enabled_logs))); + + dcb->state = DCB_STATE_DISCONNECTED; + nextdcb = dcb->memdata.next; + dcb_final_free(dcb); + dcb = nextdcb; + } + /** Reset threads session data */ + LOGIF(LT, tls_log_info.li_sesid = 0); } /** * Connect to a server * * This routine will create a server connection - * If succesful the new dcb will be put in + * If successful the new dcb will be put in * epoll set by dcb->func.connect * * @param server The server to connect to @@ -628,11 +600,49 @@ bool succp = false; DCB * dcb_connect(SERVER *server, SESSION *session, const char *protocol) { -DCB *dcb; -GWPROTOCOL *funcs; -int fd; -int rc; + DCB *dcb; + GWPROTOCOL *funcs; + int fd; + int rc; + char *user; + user = session_getUser(session); + if (user && strlen(user)) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_connect] Looking for persistent connection DCB user %s protocol %s\n", pthread_self(), user, protocol))); + dcb = server_get_persistent(server, user, protocol); + if (dcb) + { + /** + * Link dcb to session. Unlink is called in dcb_final_free + */ + if (!session_link_dcb(session, dcb)) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_connect] Failed to link to session, the " + "session has been removed.\n", + pthread_self()))); + dcb_close(dcb); + return NULL; + } + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_connect] Reusing a persistent connection, dcb %p\n", pthread_self(), dcb))); + dcb->persistentstart = 0; + return dcb; + } + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_connect] Failed to find a reusable persistent connection.\n", + pthread_self()))); + } + } + if ((dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER)) == NULL) { return NULL; @@ -641,7 +651,7 @@ int rc; if ((funcs = (GWPROTOCOL *)load_module(protocol, MODULE_PROTOCOL)) == NULL) { - dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); + dcb->state = DCB_STATE_DISCONNECTED; dcb_final_free(dcb); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -652,6 +662,7 @@ int rc; return NULL; } memcpy(&(dcb->func), funcs, sizeof(GWPROTOCOL)); + dcb->protoname = strdup(protocol); /** * Link dcb to session. Unlink is called in dcb_final_free @@ -679,7 +690,7 @@ int rc; dcb, session->client, session->client->fd))); - dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); + dcb->state = DCB_STATE_DISCONNECTED; dcb_final_free(dcb); return NULL; } else { @@ -707,7 +718,7 @@ int rc; /** Copy status field to DCB */ dcb->dcb_server_status = server->status; - ss_debug(dcb->dcb_port = server->port;) + dcb->dcb_port = server->port; /** * backend_dcb is connected to backend server, and once backend_dcb @@ -721,8 +732,8 @@ int rc; */ rc = poll_add_dcb(dcb); - if (rc == DCBFD_CLOSED) { - dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); + if (rc) { + dcb->state = DCB_STATE_DISCONNECTED; dcb_final_free(dcb); return NULL; } @@ -735,27 +746,175 @@ int rc; return dcb; } - /** * General purpose read routine to read data from a socket in the * Descriptor Control Block and append it to a linked list of buffers. - * The list may be empty, in which case *head == NULL + * The list may be empty, in which case *head == NULL. The third + * parameter indicates the maximum number of bytes to be read (needed + * for SSL processing) with 0 meaning no limit. + * + * @param dcb The DCB to read from + * @param head Pointer to linked list to append data to + * @param maxbytes Maximum bytes to read (0 = no limit) + * @return -1 on error, otherwise the number of read bytes on + * the last iteration of while loop. 0 is returned if no data available. + */ +int dcb_read( + DCB *dcb, + GWBUF **head, + int maxbytes) +{ + GWBUF *buffer = NULL; + int bytesavailable; + int nsingleread = 0; + int nreadtotal = 0; + + CHK_DCB(dcb); + + if (dcb->fd <= 0) + { + /* */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [dcb_read] Error : Read failed, dcb is %s.", + pthread_self(), + dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not readable"))); + /* */ + return 0; + } + + while (0 == maxbytes || nreadtotal < maxbytes) + { + int bufsize; + + if (-1 == ioctl(dcb->fd, FIONREAD, &bytesavailable)) + { + /* */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [dcb_read] Error : ioctl FIONREAD for dcb %p in " + "state %s fd %d failed due error %d, %s.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + errno, + strerror(errno)))); + /* */ + return -1; + } + + if (bytesavailable == 0) + { + /** Handle closed client socket */ + if (nreadtotal == 0 && dcb_isclient(dcb)) + { + char c; + int l_errno = 0; + int r = -1; + + /* try to read 1 byte, without consuming the socket buffer */ + r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK); + l_errno = errno; + + if (r <= 0 && + l_errno != EAGAIN && + l_errno != EWOULDBLOCK && + l_errno != 0) + { + return -1; + } + } + return 0; + } + + dcb->last_read = hkheartbeat; + + bufsize = MIN(bytesavailable, MAX_BUFFER_SIZE); + if (maxbytes) bufsize = MIN(bufsize, maxbytes-nreadtotal); + + if ((buffer = gwbuf_alloc(bufsize)) == NULL) + { + /*< + * This is a fatal error which should cause shutdown. + * Todo shutdown if memory allocation fails. + */ + /* */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [dcb_read] Error : Failed to allocate read buffer " + "for dcb %p fd %d, due %d, %s.", + pthread_self(), + dcb, + dcb->fd, + errno, + strerror(errno)))); + /* */ + return -1; + } + GW_NOINTR_CALL(nsingleread = read(dcb->fd, GWBUF_DATA(buffer), bufsize); + dcb->stats.n_reads++); + + if (nsingleread <= 0) + { + if (errno != 0 && errno != EAGAIN && errno != EWOULDBLOCK) + { + /* */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [dcb_read] Error : Read failed, dcb %p in state " + "%s fd %d, due %d, %s.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + errno, + strerror(errno)))); + /* */ + } + gwbuf_free(buffer); + return nsingleread; + } + nreadtotal += nsingleread; + /* */ + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_read] Read %d bytes from dcb %p in state %s " + "fd %d.", + pthread_self(), + nsingleread, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + /* */ + /*< Append read data to the gwbuf */ + *head = gwbuf_append(*head, buffer); + } /*< while (0 == maxbytes || nreadtotal < maxbytes) */ + + return nsingleread; +} + +/** + * General purpose read routine to read data from a socket through the SSL + * structure lined with this DCB and append it to a linked list of buffers. + * The list may be empty, in which case *head == NULL. The SSL structure should + * be initialized and the SSL handshake should be done. * * @param dcb The DCB to read from * @param head Pointer to linked list to append data to - * @return -1 on error, otherwise the number of read bytes on the last + * @return -1 on error, otherwise the number of read bytes on the last * iteration of while loop. 0 is returned if no data available. */ -int dcb_read( - DCB *dcb, +int dcb_read_SSL( + DCB *dcb, GWBUF **head) { GWBUF *buffer = NULL; - int b; + int b,pending; int rc; int n; int nread = 0; - + int ssl_errno = 0; CHK_DCB(dcb); if (dcb->fd <= 0) @@ -771,10 +930,10 @@ int dcb_read( while (true) { int bufsize; - + ssl_errno = 0; rc = ioctl(dcb->fd, FIONREAD, &b); - - if (rc == -1) + pending = SSL_pending(dcb->ssl); + if (rc == -1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -789,41 +948,48 @@ int dcb_read( goto return_n; } - if (b == 0 && nread == 0) - { + if (b == 0 && pending == 0 && nread == 0) + { /** Handle closed client socket */ - if (dcb_isclient(dcb)) + if (dcb_isclient(dcb)) { - char c; - int l_errno = 0; + char c = 0; int r = -1; - + /* try to read 1 byte, without consuming the socket buffer */ - r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK); - l_errno = errno; - - if (r <= 0 && - l_errno != EAGAIN && - l_errno != EWOULDBLOCK && - l_errno != 0) + r = SSL_peek(dcb->ssl, &c, sizeof(char)); + if (r <= 0) { + ssl_errno = SSL_get_error(dcb->ssl,r); + if(ssl_errno != SSL_ERROR_WANT_READ && + ssl_errno != SSL_ERROR_WANT_WRITE && + ssl_errno != SSL_ERROR_NONE) n = -1; - goto return_n; + else + n = 0; + goto return_n; } } n = 0; goto return_n; } - else if (b == 0) + else if (b == 0 && pending == 0) { n = 0; goto return_n; } +#ifdef SS_DEBUG + else + { + skygw_log_write_flush(LD,"Total: %d Socket: %d Pending: %d", + nread,b,pending); + } +#endif dcb->last_read = hkheartbeat; bufsize = MIN(b, MAX_BUFFER_SIZE); - + if ((buffer = gwbuf_alloc(bufsize)) == NULL) { /*< @@ -835,49 +1001,109 @@ int dcb_read( "Error : Failed to allocate read buffer " "for dcb %p fd %d, due %d, %s.", dcb, - dcb->fd, + dcb->fd, errno, strerror(errno)))); - + n = -1; goto return_n; } - GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); - dcb->stats.n_reads++); - - if (n <= 0) - { - if (errno != 0 && errno != EAGAIN && errno != EWOULDBLOCK) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Read failed, dcb %p in state " - "%s fd %d, due %d, %s.", - dcb, - STRDCBSTATE(dcb->state), - dcb->fd, - errno, - strerror(errno)))); - } + + n = SSL_read(dcb->ssl, GWBUF_DATA(buffer), bufsize); + dcb->stats.n_reads++; + + if (n < 0) + { + char errbuf[200]; + ssl_errno = SSL_get_error(dcb->ssl,n); +#ifdef SS_DEBUG + if(ssl_errno == SSL_ERROR_SSL || + ssl_errno == SSL_ERROR_SYSCALL) + { + int eno; + while((eno = ERR_get_error()) != 0) + { + ERR_error_string_n(eno,errbuf,200); + skygw_log_write(LE, + "%s", + errbuf); + } + } +#endif + if(ssl_errno == SSL_ERROR_WANT_READ || + ssl_errno == SSL_ERROR_WANT_WRITE || + ssl_errno == SSL_ERROR_NONE) + { + n = 0; + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Read failed, dcb %p in state " + "%s fd %d, SSL error %d: %s.", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ssl_errno, + strerror(errno)))); + + if(ssl_errno == SSL_ERROR_SSL || + ssl_errno == SSL_ERROR_SYSCALL) + { + while((ssl_errno = ERR_get_error()) != 0) + { + ERR_error_string_n(ssl_errno,errbuf,200); + skygw_log_write(LE, + "%s", + errbuf); + } + } + } + n = -1; gwbuf_free(buffer); - goto return_n; - } + goto return_n; + } + else if(n == 0) + { + gwbuf_free(buffer); + goto return_n; + } + + buffer = gwbuf_rtrim(buffer,bufsize - n); + if(buffer == NULL) + { + goto return_n; + } +#ifdef SS_DEBUG + skygw_log_write(LD,"%lu SSL: Truncated buffer from %d to %d bytes. " + "Read %d bytes, %d bytes waiting.\n",pthread_self(), + bufsize,GWBUF_LENGTH(buffer),n,b); + + if(GWBUF_LENGTH(buffer) != n){ + skygw_log_sync_all(); + } + + ss_info_dassert((buffer->start <= buffer->end),"Buffer start has passed end."); + ss_info_dassert(GWBUF_LENGTH(buffer) == n,"Buffer size not equal to read bytes."); +#endif nread += n; - + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, - "%lu [dcb_read] Read %d bytes from dcb %p in state %s " - "fd %d.", + "%lu [dcb_read_SSL] Read %d bytes from dcb %p in state %s " + "fd %d.", pthread_self(), n, dcb, STRDCBSTATE(dcb->state), dcb->fd))); + /*< Append read data to the gwbuf */ *head = gwbuf_append(*head, buffer); } /*< while (true) */ return_n: - return n; + return nread; } /** @@ -889,231 +1115,473 @@ return_n: int dcb_write(DCB *dcb, GWBUF *queue) { -int w; -int saved_errno = 0; +int written; 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 - * authentication yet and thus they have no information about router - * being closed. Session state is changed to SESSION_STATE_STOPPING - * before router's closeSession is called and that tells that DCB may - * still be writable. + below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; + // The following guarantees that queue is not NULL + if (!dcb_write_parameter_check(dcb, queue)) return 0; + + spinlock_acquire(&dcb->writeqlock); + if (dcb->writeq) + { + dcb_write_when_already_queued(dcb, queue); + } + else + { + /* + * Loop over the buffer chain that has been passed to us + * from the reading side. + * Send as much of the data in that chain as possible and + * add any balance to the write queue. */ - if (queue == NULL || - (dcb->state != DCB_STATE_ALLOC && - dcb->state != DCB_STATE_POLLING && - dcb->state != DCB_STATE_LISTENING && - dcb->state != DCB_STATE_NOPOLLING && - (dcb->session == NULL || - dcb->session->state != SESSION_STATE_STOPPING))) + while (queue != NULL) { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Write aborted to dcb %p because " - "it is in state %s", - pthread_self(), - dcb->stats.n_buffered, - dcb, - STRDCBSTATE(dcb->state), - dcb->fd))); - //ss_dassert(false); - return 0; - } - - spinlock_acquire(&dcb->writeqlock); - - if (dcb->writeq != NULL) - { - /* - * We have some queued data, so add our data to - * the write queue and return. - * The assumption is that there will be an EPOLLOUT - * event to drain what is already queued. We are protected - * by the spinlock, which will also be acquired by the - * the routine that drains the queue data, so we should - * not have a race condition on the event. - */ - if (queue) - { - int qlen; - - qlen = gwbuf_length(queue); - atomic_add(&dcb->writeqlen, qlen); - dcb->writeq = gwbuf_append(dcb->writeq, queue); - dcb->stats.n_buffered++; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Append to writequeue. %d writes " - "buffered for dcb %p in state %s fd %d", - pthread_self(), - dcb->stats.n_buffered, - dcb, - STRDCBSTATE(dcb->state), - dcb->fd))); - } - } - else - { - /* - * Loop over the buffer chain that has been passed to us - * from the reading side. - * Send as much of the data in that chain as possible and - * add any balance to the write queue. - */ - while (queue != NULL) - { - int qlen; #if defined(FAKE_CODE) - if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER && - dcb->session != NULL) - { - if (dcb_isclient(dcb) && fail_next_client_fd) { - dcb_fake_write_errno[dcb->fd] = 32; - dcb_fake_write_ev[dcb->fd] = 29; - fail_next_client_fd = false; - } else if (!dcb_isclient(dcb) && - fail_next_backend_fd) - { - dcb_fake_write_errno[dcb->fd] = 32; - dcb_fake_write_ev[dcb->fd] = 29; - fail_next_backend_fd = false; - } - } + dcb_write_fake_code(dcb); #endif /* FAKE_CODE */ - qlen = GWBUF_LENGTH(queue); - GW_NOINTR_CALL( - w = gw_write(dcb, GWBUF_DATA(queue), qlen); - dcb->stats.n_writes++; - ); - - if (w < 0) - { - saved_errno = errno; - errno = 0; + GW_NOINTR_CALL( + written = gw_write(dcb, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); + dcb->stats.n_writes++; + ); + + if (written < 0) + { + int rv = dcb_log_write_failure(dcb, queue, errno); - if (LOG_IS_ENABLED(LOGFILE_DEBUG)) - { - if (saved_errno == EPIPE) - { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Write to dcb " - "%p in state %s fd %d failed " - "due errno %d, %s", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state), - dcb->fd, - saved_errno, - strerror(saved_errno)))); - } - } - - if (LOG_IS_ENABLED(LOGFILE_ERROR)) - { - if (saved_errno != EPIPE && - saved_errno != EAGAIN && - saved_errno != EWOULDBLOCK) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Write to dcb %p in " - "state %s fd %d failed due " - "errno %d, %s", - dcb, - STRDCBSTATE(dcb->state), - dcb->fd, - saved_errno, - strerror(saved_errno)))); - } - } - break; - } - /* - * Pull the number of bytes we have written from - * queue with have. - */ - queue = gwbuf_consume(queue, w); - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Wrote %d Bytes to dcb %p in " - "state %s fd %d", - pthread_self(), - w, - dcb, - STRDCBSTATE(dcb->state), - dcb->fd))); - } /*< while (queue != NULL) */ /*< * What wasn't successfully written is stored to write queue * for suspended write. */ + atomic_add(&dcb->writeqlen, gwbuf_length(queue)); dcb->writeq = queue; + dcb->stats.n_buffered++; + spinlock_release(&dcb->writeqlock); - if (queue) + /** Return 1 if the write failure was due to EWOULDBLOCK or EAGAIN. + The rest of the buffer will be written once an EPOLL_OUT event + arrives.*/ + return rv == 0 ? 1 : 0; + } + /* + * Pull the number of bytes we have written from + * queue with have. + */ + queue = gwbuf_consume(queue, written); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Wrote %d Bytes to dcb %p in " + "state %s fd %d", + pthread_self(), + written, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + } /*< while (queue != NULL) */ + + } /* if (dcb->writeq) */ + + dcb_write_tidy_up(dcb, below_water); + + return 1; +} + +#if defined(FAKE_CODE) +/** + * Fake code for dcb_write + * (Should have fuller description) + * + * @param dcb The DCB of the client + */ +static inline void +dcb_write_fake_code(DCB *dcb) +{ + if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER && + dcb->session != NULL) + { + if (dcb_isclient(dcb) && fail_next_client_fd) + { + dcb_fake_write_errno[dcb->fd] = 32; + dcb_fake_write_ev[dcb->fd] = 29; + fail_next_client_fd = false; + } + else if (!dcb_isclient(dcb) && + fail_next_backend_fd) + { + dcb_fake_write_errno[dcb->fd] = 32; + dcb_fake_write_ev[dcb->fd] = 29; + fail_next_backend_fd = false; + } + } +} +#endif /* FAKE_CODE */ + +/** + * Check the parameters for dcb_write + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + * @return true if parameters acceptable, false otherwise + */ +static inline bool +dcb_write_parameter_check(DCB *dcb, GWBUF *queue) +{ + if (queue == NULL) return false; + + 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"))); + gwbuf_free(queue); + return false; + } + + if (dcb->session == NULL || dcb->session->state != SESSION_STATE_STOPPING) + { + /** + * SESSION_STATE_STOPPING means that one of the backends is closing + * the router session. Some backends may have not completed + * authentication yet and thus they have no information about router + * being closed. Session state is changed to SESSION_STATE_STOPPING + * before router's closeSession is called and that tells that DCB may + * still be writable. + */ + if (dcb->state != DCB_STATE_ALLOC && + + dcb->state != DCB_STATE_POLLING && + dcb->state != DCB_STATE_LISTENING && + dcb->state != DCB_STATE_NOPOLLING) + + + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write aborted to dcb %p because " + "it is in state %s", + pthread_self(), + dcb->stats.n_buffered, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + gwbuf_free(queue); + return false; + } + } + return true; +} + +/** + * Handle writing when there is already queued data + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + */ +static inline void +dcb_write_when_already_queued(DCB *dcb, GWBUF *queue) +{ + /* + * We have some queued data, so add our data to + * the write queue and return. + * The assumption is that there will be an EPOLLOUT + * event to drain what is already queued. We are protected + * by the spinlock, which will also be acquired by the + * the routine that drains the queue data, so we should + * not have a race condition on the event. + */ + atomic_add(&dcb->writeqlen, gwbuf_length(queue)); + dcb->writeq = gwbuf_append(dcb->writeq, queue); + dcb->stats.n_buffered++; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Append to writequeue. %d writes " + "buffered for dcb %p in state %s fd %d", + pthread_self(), + dcb->stats.n_buffered, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); +} + +/** + * Debug log write failure, except when it is COM_QUIT + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + * @param eno Error number for logging + */ +static int +dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno) +{ + int rval = 0; + if (LOG_IS_ENABLED(LOGFILE_DEBUG)) + { + if (eno == EPIPE) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due errno %d, %s", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + eno, + strerror(eno)))); + rval = -1; + } + } + + if (LOG_IS_ENABLED(LOGFILE_ERROR)) + { + if (eno != EPIPE && + eno != EAGAIN && + eno != EWOULDBLOCK) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write to dcb %p in " + "state %s fd %d failed due " + "errno %d, %s", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + eno, + strerror(eno)))); + rval = -1; + + } + + } + + bool dolog = true; + + if (eno != 0 && + eno != EAGAIN && + eno != EWOULDBLOCK) + { + /** + * Do not log if writing COM_QUIT to backend failed. + */ + if (GWBUF_IS_TYPE_MYSQL(queue)) + { + uint8_t* data = GWBUF_DATA(queue); + + if (data[4] == 0x01) + { + dolog = false; + } + } + if (dolog) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Writing to %s socket failed due %d, %s.", + pthread_self(), + dcb_isclient(dcb) ? "client" : "backend server", + eno, + strerror(eno)))); + rval = -1; + } + } + return rval; +} + +/** + * Last few things to do at end of a write + * + * @param dcb The DCB of the client + * @param below_water A boolean + */ +static inline void +dcb_write_tidy_up (DCB *dcb, bool below_water) +{ + spinlock_release(&dcb->writeqlock); + + if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water) + { + atomic_add(&dcb->stats.n_high_water, 1); + dcb_call_callback(dcb, DCB_REASON_HIGH_WATER); + } +} + +/** + * General purpose routine to write to an SSL enabled DCB + * + * @param dcb The DCB of the client + * @param ssl The SSL structure for this DCB + * @param queue Queue of buffers to write + * @return 0 on failure, 1 on success + */ +int +dcb_write_SSL(DCB *dcb, GWBUF *queue) +{ + int w; + int saved_errno = 0; + bool below_water; + + below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; + // The following guarantees that queue is not NULL + if (!dcb_write_parameter_check(dcb, queue)) return 0; + + spinlock_acquire(&dcb->writeqlock); + + if (dcb->writeq) + { + dcb_write_when_already_queued(dcb, queue); + } + else + { + /* + * Loop over the buffer chain that has been passed to us + * from the reading side. + * Send as much of the data in that chain as possible and + * add any balance to the write queue. + */ + while (queue != NULL) + { +#if defined(FAKE_CODE) + dcb_write_fake_code(dcb); +#endif /* FAKE_CODE */ + do + { + w = gw_write_SSL(dcb->ssl, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); + dcb->stats.n_writes++; + + if (w <= 0) { - int qlen; - - qlen = gwbuf_length(queue); - atomic_add(&dcb->writeqlen, qlen); + int ssl_errno = dcb_write_SSL_error_report (dcb, w); + if(ssl_errno != SSL_ERROR_WANT_WRITE) + { + atomic_add(&dcb->writeqlen, gwbuf_length(queue)); dcb->stats.n_buffered++; + dcb_write_tidy_up(dcb, below_water); + return 1; + } } - } /* if (dcb->writeq) */ + } while(w <= 0); - if (saved_errno != 0 && - queue != NULL && - saved_errno != EAGAIN && - saved_errno != EWOULDBLOCK) - { - bool dolog = true; + /** Remove written bytes from the queue */ + queue = gwbuf_consume(queue, w); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Wrote %d Bytes to dcb %p in " + "state %s fd %d", + pthread_self(), + w, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + } /*< while (queue != NULL) */ + /*< + * What wasn't successfully written is stored to write queue + * for suspended write. + */ + dcb->writeq = queue; - /** - * Do not log if writing COM_QUIT to backend failed. - */ - if (GWBUF_IS_TYPE_MYSQL(queue)) + } /* if (dcb->writeq) */ + + dcb_write_tidy_up(dcb, below_water); + + return 1; +} + +/** + * General purpose routine to write error reports for SSL writes + * + * @param dcb The DCB of the client + * @param ret The SSL operation return code + * @return The final SSL error number + */ +static int +dcb_write_SSL_error_report (DCB *dcb, int ret) +{ + int ssl_errno; + char errbuf[256]; + ssl_errno = SSL_get_error(dcb->ssl,ret); + + if (LOG_IS_ENABLED(LOGFILE_DEBUG)) + { + switch(ssl_errno) + { + case SSL_ERROR_WANT_READ: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error SSL_ERROR_WANT_READ", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + break; + case SSL_ERROR_WANT_WRITE: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error SSL_ERROR_WANT_WRITE", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + break; + default: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error %d", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd,ssl_errno))); + break; + } + } + + if (LOG_IS_ENABLED(LOGFILE_ERROR) && ssl_errno != SSL_ERROR_WANT_WRITE) + { + if (ret == -1) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write to dcb %p in " + "state %s fd %d failed due to " + "SSL error %d", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ssl_errno))); + if(ssl_errno == SSL_ERROR_SSL || ssl_errno == SSL_ERROR_SYSCALL) + { + if(ssl_errno == SSL_ERROR_SYSCALL) { - uint8_t* data = GWBUF_DATA(queue); - - if (data[4] == 0x01) - { - dolog = false; - } + strerror_r(errno,errbuf,255); + errbuf[255] = '\0'; + skygw_log_write(LE,"%d:%s",errno,errbuf); } - if (dolog) + do { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Writing to %s socket failed due %d, %s.", - pthread_self(), - dcb_isclient(dcb) ? "client" : "backend server", - saved_errno, - strerror(saved_errno)))); - } - spinlock_release(&dcb->writeqlock); - return 0; - } - spinlock_release(&dcb->writeqlock); - - if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water) - { - atomic_add(&dcb->stats.n_high_water, 1); - dcb_call_callback(dcb, DCB_REASON_HIGH_WATER); - } - - return 1; + char errbuf[140]; + ERR_error_string_n(ssl_errno,errbuf,140); + skygw_log_write(LE,"%d:%s",ssl_errno,errbuf); + } while((ssl_errno = ERR_get_error()) != 0); + } + } + else if(ret == 0) + { + do + { + char errbuf[140]; + ERR_error_string_n(ssl_errno,errbuf,140); + skygw_log_write(LE,"%d:%s",ssl_errno,errbuf); + } while((ssl_errno = ERR_get_error()) != 0); + } + } + return SSL_ERROR_NONE; } /** @@ -1208,8 +1676,106 @@ int above_water; return n; } +/** + * Drain the write queue of a DCB. This is called as part of the EPOLLOUT handling + * of a socket and will try to send any buffered data from the write queue + * up until the point the write would block. This function uses SSL encryption + * and the SSL handshake should have been completed prior to calling this function. + * + * @param dcb DCB to drain the write queue of + * @return The number of bytes written + */ +int +dcb_drain_writeq_SSL(DCB *dcb) +{ + int n = 0; + int w; + int saved_errno = 0; + int above_water; + + above_water = (dcb->low_water && dcb->writeqlen > dcb->low_water) ? 1 : 0; + + spinlock_acquire(&dcb->writeqlock); + + if (dcb->writeq) + { + int len; + /* + * Loop over the buffer chain in the pending writeq + * Send as much of the data in that chain as possible and + * leave any balance on the write queue. + */ + while (dcb->writeq != NULL) + { + len = GWBUF_LENGTH(dcb->writeq); + w = gw_write_SSL(dcb->ssl, GWBUF_DATA(dcb->writeq), len); + + if (w < 0) + { + int ssl_errno = SSL_get_error(dcb->ssl,w); + + if(ssl_errno == SSL_ERROR_WANT_WRITE || ssl_errno == SSL_ERROR_WANT_READ) + { + break; + } + skygw_log_write_flush(LOGFILE_ERROR, + "Error : Write to dcb failed due to " + "SSL error %d:", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ssl_errno); + switch(ssl_errno) + { + case SSL_ERROR_SSL: + case SSL_ERROR_SYSCALL: + while((ssl_errno = ERR_get_error()) != 0) + { + char errbuf[140]; + ERR_error_string_n(ssl_errno,errbuf,140); + skygw_log_write(LE,"%s",errbuf); + } + if(errno != 0) + skygw_log_write(LE,"%d:%s",errno,strerror(errno)); + break; + case SSL_ERROR_ZERO_RETURN: + skygw_log_write(LE,"Socket is closed."); + break; + + default: + skygw_log_write(LE,"Unexpected error."); + break; + } + break; + + + } + /* + * Pull the number of bytes we have written from + * queue with have. + */ + dcb->writeq = gwbuf_consume(dcb->writeq, w); + n += w; + } + } + spinlock_release(&dcb->writeqlock); + atomic_add(&dcb->writeqlen, -n); + + /* The write queue has drained, potentially need to call a callback function */ + if (dcb->writeq == NULL) + dcb_call_callback(dcb, DCB_REASON_DRAINED); + + if (above_water && dcb->writeqlen < dcb->low_water) + { + atomic_add(&dcb->stats.n_low_water, 1); + dcb_call_callback(dcb, DCB_REASON_LOW_WATER); + } + + return n; +} + /** - * Removes dcb from poll set, and adds it to zombies list. As a consequense, + * Removes dcb from poll set, and adds it to zombies list. As a consequence, * dcb first moves to DCB_STATE_NOPOLLING, and then to DCB_STATE_ZOMBIE state. * At the end of the function state may not be DCB_STATE_ZOMBIE because once * dcb_initlock is released parallel threads may change the state. @@ -1222,75 +1788,175 @@ int above_water; void dcb_close(DCB *dcb) { - int rc = 0; + CHK_DCB(dcb); - CHK_DCB(dcb); + LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, + "%lu [dcb_close] DCB %p in state %s", + pthread_self(), + dcb, + dcb ? STRDCBSTATE(dcb->state) : "Invalid DCB"))); - LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, - "%lu [dcb_close]", - pthread_self()))); - - /** - * dcb_close may be called for freshly created dcb, in which case - * it only needs to be freed. - */ - if (dcb->state == DCB_STATE_ALLOC) + if (DCB_STATE_ZOMBIE == dcb->state) + { + return; + } + + if (DCB_STATE_UNDEFINED == dcb->state + || DCB_STATE_DISCONNECTED == dcb->state) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "%lu [dcb_close] Error : Removing DCB %p but was in state %s " + "which is not legal for a call to dcb_close. ", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + raise(SIGABRT); + } + + /** + * dcb_close may be called for freshly created dcb, in which case + * it only needs to be freed. + */ + if (dcb->state == DCB_STATE_ALLOC && dcb->fd == DCBFD_CLOSED) + { + dcb_final_free(dcb); + return; + } + + /*< + * Stop dcb's listening and modify state accordingly. + */ + if (dcb->state == DCB_STATE_POLLING || dcb->state == DCB_STATE_LISTENING) + { + if (dcb->state == DCB_STATE_LISTENING) { - dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); - dcb_final_free(dcb); - return; + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "%lu [dcb_close] Error : Removing DCB %p but was in state %s " + "which is not expected for a call to dcb_close, although it" + "should be processed correctly. ", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); } - - ss_dassert(dcb->state == DCB_STATE_POLLING || - dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE); - - /*< - * Stop dcb's listening and modify state accordingly. - */ - if (dcb->state == DCB_STATE_POLLING) + if ((dcb->state == DCB_STATE_POLLING && !dcb_maybe_add_persistent(dcb)) + || (dcb->state == DCB_STATE_LISTENING)) { - 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) - { - /** - * close protocol and router session - */ - if (dcb->func.close != NULL) - { - dcb->func.close(dcb); - } - /** Call possible callback for this DCB in case of close */ - dcb_call_callback(dcb, DCB_REASON_CLOSE); - - if (dcb->state == DCB_STATE_NOPOLLING) - { - dcb_add_to_zombieslist(dcb); - } - } - ss_dassert(dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE); - } + dcb_close_finish(dcb); + } + } + + spinlock_acquire(&zombiespin); + if (dcb->state == DCB_STATE_NOPOLLING || dcb->state == DCB_STATE_ALLOC) + { + /*< + * Add closing dcb to the top of the list. + */ + dcb->memdata.next = zombies; + zombies = dcb; + /*< + * Set state which indicates that it has been added to zombies + * list. + */ + dcb->state = DCB_STATE_ZOMBIE; + } + spinlock_release(&zombiespin); } +/** + * Add DCB to persistent pool if it qualifies, close otherwise + * + * @param dcb The DCB to go to persistent pool or be closed + * @return bool - whether the DCB was added to the pool + * + */ +static bool +dcb_maybe_add_persistent(DCB *dcb) +{ + char *user; + int poolcount = -1; + user = session_getUser(dcb->session); + if (user + && strlen(user) + && dcb->server + && dcb->server->persistpoolmax + && !dcb->dcb_errhandle_called + && !(dcb->flags & DCBF_HUNG) + && (poolcount = dcb_persistent_clean_count(dcb, false)) < dcb->server->persistpoolmax) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_maybe_add_persistent] Adding DCB to persistent pool, user %s.\n", + pthread_self(), + user))); + dcb->user = strdup(user); + dcb->persistentstart = time(NULL); + session_unlink_dcb(dcb->session, dcb); + spinlock_acquire(&dcb->server->persistlock); + dcb->nextpersistent = dcb->server->persistent; + dcb->server->persistent = dcb; + spinlock_release(&dcb->server->persistlock); + atomic_add(&dcb->server->stats.n_persistent, 1); + atomic_add(&dcb->server->stats.n_current, -1); + return true; + } + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_maybe_add_persistent] Not adding DCB %p to persistent pool, user %s, " + "max for pool %d, error handle called %s, hung flag %s, pool count %d.\n", + pthread_self(), + dcb, + user ? user : "", + (dcb->server && dcb->server->persistpoolmax) ? dcb->server->persistpoolmax : 0, + dcb->dcb_errhandle_called ? "true" : "false", + (dcb->flags & DCBF_HUNG) ? "true" : "false", + poolcount))); + } + return false; +} + +/** + * Final calls for DCB close + * + * @param dcb The DCB to print + * + */ +static void +dcb_close_finish(DCB *dcb) +{ + poll_remove_dcb(dcb); + /* + * Return will always be 0 or function will have crashed, so we + * threw away return value. + */ + 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)))); + /** + * Do a consistency check, then adjust counter if not from persistent pool + */ + if (dcb->server) + { + if (dcb->server->persistent) CHK_DCB(dcb->server->persistent); + if (0 == dcb->persistentstart) atomic_add(&dcb->server->stats.n_current, -1); + } + /** + * close protocol and router session + */ + if (dcb->func.close != NULL) + { + dcb->func.close(dcb); + } + /** Call possible callback for this DCB in case of close */ + dcb_call_callback(dcb, DCB_REASON_CLOSE); + } + /** * Diagnostic to print a DCB * @@ -1305,9 +1971,23 @@ printDCB(DCB *dcb) if (dcb->remote) printf("\tConnected to: %s\n", dcb->remote); if (dcb->user) - printf("\tUsername to: %s\n", dcb->user); + printf("\tUsername: %s\n", dcb->user); + if (dcb->protoname) + printf("\tProtocol: %s\n", dcb->protoname); if (dcb->writeq) printf("\tQueued write data: %d\n",gwbuf_length(dcb->writeq)); + char *statusname = server_status(dcb->server); + if (statusname) + { + printf("\tServer status: %s\n", statusname); + free(statusname); + } + char *rolename = dcb_role_name(dcb); + if (rolename) + { + printf("\tRole: %s\n", rolename); + free(rolename); + } printf("\tStatistics:\n"); printf("\t\tNo. of Reads: %d\n", dcb->stats.n_reads); @@ -1354,12 +2034,70 @@ DCB *dcb; spinlock_release(&dcbspin); } - +/** + * Diagnostic to print one DCB in the system + * + * @param pdcb DCB to print results to + * @param dcb DCB to be printed + */ +void +dprintOneDCB(DCB *pdcb, DCB *dcb) +{ + dcb_printf(pdcb, "DCB: %p\n", (void *)dcb); + dcb_printf(pdcb, "\tDCB state: %s\n", + gw_dcb_state2string(dcb->state)); + if (dcb->session && dcb->session->service) + dcb_printf(pdcb, "\tService: %s\n", + dcb->session->service->name); + if (dcb->remote) + dcb_printf(pdcb, "\tConnected to: %s\n", + dcb->remote); + if (dcb->user) + dcb_printf(pdcb, "\tUsername: %s\n", + dcb->user); + if (dcb->protoname) + dcb_printf(pdcb, "\tProtocol: %s\n", + dcb->protoname); + if (dcb->writeq) + dcb_printf(pdcb, "\tQueued write data: %d\n", + gwbuf_length(dcb->writeq)); + char *statusname = server_status(dcb->server); + if (statusname) + { + dcb_printf(pdcb, "\tServer status: %s\n", statusname); + free(statusname); + } + char *rolename = dcb_role_name(dcb); + if (rolename) + { + dcb_printf(pdcb, "\tRole: %s\n", rolename); + free(rolename); + } + dcb_printf(pdcb, "\tStatistics:\n"); + dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); + dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); + dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); + dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); + dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); + dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); + if (dcb->flags & DCBF_CLONE) + dcb_printf(pdcb, "\t\tDCB is a clone.\n"); + if (dcb->persistentstart) + { + char buff[20]; + struct tm * timeinfo; + timeinfo = localtime (&dcb->persistentstart); + strftime(buff, sizeof(buff), "%b %d %H:%M:%S", timeinfo); + dcb_printf(pdcb, "\t\tAdded to persistent pool: %s\n", buff); + } +} /** * Diagnostic to print all DCB allocated in the system * + * @param pdcb DCB to print results to */ -void dprintAllDCBs(DCB *pdcb) +void +dprintAllDCBs(DCB *pdcb) { DCB *dcb; @@ -1373,37 +2111,14 @@ DCB *dcb; dcb = allDCBs; while (dcb) { - dcb_printf(pdcb, "DCB: %p\n", (void *)dcb); - dcb_printf(pdcb, "\tDCB state: %s\n", - gw_dcb_state2string(dcb->state)); - if (dcb->session && dcb->session->service) - dcb_printf(pdcb, "\tService: %s\n", - dcb->session->service->name); - if (dcb->remote) - dcb_printf(pdcb, "\tConnected to: %s\n", - dcb->remote); - if (dcb->user) - dcb_printf(pdcb, "\tUsername: %s\n", - dcb->user); - if (dcb->writeq) - dcb_printf(pdcb, "\tQueued write data: %d\n", - gwbuf_length(dcb->writeq)); - dcb_printf(pdcb, "\tStatistics:\n"); - dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); - dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); - dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); - dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); - dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); - dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); - if (dcb->flags & DCBF_CLONE) - dcb_printf(pdcb, "\t\tDCB is a clone.\n"); - dcb = dcb->next; + dprintOneDCB(pdcb, dcb); + dcb = dcb->next; } spinlock_release(&dcbspin); } /** - * Diagnotic routine to print DCB data in a tabular form. + * Diagnostic routine to print DCB data in a tabular form. * * @param pdcb DCB to print results to */ @@ -1433,7 +2148,7 @@ DCB *dcb; } /** - * Diagnotic routine to print client DCB data in a tabular form. + * Diagnostic routine to print client DCB data in a tabular form. * * @param pdcb DCB to print results to */ @@ -1486,11 +2201,26 @@ dprintDCB(DCB *pdcb, DCB *dcb) if (dcb->user) dcb_printf(pdcb, "\tUsername: %s\n", dcb->user); + if (dcb->protoname) + dcb_printf(pdcb, "\tProtocol: %s\n", + dcb->protoname); dcb_printf(pdcb, "\tOwning Session: %p\n", dcb->session); if (dcb->writeq) dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); if (dcb->delayq) dcb_printf(pdcb, "\tDelayed write data: %d\n", gwbuf_length(dcb->delayq)); + char *statusname = server_status(dcb->server); + if (statusname) + { + dcb_printf(pdcb, "\tServer status: %s\n", statusname); + free(statusname); + } + char *rolename = dcb_role_name(dcb); + if (rolename) + { + dcb_printf(pdcb, "\tRole: %s\n", rolename); + free(rolename); + } dcb_printf(pdcb, "\tStatistics:\n"); dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); @@ -1526,6 +2256,14 @@ dprintDCB(DCB *pdcb, DCB *dcb) dcb_printf(pdcb, "\tCallback Lock Statistics:\n"); spinlock_stats(&dcb->cb_lock, spin_reporter, pdcb); #endif + if (dcb->persistentstart) + { + char buff[20]; + struct tm * timeinfo; + timeinfo = localtime (&dcb->persistentstart); + strftime(buff, sizeof(buff), "%b %d %H:%M:%S", timeinfo); + dcb_printf(pdcb, "\t\tAdded to persistent pool: %s\n", buff); + } } /** @@ -1536,12 +2274,15 @@ dprintDCB(DCB *pdcb, DCB *dcb) * */ const char * -gw_dcb_state2string (int state) { +gw_dcb_state2string (int state) +{ switch(state) { case DCB_STATE_ALLOC: return "DCB Allocated"; case DCB_STATE_POLLING: return "DCB in the polling loop"; + case DCB_STATE_NOPOLLING: + return "DCB not in the polling loop"; case DCB_STATE_LISTENING: return "DCB for listening socket"; case DCB_STATE_DISCONNECTED: @@ -1550,14 +2291,16 @@ gw_dcb_state2string (int state) { return "DCB memory could be freed"; case DCB_STATE_ZOMBIE: return "DCB Zombie"; + case DCB_STATE_UNDEFINED: + return "DCB undefined state"; default: - return "DCB (unknown)"; + return "DCB (unknown - erroneous)"; } } /** * A DCB based wrapper for printf. Allows formatting printing to - * a descritor control block. + * a descriptor control block. * * @param dcb Descriptor to write to * @param fmt A printf format string @@ -1606,8 +2349,8 @@ dcb_isclient(DCB *dcb) * @param table The hash table */ void dcb_hashtable_stats( - DCB* dcb, - void* table) + DCB *dcb, + void *table) { int total; int longest; @@ -1630,167 +2373,29 @@ void dcb_hashtable_stats( dcb_printf(dcb, "\tLongest chain length: %d\n", longest); } - -bool dcb_set_state( - DCB* dcb, - const dcb_state_t new_state, - dcb_state_t* old_state) +/** + * Write data to a socket through an SSL structure. The SSL structure is linked to a DCB's socket + * and all communication is encrypted and done via the SSL structure. + * + * @param ssl The SSL structure to use for writing + * @param buf Buffer to write + * @param nbytes Number of bytes to write + * @return Number of written bytes + */ +int +gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes) { - bool succp; - dcb_state_t state ; - - CHK_DCB(dcb); - spinlock_acquire(&dcb->dcb_initlock); - succp = dcb_set_state_nomutex(dcb, new_state, &state); - - spinlock_release(&dcb->dcb_initlock); + int w = 0; + int fd = SSL_get_fd(ssl); - if (old_state != NULL) { - *old_state = state; - } - return succp; + if (fd > 0) + { + w = SSL_write(ssl, buf, nbytes); + } + return w; } -static bool dcb_set_state_nomutex( - DCB* dcb, - const dcb_state_t new_state, - dcb_state_t* old_state) -{ - bool succp = false; - dcb_state_t state = DCB_STATE_UNDEFINED; - - CHK_DCB(dcb); - state = dcb->state; - - if (old_state != NULL) { - *old_state = state; - } - - switch (state) { - case DCB_STATE_UNDEFINED: - dcb->state = new_state; - succp = true; - break; - - case DCB_STATE_ALLOC: - switch (new_state) { - /** fall through, for client requests */ - case DCB_STATE_POLLING: - /** fall through, for connect listeners */ - case DCB_STATE_LISTENING: - /** for failed connections */ - case DCB_STATE_DISCONNECTED: - dcb->state = new_state; - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_POLLING: - switch(new_state) { - case DCB_STATE_NOPOLLING: - dcb->state = new_state; - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_LISTENING: - switch(new_state) { - case DCB_STATE_NOPOLLING: - dcb->state = new_state; - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_NOPOLLING: - switch (new_state) { - case DCB_STATE_ZOMBIE: /*< fall through */ - dcb->state = new_state; - case DCB_STATE_POLLING: /*< ok to try but state can't change */ - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_ZOMBIE: - switch (new_state) { - case DCB_STATE_DISCONNECTED: /*< fall through */ - dcb->state = new_state; - case DCB_STATE_POLLING: /*< ok to try but state can't change */ - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_DISCONNECTED: - switch (new_state) { - case DCB_STATE_FREED: - dcb->state = new_state; - succp = true; - break; - default: - ss_dassert(old_state != NULL); - break; - } - break; - - case DCB_STATE_FREED: - ss_dassert(old_state != NULL); - break; - - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unknown dcb state %s for " - "dcb %p", - STRDCBSTATE(dcb->state), - dcb))); - ss_dassert(false); - break; - } /*< switch (dcb->state) */ - - if (succp) { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [dcb_set_state_nomutex] dcb %p fd %d %s -> %s", - pthread_self(), - dcb, - dcb->fd, - STRDCBSTATE(state), - STRDCBSTATE(dcb->state)))); - } - else - { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_set_state_nomutex] Failed " - "to change state of DCB %p. " - "Old state %s > new state %s.", - pthread_self(), - dcb, - (old_state == NULL ? "NULL" : STRDCBSTATE(*old_state)), - STRDCBSTATE(new_state)))); - } - return succp; -} /** * Write data to a DCB @@ -2042,6 +2647,26 @@ int rval = 0; return rval; } +/** + * Find a DCB in the list of all DCB's + * + * @param dcb The DCB to find + * @return A pointer to the DCB or NULL if not in the list + */ +static inline DCB * +dcb_find_in_list (DCB *dcb) +{ + DCB *ptr = NULL; + if (dcb) + { + ptr = allDCBs; + while (ptr && ptr != dcb) + { + ptr = ptr->next; + } + } + return ptr; +} /** * Check the passed DCB to ensure it is in the list of allDCBS. @@ -2050,22 +2675,10 @@ int rval = 0; * @param dcb The DCB to check * @return 1 if the DCB is in the list, otherwise 0 */ -static int +static inline int dcb_isvalid_nolock(DCB *dcb) { -DCB *ptr; -int rval = 0; - - if (dcb) - { - ptr = allDCBs; - while (ptr && ptr != dcb) - { - ptr = ptr->next; - } - rval = (ptr == dcb); - } - return rval; + return (dcb == dcb_find_in_list(dcb)); } @@ -2076,7 +2689,7 @@ int rval = 0; * @return The pointer to the next DCB or NULL if this is the last */ static DCB * -dcb_get_next (DCB* dcb) +dcb_get_next (DCB *dcb) { spinlock_acquire(&dcbspin); if (dcb) { @@ -2109,7 +2722,7 @@ dcb_call_foreach(struct server* server, DCB_REASON reason) case DCB_REASON_HUP: case DCB_REASON_NOT_RESPONDING: { - DCB* dcb; + DCB *dcb; dcb = dcb_get_next(NULL); while (dcb != NULL) @@ -2177,6 +2790,70 @@ dcb_null_auth(DCB *dcb, SERVER *server, SESSION *session, GWBUF *buf) return 0; } +/** + * Check persistent pool for expiry or excess size and count + * + * @param dcb The DCB being closed. + * @param cleanall Boolean, if true the whole pool is cleared for the + * server related to the given DCB + * @return A count of the DCBs remaining in the pool + */ +int +dcb_persistent_clean_count(DCB *dcb, bool cleanall) +{ + int count = 0; + if (dcb && dcb->server) + { + SERVER *server = dcb->server; + DCB *previousdcb = NULL; + DCB *persistentdcb, *nextdcb; + DCB *disposals = NULL; + + CHK_SERVER(server); + spinlock_acquire(&server->persistlock); + persistentdcb = server->persistent; + while (persistentdcb) { + CHK_DCB(persistentdcb); + nextdcb = persistentdcb->nextpersistent; + if (cleanall + || persistentdcb-> dcb_errhandle_called + || count >= server->persistpoolmax + || (time(NULL) - persistentdcb->persistentstart) > server->persistmaxtime) + { + /* Remove from persistent pool */ + if (previousdcb) { + previousdcb->nextpersistent = nextdcb; + } + else + { + server->persistent = nextdcb; + } + /* Add removed DCBs to disposal list for processing outside spinlock */ + persistentdcb->nextpersistent = disposals; + disposals = persistentdcb; + atomic_add(&server->stats.n_persistent, -1); + } + else + { + count++; + previousdcb = persistentdcb; + } + persistentdcb = nextdcb; + } + server->persistmax = MAX(server->persistmax, count); + spinlock_release(&server->persistlock); + /** Call possible callback for this DCB in case of close */ + while (disposals) + { + nextdcb = disposals->nextpersistent; + dcb_close_finish(disposals); + dcb_close(disposals); + disposals = nextdcb; + } + } + return count; +} + /** * Return DCB counts optionally filtered by usage * @@ -2225,3 +2902,229 @@ DCB *ptr; spinlock_release(&dcbspin); return rval; } + +/** + * Create the SSL structure for this DCB. + * This function creates the SSL structure for the given SSL context. This context + * should be the service's context + * @param dcb + * @param context + * @return + */ +int dcb_create_SSL(DCB* dcb) +{ + + if(serviceInitSSL(dcb->service) != 0) + { + return -1; + } + + if((dcb->ssl = SSL_new(dcb->service->ctx)) == NULL) + { + skygw_log_write(LE,"Error: Failed to initialize SSL for connection."); + return -1; + } + + if(SSL_set_fd(dcb->ssl,dcb->fd) == 0) + { + skygw_log_write(LE,"Error: Failed to set file descriptor for SSL connection."); + return -1; + } + + return 0; +} + +/** + * Accept a SSL connection and do the SSL authentication handshake. + * This function accepts a client connection to a DCB. It assumes that the SSL + * structure has the underlying method of communication set and this method is ready + * for usage. It then proceeds with the SSL handshake and stops only if an error + * occurs or the client has not yet written enough data to complete the handshake. + * @param dcb DCB which should accept the SSL connection + * @return 1 if the handshake was successfully completed, 0 if the handshake is + * still ongoing and another call to dcb_SSL_accept should be made or -1 if an + * error occurred during the handshake and the connection should be terminated. + */ +int dcb_accept_SSL(DCB* dcb) +{ + int rval = 0,ssl_rval,errnum = 0,fd,b = 0,pending; + char errbuf[140]; + fd = dcb->fd; + + do + { + ssl_rval = SSL_accept(dcb->ssl); + + LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept %d, error %d", + ssl_rval,errnum))); + switch(ssl_rval) + { + case 0: + errnum = SSL_get_error(dcb->ssl,ssl_rval); + skygw_log_write(LE,"Error: SSL authentication failed (SSL error %d):", + dcb, + dcb->remote, + errnum); + + if(errnum == SSL_ERROR_SSL || + errnum == SSL_ERROR_SYSCALL) + { + while((errnum = ERR_get_error()) != 0) + { + ERR_error_string_n(errnum,errbuf,140); + skygw_log_write(LE,"%s",errbuf); + } + } + rval = -1; + break; + case 1: + rval = 1; + LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept done for %s", + dcb->remote))); + return rval; + + case -1: + + errnum = SSL_get_error(dcb->ssl,ssl_rval); + + if(errnum == SSL_ERROR_WANT_READ || errnum == SSL_ERROR_WANT_WRITE) + { + /** Not all of the data has been read. Go back to the poll + queue and wait for more.*/ + rval = 0; + LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept ongoing for %s", + dcb->remote))); + return rval; + } + else + { + rval = -1; + skygw_log_write(LE, + "Error: Fatal error in SSL_accept for %s: (SSL version: %s SSL error code: %d)", + dcb->remote, + SSL_get_version(dcb->ssl), + errnum); + if(errnum == SSL_ERROR_SSL || + errnum == SSL_ERROR_SYSCALL) + { + while((errnum = ERR_get_error()) != 0) + { + ERR_error_string_n(errnum,errbuf,140); + skygw_log_write(LE, + "%s", + errbuf); + } + } + } + break; + + default: + skygw_log_write_flush(LE, + "Error: Fatal library error in SSL_accept, returned value was %d.", + ssl_rval); + rval = -1; + break; + } + ioctl(fd,FIONREAD,&b); + pending = SSL_pending(dcb->ssl); +#ifdef SS_DEBUG + skygw_log_write_flush(LD,"[dcb_accept_SSL] fd %d: %d bytes, %d pending",fd,b,pending); +#endif + }while((b > 0 || pending > 0) && rval != -1); + + return rval; +} + +/** + * Initiate an SSL client connection to a server + * + * This functions starts an SSL client connection to a server which is expecting + * an SSL handshake. The DCB should already have a TCP connection to the server and + * this connection should be in a state that expects an SSL handshake. + * @param dcb DCB to connect + * @return 1 on success, -1 on error and 0 if the SSL handshake is still ongoing + */ +int dcb_connect_SSL(DCB* dcb) +{ + int rval,errnum; + char errbuf[140]; + rval = SSL_connect(dcb->ssl); + + switch(rval) + { + case 0: + errnum = SSL_get_error(dcb->ssl,rval); + LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect shutdown for %s@%s", + dcb->user, + dcb->remote))); + return -1; + break; + case 1: + rval = 1; + LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect done for %s@%s", + dcb->user, + dcb->remote))); + return rval; + + case -1: + errnum = SSL_get_error(dcb->ssl,rval); + + if(errnum == SSL_ERROR_WANT_READ || errnum == SSL_ERROR_WANT_WRITE) + { + /** Not all of the data has been read. Go back to the poll + queue and wait for more.*/ + + rval = 0; + LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect ongoing for %s@%s", + dcb->user, + dcb->remote))); + } + else + { + rval = -1; + ERR_error_string_n(errnum,errbuf,140); + skygw_log_write_flush(LE, + "Error: Fatal error in SSL_accept for %s@%s: (SSL error code: %d) %s", + dcb->user, + dcb->remote, + errnum, + errbuf); + } + break; + + default: + skygw_log_write_flush(LE, + "Error: Fatal error in SSL_connect, returned value was %d.", + rval); + break; + } + + return rval; +} + +/** + * Convert a DCB role to a string, the returned + * string has been malloc'd and must be free'd by the caller + * + * @param DCB The DCB to return the role of + * @return A string representation of the DCB role + */ +char * +dcb_role_name(DCB *dcb) +{ +char *name = NULL; + + if (NULL != (name = (char *)malloc(64))) + { + name[0] = 0; + if (DCB_ROLE_SERVICE_LISTENER == dcb->dcb_role) + strcat(name, "Service Listener"); + else if (DCB_ROLE_REQUEST_HANDLER == dcb->dcb_role) + strcat(name, "Request Handler"); + else if (DCB_ROLE_INTERNAL == dcb->dcb_role) + strcat(name, "Internal"); + else + strcat(name, "Unknown"); + } + return name; +} diff --git a/server/core/externcmd.c b/server/core/externcmd.c new file mode 100644 index 000000000..93579faef --- /dev/null +++ b/server/core/externcmd.c @@ -0,0 +1,169 @@ +#include + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +/** + * Tokenize a string into arguments suitable for a execvp call. + * @param args Argument string + * @param argv Array of char pointers to be filled with tokenized arguments + * @return 0 on success, -1 on error + */ +int tokenize_arguments(char* args, char** argv) +{ + int i = 0; + bool quoted = false; + bool read = false; + bool escaped = false; + char *ptr,*start; + char qc; + + start = args; + ptr = start; + + while(*ptr != '\0' && i < MAXSCALE_EXTCMD_ARG_MAX) + { + if(escaped) + { + escaped = false; + } + else + { + if(*ptr == '\\') + { + escaped = true; + } + else if(quoted && !escaped && *ptr == qc) /** End of quoted string */ + { + *ptr = '\0'; + argv[i++] = strdup(start); + read = false; + quoted = false; + } + else if (!quoted) + { + if(isspace(*ptr)) + { + *ptr = '\0'; + if(read) /** New token */ + { + argv[i++] = strdup(start); + read = false; + } + } + else if( *ptr == '\"' || *ptr == '\'') + { + /** New quoted token, strip quotes */ + quoted = true; + qc = *ptr; + start = ptr + 1; + } + else if(!read) + { + start = ptr; + read = true; + } + } + } + ptr++; + } + if(read) + argv[i++] = strdup(start); + argv[i] = NULL; + + return 0; +} + +/** + * Allocate a new external command. + * The name and parameters are copied into the external command structure so + * the original memory can be freed if needed. + * @param command Command to execute with the parameters + * @return Pointer to new external command struct or NULL if an error occurred + */ +EXTERNCMD* externcmd_allocate(char* argstr) +{ + EXTERNCMD* cmd; + + if(argstr == NULL) + return NULL; + + if((cmd = (EXTERNCMD*)malloc(sizeof(EXTERNCMD))) != NULL) + { + if(tokenize_arguments(argstr,cmd->parameters) == -1) + { + free(cmd); + return NULL; + } + if(access(cmd->parameters[0],F_OK) != 0) + { + skygw_log_write(LE, + "Error: Cannot find file: %s", + cmd->parameters[0]); + externcmd_free(cmd); + return NULL; + } + + if(access(cmd->parameters[0],X_OK) != 0) + { + skygw_log_write(LE, + "Error: Cannot execute file: %s", + cmd->parameters[0]); + externcmd_free(cmd); + return NULL; + } + } + return cmd; +} + +/** + * Free a previously allocated external command. + * @param cmd Command to free + */ +void externcmd_free(EXTERNCMD* cmd) +{ + int i; + + for(i = 0;cmd->parameters[i] != NULL;i++) + { + free(cmd->parameters[i]); + } + free(cmd); +} + +/** + *Execute a command in a separate process. + *@param cmd Command to execute + *@return 0 on success, -1 on error. + */ +int externcmd_execute(EXTERNCMD* cmd) +{ + int rval = 0; + pid_t pid; + + pid = fork(); + + if(pid < 0) + { + skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s', fork failed: [%d] %s", + cmd->parameters[0],errno,strerror(errno)); + rval = -1; + } + else if(pid == 0) + { + /** Child process, execute command */ + execvp(cmd->parameters[0],cmd->parameters); + _exit(1); + } + else + { + cmd->child = pid; + cmd->n_exec++; + LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd)); + } + + return rval; +} + diff --git a/server/core/gateway.c b/server/core/gateway.c index 6c505b124..0ab08c818 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -40,7 +40,16 @@ * @endverbatim */ #define _XOPEN_SOURCE 700 +#define OPENSSL_THREAD_DEFINES #include + + #include + #if defined(OPENSSL_THREADS) +#define HAVE_OPENSSL_THREADS 1 + #else +#define HAVE_OPENSSL_THREADS 0 + #endif + #include #include #include @@ -74,6 +83,9 @@ #include +#include +#include + /** for procname */ #if !defined(_GNU_SOURCE) # define _GNU_SOURCE @@ -111,8 +123,6 @@ static char* server_options[] = { const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1; -const char* default_cnf_fname = "etc/MaxScale.cnf"; - static char* server_groups[] = { "embedded", "server", @@ -125,10 +135,11 @@ static char* server_groups[] = { /* The data directory we created for this gateway instance */ static char datadir[PATH_MAX+1] = ""; - +static bool datadir_defined = false; /*< If the datadir was already set */ /* The data directory we created for this gateway instance */ static char pidfile[PATH_MAX+1] = ""; + /** * exit flag for log flusher. */ @@ -150,13 +161,21 @@ static struct option long_options[] = { {"config", required_argument, 0, 'f'}, {"nodaemon", no_argument, 0, 'd'}, {"log", required_argument, 0, 'l'}, + {"logdir", required_argument, 0, 'L'}, + {"datadir", required_argument, 0, 'D'}, + {"configdir",required_argument, 0, 'C'}, + {"piddir",required_argument, 0, 'P'}, + {"libdir",required_argument, 0, 'B'}, + {"cachedir",required_argument, 0, 'A'}, + {"language",required_argument, 0, 'N'}, {"syslog", required_argument, 0, 's'}, - {"maxscalelog", required_argument, 0, 'S'}, + {"maxscalelog",required_argument,0,'S'}, + {"user",required_argument,0,'U'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, '?'}, {0, 0, 0, 0} }; - +static int cnf_preparser(void* data, const char* section, const char* name, const char* value); static void log_flush_shutdown(void); static void log_flush_cb(void* arg); static int write_pid_file(char *); /* write MaxScale pidfile */ @@ -168,6 +187,7 @@ static void write_footer(void); static int ntfw_cb(const char*, const struct stat*, int, struct FTW*); static bool file_is_readable(char* absolute_pathname); static bool file_is_writable(char* absolute_pathname); +bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr); static void usage(void); static char* get_expanded_pathname( char** abs_path, @@ -183,10 +203,86 @@ static bool resolve_maxscale_conf_fname( char** cnf_full_path, char* home_dir, char* cnf_file_arg); -static bool resolve_maxscale_homedir( - char** p_home_dir); -static char* check_dir_access(char* dirname); +static char* check_dir_access(char* dirname,bool,bool); +static int set_user(); + +/** SSL multi-threading functions and structures */ + +static SPINLOCK* ssl_locks; + +static void ssl_locking_function(int mode,int n,const char* file, int line) +{ + if(mode & CRYPTO_LOCK) + spinlock_acquire(&ssl_locks[n]); + else + spinlock_release(&ssl_locks[n]); +} +/** + * OpenSSL requires this struct to be defined in order to use dynamic locks + */ +struct CRYPTO_dynlock_value +{ + SPINLOCK lock; +}; + +/** + * Create a dynamic OpenSSL lock. The dynamic lock is just a wrapper structure + * around a SPINLOCK structure. + * @param file File name + * @param line Line number + * @return Pointer to new lock or NULL of an error occurred + */ +static struct CRYPTO_dynlock_value *ssl_create_dynlock(const char* file, int line) +{ + struct CRYPTO_dynlock_value* lock = malloc(sizeof(struct CRYPTO_dynlock_value)); + if(lock) + { + spinlock_init(&lock->lock); + } + return lock; +} + +/** + * Lock a dynamic lock for OpenSSL. + * @param mode + * @param n pointer to lock + * @param file File name + * @param line Line number + */ +static void ssl_lock_dynlock(int mode,struct CRYPTO_dynlock_value * n,const char* file, int line) +{ + if(mode & CRYPTO_LOCK) + { + spinlock_acquire(&n->lock); + } + else + { + spinlock_release(&n->lock); + } +} + +/** + * Free a dynamic OpenSSL lock. + * @param n Lock to free + * @param file File name + * @param line Line number + */ +static void ssl_free_dynlock(struct CRYPTO_dynlock_value * n,const char* file, int line) +{ + free(n); +} + +#ifdef OPENSSL_1_0 +/** + * The thread ID callback function for OpenSSL dynamic locks. + * @param id Id to modify + */ +static void maxscale_ssl_id(CRYPTO_THREADID* id) +{ + CRYPTO_THREADID_set_numeric(id,pthread_self()); +} +#endif /** * Handler for SIGHUP signal. Reload the configuration for the @@ -238,6 +334,41 @@ sigint_handler (int i) fprintf(stderr, "\n\nShutting down MaxScale\n\n"); } +static void +sigchld_handler (int i) +{ + int exit_status = 0; + pid_t child = -1; + + if((child = wait(&exit_status)) == -1) + { + char errbuf[512]; + strerror_r(errno,errbuf,511); + errbuf[511] = '\0'; + skygw_log_write_flush(LE,"Error: failed to wait child process: %d %s",errno,errbuf); + } + else + { + if(WIFEXITED(exit_status)) + { + skygw_log_write_flush(WEXITSTATUS(exit_status) != 0 ? LE : LT, + "Child process %d exited with status %d", + child,WEXITSTATUS(exit_status)); + } + else if(WIFSIGNALED(exit_status)) + { + skygw_log_write_flush((LE|LT), + "Child process %d was stopped by signal %d.", + child,WTERMSIG(exit_status)); + } + else + { + skygw_log_write_flush((LE|LT), + "Child process %d did not exit normally. Exit status: %d", + child,exit_status); + } + } +} int fatal_handling = 0; @@ -356,11 +487,10 @@ void datadir_cleanup() { int depth = 1; int flags = FTW_CHDIR|FTW_DEPTH|FTW_MOUNT; - int rc; if (datadir[0] != 0 && access(datadir, F_OK) == 0) { - rc = nftw(datadir, ntfw_cb, depth, flags); + nftw(datadir, ntfw_cb, depth, flags); } } @@ -381,14 +511,13 @@ static bool file_write_footer( FILE* outfile) { bool succp = false; - size_t wbytes1; size_t len1; const char* header_buf1; header_buf1 = "------------------------------------------------------" "\n\n"; len1 = strlen(header_buf1); - wbytes1=fwrite((void*)header_buf1, len1, 1, outfile); + fwrite((void*)header_buf1, len1, 1, outfile); succp = true; @@ -399,9 +528,6 @@ static bool file_write_header( FILE* outfile) { bool succp = false; - size_t wbytes1; - size_t wbytes2; - size_t wbytes3; size_t len1; size_t len2; size_t len3; @@ -445,9 +571,9 @@ static bool file_write_header( #if defined(LAPTOP_TEST) nanosleep(&ts1, NULL); #else - wbytes1=fwrite((void*)header_buf1, len1, 1, outfile); - wbytes2=fwrite((void*)header_buf2, len2, 1, outfile); - wbytes3=fwrite((void*)header_buf3, len3, 1, outfile); + fwrite((void*)header_buf1, len1, 1, outfile); + fwrite((void*)header_buf2, len2, 1, outfile); + fwrite((void*)header_buf3, len3, 1, outfile); #endif succp = true; @@ -490,15 +616,9 @@ static bool resolve_maxscale_conf_fname( * directory. * '-f MaxScale.cnf' */ - home_etc_dir = (char*)malloc(strlen(home_dir)+strlen("/etc")+1); - snprintf(home_etc_dir, - strlen(home_dir)+strlen("/etc")+1, - "%s/etc", - home_dir); *cnf_full_path = get_expanded_pathname(NULL, - home_etc_dir, + home_dir, cnf_file_arg); - free(home_etc_dir); if (*cnf_full_path != NULL) { @@ -577,149 +697,6 @@ return_succp: return succp; } - -static bool resolve_maxscale_homedir( - char** p_home_dir) -{ - bool succp = false; - char* tmp; - char* tmp2; - char* log_context = NULL; - - ss_dassert(*p_home_dir == NULL); - - if (*p_home_dir != NULL) - { - log_context = strdup("Command-line argument"); - tmp = NULL; - goto check_home_dir; - } - /*< - * 1. if home dir wasn't specified by a command-line argument, - * read env. variable MAXSCALE_HOME. - */ - if (getenv("MAXSCALE_HOME") != NULL) - { - tmp = strndup(getenv("MAXSCALE_HOME"), PATH_MAX); - get_expanded_pathname(p_home_dir, tmp, NULL); - - if (*p_home_dir != NULL) - { - log_context = strdup("MAXSCALE_HOME"); - goto check_home_dir; - } - free(tmp); - } - else - { - fprintf(stderr, "\n*\n* Warning : MAXSCALE_HOME environment variable " - "is not set.\n*\n"); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Warning : MAXSCALE_HOME environment " - "variable is not set."))); - } - /*< - * 2. if home dir wasn't specified in MAXSCALE_HOME, - * try access /etc/MaxScale/ - */ - tmp = strdup("/etc/MaxScale"); - get_expanded_pathname(p_home_dir, tmp, NULL); - - if (*p_home_dir != NULL) - { - log_context = strdup("/etc/MaxScale"); - goto check_home_dir; - } - free(tmp); - /*< - * 3. if /etc/MaxScale/MaxScale.cnf didn't exist or wasn't accessible, home - * isn't specified. Thus, try to access $PWD/MaxScale.cnf . - */ - char *pwd = getenv("PWD"); - tmp = strndup(pwd ? pwd : "PWD_NOT_SET", PATH_MAX); - tmp2 = get_expanded_pathname(p_home_dir, tmp, default_cnf_fname); - free(tmp2); /*< full path isn't needed so simply free it */ - - if (*p_home_dir != NULL) - { - log_context = strdup("Current working directory"); - } - -check_home_dir: - if (*p_home_dir != NULL) - { - if (!file_is_readable(*p_home_dir)) - { - char* tailstr = "MaxScale doesn't have read permission " - "to MAXSCALE_HOME."; - char* logstr = (char*)malloc(strlen(log_context)+ - 1+ - strlen(tailstr)+ - 1); - snprintf(logstr, - strlen(log_context)+ - 1+ - strlen(tailstr)+1, - "%s:%s", - log_context, - tailstr); - print_log_n_stderr(true, true, logstr, logstr, 0); - free(logstr); - goto return_succp; - } - -#if WRITABLE_HOME - if (!file_is_writable(*p_home_dir)) - { - char* tailstr = "MaxScale doesn't have write permission " - "to MAXSCALE_HOME. Exiting."; - char* logstr = (char*)malloc(strlen(log_context)+ - 1+ - strlen(tailstr)+ - 1); - snprintf(logstr, - strlen(log_context)+ - 1+ - strlen(tailstr)+1, - "%s:%s", - log_context, - tailstr); - print_log_n_stderr(true, true, logstr, logstr, 0); - free(logstr); - goto return_succp; - } -#endif - if (!daemon_mode) - { - fprintf(stderr, - "Using %s as MAXSCALE_HOME = %s\n", - log_context, - tmp); - } - succp = true; - goto return_succp; - } - -return_succp: - free (tmp); - - - if (log_context != NULL) - { - free(log_context); - } - - if (!succp) - { - char* logstr = "MaxScale was unable to locate home directory " - "with read and write permissions. \n*\n* Exiting."; - print_log_n_stderr(true, true, logstr, logstr, 0); - usage(); - } - return succp; -} - /** * Check read and write accessibility to a directory. * @param dirname directory to be checked @@ -728,8 +705,9 @@ return_succp: * read or write is not permitted. */ static char* check_dir_access( - char* dirname) + char* dirname, bool rd, bool wr) { + char errbuf[PATH_MAX*2]; char* errstr = NULL; if (dirname == NULL) @@ -737,18 +715,27 @@ static char* check_dir_access( errstr = strdup("Directory argument is NULL"); goto retblock; } - - if (!file_is_readable(dirname)) + + if(access(dirname,F_OK) != 0) { - errstr = strdup("MaxScale doesn't have read permission " - "to MAXSCALE_HOME."); + sprintf(errbuf,"Can't access '%s'.",dirname); + errstr = strdup(errbuf); + goto retblock; + } + + if (rd && !file_is_readable(dirname)) + { + sprintf(errbuf,"MaxScale doesn't have read permission " + "to '%s'.",dirname); + errstr = strdup(errbuf); goto retblock; } - if (!file_is_writable(dirname)) + if (wr && !file_is_writable(dirname)) { - errstr = strdup("MaxScale doesn't have write permission " - "to MAXSCALE_HOME. Exiting."); + sprintf(errbuf,"MaxScale doesn't have write permission " + "to '%s'.",dirname); + errstr = strdup(errbuf); goto retblock; } @@ -991,19 +978,29 @@ return_cnf_file_buf: static void usage(void) { fprintf(stderr, - "\nUsage : %s [-h] | [-d] [-c ] [-f ]\n\n" - " -d|--nodaemon enable running in terminal process (default:disabled)\n" - " -c|--homedir=... relative|absolute MaxScale home directory\n" - " -f|--config=... relative|absolute pathname of MaxScale configuration file\n" - " (default: $MAXSCALE_HOME/etc/MaxScale.cnf)\n" - " -l|--log=... log to file shared memory or stdout\n" - " -lfile, -lshm or -lstdout - defaults to shared memory\n" - " -s|--syslog= log messages to syslog." - " True or false - defaults to true\n" - " -S|--maxscalelog= log messages to MaxScale log." - " True or false - defaults to true\n" - " -v|--version print version info and exit\n" - " -?|--help show this help\n" + "\nUsage : %s [OPTION]...\n\n" + " -d, --nodaemon enable running in terminal process (default:disabled)\n" + " -f, --config=FILE relative|absolute pathname of MaxScale configuration file\n" + " (default:/etc/maxscale.cnf)\n" + " -l, --log=[file|shm] log to file or shared memory (default: shm)\n" + " -L, --logdir=PATH path to log file directory\n" + " (default: /var/log/maxscale)\n" + " -A, --cachedir=PATH path to cache directory\n" + " (default: /var/cache/maxscale)\n" + " -B, --libdir=PATH path to module directory\n" + " (default: /usr/lib64/maxscale)\n" + " -C, --configdir=PATH path to configuration file directory\n" + " (default: /etc/)\n" + " -D, --datadir=PATH path to data directory, stored embedded mysql tables\n" + " (default: /var/cache/maxscale)\n" + " -P, --piddir=PATH path to PID file directory\n" + " (default: /var/run/maxscale)\n" + " -U, --user=USER run MaxScale as another user.\n" + " The user ID and group ID of this user are used to run MaxScale.\n" + " -s, --syslog=[yes|no] log messages to syslog (default:yes)\n" + " -S, --maxscalelog=[yes|no] log messages to MaxScale log (default: yes)\n" + " -v, --version print version info and exit\n" + " -?, --help show this help\n" , progname); } @@ -1028,19 +1025,13 @@ static void usage(void) * This is not obvious solution because stderr is often directed to somewhere, * but currently this is the case. * - * The configuration file is by default \/etc/MaxScale.cnf + * The configuration file is by default /etc/maxscale.cnf * The name of configuration file and its location can be specified by * command-line argument. * - * \ is resolved in the following order: - * 1. from '-c ' command-line argument - * 2. from MAXSCALE_HOME environment variable - * 3. /etc/ if MaxScale.cnf is found from there - * 4. current working directory if MaxScale.cnf is found from there - * * \ is resolved in the following order: * 1. from '-f \' command-line argument - * 2. by using default value "MaxScale.cnf" + * 2. by using default value "maxscale.cnf" * */ int main(int argc, char **argv) @@ -1062,10 +1053,11 @@ int main(int argc, char **argv) char* cnf_file_path = NULL; /*< conf file, to be freed */ char* cnf_file_arg = NULL; /*< conf filename from cmd-line arg */ void* log_flush_thr = NULL; + char* tmp_path; + char* tmp_var; int option_index; int logtofile = 0; /* Use shared memory or file */ - int logtostdout = 0; /* Use stdout for log output */ - int syslog_enabled = 1; /** Log to syslog */ + int syslog_enabled = 0; /** Log to syslog */ int maxscalelog_enabled = 1; /** Log with MaxScale */ ssize_t log_flush_timeout_ms = 0; sigset_t sigset; @@ -1075,11 +1067,13 @@ int main(int argc, char **argv) datadir_cleanup, write_footer, NULL}; + + + sigemptyset(&sigpipe_mask); sigaddset(&sigpipe_mask, SIGPIPE); - progname = *argv; - + sprintf(datadir, "%s", default_datadir); #if defined(FAKE_CODE) memset(conn_open, 0, sizeof(bool)*10240); memset(dcb_fake_write_errno, 0, sizeof(unsigned char)*10240); @@ -1106,7 +1100,8 @@ int main(int argc, char **argv) goto return_main; } } - while ((opt = getopt_long(argc, argv, "dc:f:l:vs:S:?", + + while ((opt = getopt_long(argc, argv, "dc:f:l:vs:S:?L:D:C:B:U:A:P:", long_options, &option_index)) != -1) { bool succp = true; @@ -1116,56 +1111,6 @@ int main(int argc, char **argv) /*< Debug mode, maxscale runs in this same process */ daemon_mode = false; break; - - case 'c': - /*< - * Create absolute path pointing to MaxScale home - * directory. User-provided home directory may be - * either absolute or relative. If latter, it is - * expanded and stored in home_dir if succeed. - */ - if (optarg[0] != '-') - { - struct stat sb; - - if (stat(optarg, &sb) != -1 - && (! S_ISDIR(sb.st_mode))) - { - char* logerr = "Home directory argument " - "identifier \'-c\' was specified but " - "the argument didn't specify a valid " - "a directory."; - print_log_n_stderr(true, true, logerr, logerr, 0); - usage(); - succp = false; - } - else - { - get_expanded_pathname(&home_dir, optarg, NULL); - } - } - - if (home_dir != NULL) - { - /*< - * MAXSCALE_HOME is set. - * It is used to assist in finding the modules - * to be loaded into MaxScale. - */ - setenv("MAXSCALE_HOME", home_dir, 1); - } - else - { - char* logerr = "Home directory argument " - "identifier \'-c\' was specified but " - "the argument didn't specify \n a valid " - "home directory or the argument was " - "missing."; - print_log_n_stderr(true, true, logerr, logerr, 0); - usage(); - succp = false; - } - break; case 'f': /*< @@ -1199,8 +1144,6 @@ int main(int argc, char **argv) logtofile = 1; else if (strncasecmp(optarg, "shm", PATH_MAX) == 0) logtofile = 0; - else if (strncasecmp(optarg, "stdout", PATH_MAX) == 0) - logtostdout = 1; else { char* logerr = "Configuration file argument " @@ -1213,26 +1156,83 @@ int main(int argc, char **argv) succp = false; } break; - case 'S': - if(strstr(optarg,"=")) + case 'L': + + if(handle_path_arg(&tmp_path,optarg,NULL,true,false)) { - strtok(optarg,"= "); - maxscalelog_enabled = config_truth_value(strtok(NULL,"= ")); + logdir = tmp_path; } - else + + break; + case 'N': + if(handle_path_arg(&tmp_path,optarg,NULL,true,false)) { - maxscalelog_enabled = config_truth_value(optarg); + langdir = tmp_path; } break; - case 's': - if(strstr(optarg,"=")) + case 'P': + if(handle_path_arg(&tmp_path,optarg,NULL,true,true)) { - strtok(optarg,"= "); - syslog_enabled = config_truth_value(strtok(NULL,"= ")); + piddir = tmp_path; } - else + break; + case 'D': + sprintf(datadir,"%s",optarg); + maxscaledatadir = strdup(optarg); + datadir_defined = true; + break; + case 'C': + if(handle_path_arg(&tmp_path,optarg,NULL,true,false)) { - syslog_enabled = config_truth_value(optarg); + configdir = tmp_path; + } + break; + case 'B': + if(handle_path_arg(&tmp_path,optarg,NULL,true,false)) + { + libdir = tmp_path; + } + break; + case 'A': + if(handle_path_arg(&tmp_path,optarg,NULL,true,true)) + { + cachedir = tmp_path; + } + break; + case 'S': + { + char* tok = strstr(optarg,"="); + if(tok) + { + tok++; + if(tok) + maxscalelog_enabled = config_truth_value(tok); + } + else + { + maxscalelog_enabled = config_truth_value(optarg); + } + } + break; + case 's': + { + char* tok = strstr(optarg,"="); + if(tok) + { + tok++; + if(tok) + syslog_enabled = config_truth_value(tok); + } + else + { + syslog_enabled = config_truth_value(optarg); + } + } + break; + case 'U': + if(set_user(optarg) != 0) + { + succp = false; } break; case '?': @@ -1469,6 +1469,14 @@ int main(int argc, char **argv) "SIGFPE. Exiting."); goto sigset_err; } + l = signal_set(SIGCHLD, sigchld_handler); + + if (l != 0) + { + logerr = strdup("Failed to set signal handler for " + "SIGCHLD. Exiting."); + goto sigset_err; + } #ifdef SIGBUS l = signal_set(SIGBUS, sigfatal_handler); @@ -1500,6 +1508,18 @@ int main(int argc, char **argv) rc = MAXSCALE_INTERNALERROR; goto return_main; } + + /** OpenSSL initialization */ + if(!HAVE_OPENSSL_THREADS) + { + char* logerr = "OpenSSL library does not support multi-threading"; + print_log_n_stderr(true, true, logerr, logerr, eno); + rc = MAXSCALE_INTERNALERROR; + goto return_main; + } + SSL_library_init(); + SSL_load_error_strings(); + OPENSSL_add_all_algorithms_noconf(); /* register exit function for embedded MySQL library */ l = atexit(libmysqld_done); @@ -1514,88 +1534,60 @@ int main(int argc, char **argv) goto return_main; } - /*< - * If MaxScale home directory wasn't set by command-line argument. - * Next, resolve it from environment variable and further on, - * try to use default. + /** + * Resolve the full pathname for configuration file and check for + * read accessibility. */ - if (home_dir == NULL) + char pathbuf[PATH_MAX+1]; + snprintf(pathbuf,PATH_MAX,"%s",configdir ? configdir:default_configdir); + if(pathbuf[strlen(pathbuf)-1] != '/') + strcat(pathbuf,"/"); + + if (!resolve_maxscale_conf_fname(&cnf_file_path, pathbuf, cnf_file_arg)) { - if (!resolve_maxscale_homedir(&home_dir)) - { - ss_dassert(home_dir != NULL); - rc = MAXSCALE_HOMELESS; - goto return_main; - } - sprintf(mysql_home, "%s/mysql", home_dir); - setenv("MYSQL_HOME", mysql_home, 1); + ss_dassert(cnf_file_path == NULL); + rc = MAXSCALE_BADCONFIG; + goto return_main; } - else - { - char* log_context = strdup("Home directory command-line argument"); - char* errstr; - - errstr = check_dir_access(home_dir); - - if (errstr != NULL) - { - char* logstr = (char*)malloc(strlen(log_context)+ - 1+ - strlen(errstr)+ - 1); - - snprintf(logstr, - strlen(log_context)+ - 1+ - strlen(errstr)+1, - "%s: %s", - log_context, - errstr); - - print_log_n_stderr(true, true, logstr, logstr, 0); - - free(errstr); - free(logstr); - rc = MAXSCALE_HOMELESS; - goto return_main; - } - else if (!daemon_mode) - { - fprintf(stderr, - "Using %s as MAXSCALE_HOME = %s\n", - log_context, - home_dir); - } - free(log_context); - } + + ini_parse(cnf_file_path,cnf_preparser,NULL); + + if(!datadir_defined) + sprintf(datadir,"%s",default_datadir); + + + /** Use the cache dir for the mysql folder of the embedded library */ + sprintf(mysql_home, "%s/mysql", cachedir?cachedir:default_cachedir); + setenv("MYSQL_HOME", mysql_home, 1); + /** * Init Log Manager for MaxScale. - * If $MAXSCALE_HOME is set then write the logs into $MAXSCALE_HOME/log. * The skygw_logmanager_init expects to take arguments as passed to main * and proesses them with getopt, therefore we need to give it a dummy * argv[0] */ { char buf[1024]; - char *argv[9]; + char *argv[8]; bool succp; - /** Set log directory under $MAXSCALE_HOME/log */ - sprintf(buf, "%s/log", home_dir); - if(mkdir(buf, 0777) != 0) + /** Use default log directory /var/log/maxscale/ */ + if(logdir == NULL) { - if(errno != EEXIST) - { - fprintf(stderr, - "Error: Cannot create log directory: %s\n", - buf); - goto return_main; - } + if(mkdir(default_logdir,0777) != 0 && errno != EEXIST) + { + fprintf(stderr, + "Error: Cannot create log directory: %s\n", + default_logdir); + goto return_main; + } + logdir = strdup(default_logdir); } + argv[0] = "MaxScale"; argv[1] = "-j"; - argv[2] = buf; + argv[2] = logdir; if(!syslog_enabled) { @@ -1608,17 +1600,8 @@ int main(int argc, char **argv) } logmanager_enable_syslog(syslog_enabled); logmanager_enable_maxscalelog(maxscalelog_enabled); - if (logtostdout) - { - argv[3] = "-s"; /*< store to shared memory */ - argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< to shm */ - argv[5] = "-l"; /*< write to syslog */ - argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< to syslog */ - argv[7] = "-o"; - argv[8] = NULL; - succp = skygw_logmanager_init(8, argv); - } - else if (logtofile) + + if (logtofile) { argv[3] = "-l"; /*< write to syslog */ /** Logs that should be syslogged */ @@ -1643,25 +1626,25 @@ int main(int argc, char **argv) goto return_main; } } - /** - * Resolve the full pathname for configuration file and check for - * read accessibility. - */ - if (!resolve_maxscale_conf_fname(&cnf_file_path, home_dir, cnf_file_arg)) - { - ss_dassert(cnf_file_path == NULL); - rc = MAXSCALE_BADCONFIG; - goto return_main; - } - /*< + + + if(cachedir == NULL) + cachedir = strdup(default_cachedir); + if(langdir == NULL) + langdir = strdup(default_langdir); + if(libdir == NULL) + libdir = strdup(default_libdir); + /** * Set a data directory for the mysqld library, we use * a unique directory name to avoid clauses if multiple - * instances of the gateway are beign run on the same + * instances of the gateway are being run on the same * machine. */ - sprintf(datadir, "%s/data", home_dir); + if(datadir[strlen(datadir)-1] != '/') + strcat(datadir,"/"); + strcat(datadir,"data"); if(mkdir(datadir, 0777) != 0){ if(errno != EEXIST){ @@ -1671,7 +1654,7 @@ int main(int argc, char **argv) } } - sprintf(datadir, "%s/data/data%d", home_dir, getpid()); + sprintf(datadir, "%s/data%d", datadir, getpid()); if(mkdir(datadir, 0777) != 0){ @@ -1685,31 +1668,41 @@ int main(int argc, char **argv) if (!daemon_mode) { fprintf(stderr, - "Home directory : %s" - "\nConfiguration file : %s" - "\nLog directory : %s/log" - "\nData directory : %s\n\n", - home_dir, + "Configuration file : %s\n" + "Log directory : %s\n" + "Data directory : %s\n" + "Module directory : %s\n" + "Service cache : %s\n\n", cnf_file_path, - home_dir, - datadir); + logdir, + datadir, + libdir, + cachedir); } - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Home directory : %s", - home_dir))); - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Data directory : %s", - datadir))); - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Log directory : %s/log", - home_dir))); - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Configuration file : %s", - cnf_file_path))); + + LOGIF(LM, + (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Configuration file: %s", + cnf_file_path))); + LOGIF(LM, + (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Log directory: %s/", + logdir))); + LOGIF(LM, + (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Data directory: %s", + datadir))); + LOGIF(LM, + (skygw_log_write_flush(LOGFILE_MESSAGE, + "Module directory: %s", + libdir))); + LOGIF(LM, + (skygw_log_write_flush(LOGFILE_MESSAGE, + "Service cache: %s", + cachedir))); /*< Update the server options */ for (i = 0; server_options[i]; i++) @@ -1723,8 +1716,8 @@ int main(int argc, char **argv) { snprintf(language_arg, 11+PATH_MAX+1, - "--language=%s/mysql", - home_dir); + "--language=%s", + langdir); server_options[i] = language_arg; } } @@ -1780,10 +1773,13 @@ int main(int argc, char **argv) } libmysqld_started = TRUE; + if(libdir == NULL) + libdir = strdup(default_libdir); + if (!config_load(cnf_file_path)) { char* fprerr = "Failed to load MaxScale configuration " - "file. Exiting."; + "file. Exiting. See the error log for details."; print_log_n_stderr(false, !daemon_mode, fprerr, fprerr, 0); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -1853,6 +1849,26 @@ int main(int argc, char **argv) LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "MaxScale started with %d server threads.", config_threadcount()))); + int numlocks = CRYPTO_num_locks(); + if((ssl_locks = malloc(sizeof(SPINLOCK)*(numlocks + 1))) == NULL) + { + char* logerr = "Memory allocation failed"; + print_log_n_stderr(true, true, logerr, logerr, eno); + rc = MAXSCALE_INTERNALERROR; + goto return_main; + } + + for(i = 0;ipw_gid); + if(rval != 0) + { + printf("Error: Failed to change group to '%d': %d %s\n", + pwname->pw_gid,errno,strerror(errno)); + return rval; + } + + rval = setuid(pwname->pw_uid); + if(rval != 0) + { + printf("Error: Failed to change user to '%s': %d %s\n", + pwname->pw_name,errno,strerror(errno)); + return rval; + } +#ifdef SS_DEBUG + else + { + printf("Running MaxScale as: %s %d:%d\n",pwname->pw_name,pwname->pw_uid,pwname->pw_gid); + } +#endif + + + return rval; +} + diff --git a/server/core/gwdirs.c b/server/core/gwdirs.c new file mode 100644 index 000000000..1fe8af008 --- /dev/null +++ b/server/core/gwdirs.c @@ -0,0 +1,29 @@ +#include + +/** + * Get the directory with all the modules. + * @return The module directory + */ +char* get_libdir() +{ + return libdir?libdir:(char*)default_libdir; +} + +/** + * Get the service cache directory + * @return The path to the cache directory + */ +char* get_cachedir() +{ + return cachedir?cachedir:(char*)default_cachedir; +} + + +/** + * Get the service cache directory + * @return The path to the cache directory + */ +char* get_datadir() +{ + return maxscaledatadir?maxscaledatadir:(char*)default_datadir; +} diff --git a/server/core/hashtable.c b/server/core/hashtable.c index df88ac9f7..c43b76916 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -170,6 +170,9 @@ hashtable_free(HASHTABLE *table) int i; HASHENTRIES *entry, *ptr; + if(table == NULL) + return; + hashtable_write_lock(table); for (i = 0; i < table->hashsize; i++) { @@ -235,7 +238,7 @@ hashtable_add(HASHTABLE *table, void *key, void *value) unsigned int hashkey; HASHENTRIES *entry; - if (key == NULL || value == NULL) + if (table == NULL || key == NULL || value == NULL) return 0; if (table->hashsize <= 0) { @@ -308,9 +311,13 @@ hashtable_add(HASHTABLE *table, void *key, void *value) int hashtable_delete(HASHTABLE *table, void *key) { -unsigned int hashkey = table->hashfn(key) % table->hashsize; +unsigned int hashkey; HASHENTRIES *entry, *ptr; + if(table == NULL || key == NULL) + return 0; + + hashkey = table->hashfn(key) % table->hashsize; hashtable_write_lock(table); entry = table->entries[hashkey % table->hashsize]; while (entry && entry->key && table->cmpfn(key, entry->key) != 0) @@ -369,9 +376,13 @@ HASHENTRIES *entry, *ptr; void * hashtable_fetch(HASHTABLE *table, void *key) { -unsigned int hashkey = table->hashfn(key) % table->hashsize; +unsigned int hashkey; HASHENTRIES *entry; + if(table == NULL || key == NULL) + return NULL; + + hashkey = table->hashfn(key) % table->hashsize; hashtable_read_lock(table); entry = table->entries[hashkey % table->hashsize]; while (entry && entry->key && table->cmpfn(key, entry->key) != 0) @@ -401,6 +412,9 @@ hashtable_stats(HASHTABLE *table) int total, longest, i, j; HASHENTRIES *entries; + if(table == NULL) + return; + printf("Hashtable: %p, size %d\n", table, table->hashsize); total = 0; longest = 0; @@ -606,6 +620,9 @@ hashtable_next(HASHITERATOR *iter) int i; HASHENTRIES *entries; + if(iter == NULL) + return NULL; + iter->depth++; while (iter->chain < iter->table->hashsize) { diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 172f24ad0..9d7d69220 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -48,6 +48,8 @@ #include #include #include +#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -104,21 +106,10 @@ WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) return realsize; } -char* get_maxscale_home(void) -{ - char* home = getenv("MAXSCALE_HOME"); - if (home == NULL) - { - home = "/usr/local/mariadb-maxscale"; - } - return home; -} - - /** * Load the dynamic library related to a gateway module. The routine * will look for library files in the current directory, - * $MAXSCALE_HOME/modules and /usr/local/mariadb-maxscale/modules. + * the configured folder and /usr/lib64/maxscale. * * @param module Name of the module to load * @param type Type of module, used purely for registration @@ -142,22 +133,17 @@ MODULE_INFO *mod_info = NULL; * * Search of the shared object. */ - snprintf(fname,MAXPATHLEN+1, "./lib%s.so", module); - + + snprintf(fname, MAXPATHLEN+1,"%s/lib%s.so", get_libdir(), module); + if (access(fname, F_OK) == -1) { - home = get_maxscale_home (); - snprintf(fname, MAXPATHLEN+1,"%s/modules/lib%s.so", home, module); - - if (access(fname, F_OK) == -1) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to find library for " - "module: %s.", - module))); - return NULL; - } + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to find library for " + "module: %s. Module dir: %s", + module, get_libdir()))); + return NULL; } if ((dlhandle = dlopen(fname, RTLD_NOW|RTLD_LOCAL)) == NULL) diff --git a/server/core/maxkeys.c b/server/core/maxkeys.c index c85dd1d8e..27b5c8382 100644 --- a/server/core/maxkeys.c +++ b/server/core/maxkeys.c @@ -31,19 +31,25 @@ #include #include #include +#include + int main(int argc, char **argv) { - int arg_count = 6; + int arg_count = 4; char *home; + char *keyfile; char** arg_vector; int rval = 0; - if (argc != 2) + if (argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; + keyfile = "/var/lib/maxscale/"; + fprintf(stderr, "Generating .secrets file in /var/lib/maxscale/ ...\n"); + } + else + { + keyfile = argv[1]; } - arg_vector = malloc(sizeof(char*)*(arg_count + 1)); if(arg_vector == NULL) @@ -54,26 +60,14 @@ int main(int argc, char **argv) arg_vector[0] = "logmanager"; arg_vector[1] = "-j"; - - if ((home = getenv("MAXSCALE_HOME")) != NULL) - { - arg_vector[2] = (char*)malloc((strlen(home) + strlen("/log"))*sizeof(char)); - sprintf(arg_vector[2],"%s/log",home); - } - else - { - arg_vector[2] = strdup("/usr/local/mariadb-maxscale/log"); - } - arg_vector[3] = "-o"; - arg_vector[4] = "-l"; - arg_vector[5] = "LOGFILE_ERROR"; - arg_vector[6] = NULL; + arg_vector[2] = "/var/log/maxscale/maxkeys"; + arg_vector[3] = "-o"; + arg_vector[4] = NULL; skygw_logmanager_init(arg_count,arg_vector); - free(arg_vector[2]); free(arg_vector); - if (secrets_writeKeys(argv[1])) + if (secrets_writeKeys(keyfile)) { fprintf(stderr, "Failed to encode the password\n"); rval = 1; diff --git a/server/core/maxpasswd.c b/server/core/maxpasswd.c index 453aba38f..0a3d5ddea 100644 --- a/server/core/maxpasswd.c +++ b/server/core/maxpasswd.c @@ -46,9 +46,9 @@ main(int argc, char **argv) char** arg_vector; int rval = 0; - if (argc != 2) + if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } @@ -60,18 +60,9 @@ main(int argc, char **argv) return 1; } - arg_vector[0] = "logmanager"; - arg_vector[1] = "-j"; - - if ((home = getenv("MAXSCALE_HOME")) != NULL) - { - arg_vector[2] = (char*)malloc((strlen(home) + strlen("/log"))*sizeof(char)); - sprintf(arg_vector[2],"%s/log",home); - } - else - { - arg_vector[2] = strdup("/usr/local/mariadb-maxscale/log"); - } + arg_vector[0] = strdup("logmanager"); + arg_vector[1] = strdup("-j"); + arg_vector[2] = strdup("/var/log/maxscale"); arg_vector[3] = "-o"; arg_vector[4] = "-l"; @@ -88,9 +79,9 @@ main(int argc, char **argv) return 1; } - strncpy(pw,argv[1],80); + strncpy(pw,argv[2],80); - if ((enc = encryptPassword(pw)) != NULL){ + if ((enc = encryptPassword(argv[1],pw)) != NULL){ printf("%s\n", enc); }else{ fprintf(stderr, "Failed to encode the password\n"); diff --git a/server/core/memlog.c b/server/core/memlog.c index 8e44e2d5c..421a89ab9 100644 --- a/server/core/memlog.c +++ b/server/core/memlog.c @@ -31,6 +31,7 @@ #include #include #include +#include static MEMLOG *memlogs = NULL; static SPINLOCK memlock = SPINLOCK_INIT; @@ -133,7 +134,7 @@ memlog_log(MEMLOG *log, void *value) switch (log->type) { case ML_INT: - ((int *)(log->values))[log->offset] = (int)value; + ((int *)(log->values))[log->offset] = (intptr_t)value; break; case ML_LONG: ((long *)(log->values))[log->offset] = (long)value; diff --git a/server/core/modutil.c b/server/core/modutil.c index ecd3b51a9..860f93cc9 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -540,7 +540,7 @@ GWBUF* modutil_get_complete_packets(GWBUF** p_readbuf) { GWBUF *buff = NULL, *packet; uint8_t *ptr; - int len,blen,total = 0; + uint32_t len,blen,total = 0; if(p_readbuf == NULL || (*p_readbuf) == NULL || gwbuf_length(*p_readbuf) < 3) @@ -841,4 +841,4 @@ int modutil_count_statements(GWBUF* buffer) } return num; -} \ No newline at end of file +} diff --git a/server/core/monitor.c b/server/core/monitor.c index a8401e6bb..f422b4f6b 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -28,6 +28,7 @@ * and monitor id * 30/10/14 Massimiliano Pinto Addition of disable_master_failback parameter * 07/11/14 Massimiliano Pinto Addition of monitor network timeouts + * 08/05/15 Markus Makela Moved common monitor variables to MONITOR struct * * @endverbatim */ @@ -65,8 +66,6 @@ MONITOR *mon; { return NULL; } - mon->state = MONITOR_STATE_ALLOC; - mon->name = strdup(name); if ((mon->module = load_module(module, MODULE_MONITOR)) == NULL) { @@ -74,13 +73,21 @@ MONITOR *mon; LOGFILE_ERROR, "Error : Unable to load monitor module '%s'.", name))); - free(mon->name); free(mon); return NULL; } - + mon->state = MONITOR_STATE_ALLOC; + mon->name = strdup(name); mon->handle = NULL; - + mon->databases = NULL; + mon->password = NULL; + mon->user = NULL; + mon->password = NULL; + mon->read_timeout = DEFAULT_READ_TIMEOUT; + mon->write_timeout = DEFAULT_WRITE_TIMEOUT; + mon->connect_timeout = DEFAULT_CONNECT_TIMEOUT; + mon->interval = MONITOR_INTERVAL; + spinlock_init(&mon->lock); spinlock_acquire(&monLock); mon->next = allMonitors; allMonitors = mon; @@ -100,7 +107,7 @@ monitor_free(MONITOR *mon) { MONITOR *ptr; - mon->module->stopMonitor(mon->handle); + mon->module->stopMonitor(mon); mon->state = MONITOR_STATE_FREED; spinlock_acquire(&monLock); if (allMonitors == mon) @@ -127,8 +134,10 @@ MONITOR *ptr; void monitorStart(MONITOR *monitor, void* params) { - monitor->handle = (*monitor->module->startMonitor)(monitor->handle,params); + spinlock_acquire(&monitor->lock); + monitor->handle = (*monitor->module->startMonitor)(monitor,params); monitor->state = MONITOR_STATE_RUNNING; + spinlock_release(&monitor->lock); } /** @@ -142,7 +151,7 @@ monitorStop(MONITOR *monitor) if(monitor->state != MONITOR_STATE_STOPPED) { monitor->state = MONITOR_STATE_STOPPING; - monitor->module->stopMonitor(monitor->handle); + monitor->module->stopMonitor(monitor); monitor->state = MONITOR_STATE_STOPPED; } } @@ -175,7 +184,32 @@ MONITOR *ptr; void monitorAddServer(MONITOR *mon, SERVER *server) { - mon->module->registerServer(mon->handle, server); + MONITOR_SERVERS *ptr, *db; + + if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) + return; + db->server = server; + db->con = NULL; + db->next = NULL; + db->mon_err_count = 0; + db->log_version_err = true; + /** Server status is uninitialized */ + db->mon_prev_status = -1; + /* pending status is updated by get_replication_tree */ + db->pending_status = 0; + + spinlock_acquire(&mon->lock); + + if (mon->databases == NULL) + mon->databases = db; + else + { + ptr = mon->databases; + while (ptr->next != NULL) + ptr = ptr->next; + ptr->next = db; + } + spinlock_release(&mon->lock); } /** @@ -189,7 +223,8 @@ monitorAddServer(MONITOR *mon, SERVER *server) void monitorAddUser(MONITOR *mon, char *user, char *passwd) { - mon->module->defaultUser(mon->handle, user, passwd); + mon->user = strdup(user); + mon->password = strdup(passwd); } /** @@ -209,7 +244,7 @@ MONITOR *ptr; dcb_printf(dcb, "Monitor: %p\n", ptr); dcb_printf(dcb, "\tName: %s\n", ptr->name); if (ptr->module->diagnostics) - ptr->module->diagnostics(dcb, ptr->handle); + ptr->module->diagnostics(dcb, ptr); ptr = ptr->next; } spinlock_release(&monLock); @@ -227,7 +262,7 @@ monitorShow(DCB *dcb, MONITOR *monitor) dcb_printf(dcb, "Monitor: %p\n", monitor); dcb_printf(dcb, "\tName: %s\n", monitor->name); if (monitor->module->diagnostics) - monitor->module->diagnostics(dcb, monitor->handle); + monitor->module->diagnostics(dcb, monitor); } /** @@ -288,10 +323,7 @@ MONITOR *ptr; void monitorSetInterval (MONITOR *mon, unsigned long interval) { - if (mon->module->setInterval != NULL) { - mon->interval = interval; - mon->module->setInterval(mon->handle, interval); - } + mon->interval = interval; } /** @@ -303,9 +335,55 @@ monitorSetInterval (MONITOR *mon, unsigned long interval) */ void monitorSetNetworkTimeout(MONITOR *mon, int type, int value) { - if (mon->module->setNetworkTimeout != NULL) { - mon->module->setNetworkTimeout(mon->handle, type, value); + + int max_timeout = (int)(mon->interval/1000); + int new_timeout = max_timeout -1; + + if (new_timeout <= 0) + new_timeout = DEFAULT_CONNECT_TIMEOUT; + + switch(type) { + case MONITOR_CONNECT_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->connect_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->connect_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); } + break; + + case MONITOR_READ_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->read_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->read_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); + } + break; + + case MONITOR_WRITE_TIMEOUT: + if (value < max_timeout) { + memcpy(&mon->write_timeout, &value, sizeof(int)); + } else { + memcpy(&mon->write_timeout, &new_timeout, sizeof(int)); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" + ", lowering to %i seconds", value, max_timeout, new_timeout))); + } + break; + default: + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); + break; + } } /** diff --git a/server/core/poll.c b/server/core/poll.c index 377310cb0..19816f155 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -15,10 +15,12 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ + #include #include #include #include +#include #include #include #include @@ -71,6 +73,7 @@ int max_poll_sleep; * in the loop after the epoll_wait. This allows for better * thread utilisaiton and fairer scheduling of the event * processing. + * 07/07/15 Martin Brampton Simplified add and remove DCB, improve error handling. * * @endverbatim */ @@ -186,6 +189,11 @@ static struct { */ static void poll_loadav(void *); +/** + * Function to analyse error return from epoll_ctl + */ +static int poll_resolve_error(DCB *, int, bool); + /** * Initialise the polling system we are using for the gateway. * @@ -247,7 +255,7 @@ int poll_add_dcb(DCB *dcb) { int rc = -1; - dcb_state_t old_state = DCB_STATE_UNDEFINED; + dcb_state_t old_state = dcb->state; dcb_state_t new_state; struct epoll_event ev; @@ -263,58 +271,67 @@ poll_add_dcb(DCB *dcb) /*< * Choose new state according to the role of dcb. */ + spinlock_acquire(&dcb->dcb_initlock); if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER) { new_state = DCB_STATE_POLLING; } else { ss_dassert(dcb->dcb_role == DCB_ROLE_SERVICE_LISTENER); new_state = DCB_STATE_LISTENING; } - /*< - * If dcb is in unexpected state, state change fails indicating that dcb - * is not polling anymore. + /* + * Check DCB current state seems sensible */ - if (dcb_set_state(dcb, new_state, &old_state)) { - rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dcb->fd, &ev); - - if (rc != 0) { - int eno = errno; - errno = 0; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Adding dcb %p in state %s " - "to poll set failed. epoll_ctl failed due " - "%d, %s.", - dcb, - STRDCBSTATE(dcb->state), - eno, - strerror(eno)))); - } else { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [poll_add_dcb] Added dcb %p in state %s to " - "poll set.", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state)))); - } - ss_info_dassert(rc == 0, "Unable to add poll"); /*< trap in debug */ - } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to set new state for dcb %p " - "in state %s. Adding to poll set failed.", - dcb, - STRDCBSTATE(dcb->state)))); + if (DCB_STATE_DISCONNECTED == dcb->state + || DCB_STATE_ZOMBIE == dcb->state + || DCB_STATE_UNDEFINED == dcb->state) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_add_dcb] Error : existing state of dcb %p " + "is %s, but this should be impossible, crashing.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + raise(SIGABRT); } - + if (DCB_STATE_POLLING == dcb->state + || DCB_STATE_LISTENING == dcb->state) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_add_dcb] Error : existing state of dcb %p " + "is %s, but this is probably an error, not crashing.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + } + dcb->state = new_state; + spinlock_release(&dcb->dcb_initlock); + /* + * The only possible failure that will not cause a crash is + * running out of system resources. + */ + rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dcb->fd, &ev); + if (rc) + { + rc = poll_resolve_error(dcb, errno, true); + } + if (0 == rc) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [poll_add_dcb] Added dcb %p in state %s to poll set.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); + } + else dcb->state = old_state; return rc; } /** * Remove a descriptor from the set of descriptors within the * polling environment. - * The state change command may fail because concurrent threads may call - * dcb_set_state simultaneously and the conflict is prevented in dcb_set_state. * * @param dcb The descriptor to remove * @return -1 on error or 0 on success @@ -322,63 +339,127 @@ poll_add_dcb(DCB *dcb) int poll_remove_dcb(DCB *dcb) { - struct epoll_event ev; int rc = -1; - dcb_state_t old_state = DCB_STATE_UNDEFINED; - dcb_state_t new_state = DCB_STATE_NOPOLLING; - + struct epoll_event ev; CHK_DCB(dcb); + spinlock_acquire(&dcb->dcb_initlock); /*< It is possible that dcb has already been removed from the set */ - if (dcb->state != DCB_STATE_POLLING) - { - if (dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE) - { - rc = 0; - } - goto return_rc; + if (dcb->state == DCB_STATE_NOPOLLING || + dcb->state == DCB_STATE_ZOMBIE) + { + spinlock_release(&dcb->dcb_initlock); + return 0; + } + if (DCB_STATE_POLLING != dcb->state + && DCB_STATE_LISTENING != dcb->state) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_remove_dcb] Error : existing state of dcb %p " + "is %s, but this is probably an error, not crashing.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state)))); } /*< * Set state to NOPOLLING and remove dcb from poll set. */ - 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); + dcb->state = DCB_STATE_NOPOLLING; + spinlock_release(&dcb->dcb_initlock); - 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. - */ - else if (old_state == new_state) + /** + * Only positive fds can be removed from epoll set. + * Cloned DCBs can have a state of DCB_STATE_POLLING but are not in + * the epoll set and do not have a valid file descriptor. Hence the + * only action for them is already done - the change of state to + * DCB_STATE_NOPOLLING. + */ + spinlock_release(&dcb->dcb_initlock); + if (dcb->fd > 0) { - rc = 0; - goto return_rc; + rc = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, dcb->fd, &ev); + /** + * The poll_resolve_error function will always + * return 0 or crash. So if it returns non-zero result, + * things have gone wrong and we crash. + */ + if (rc) rc = poll_resolve_error(dcb, errno, false); + if (rc) raise(SIGABRT); + /*< Set bit for each maxscale thread */ + bitmask_copy(&dcb->memdata.bitmask, poll_bitmask()); } - - /*< Set bit for each maxscale thread */ - bitmask_copy(&dcb->memdata.bitmask, poll_bitmask()); - rc = 0; -return_rc: return rc; } +/** + * Check error returns from epoll_ctl. Most result in a crash since they + * are "impossible". Adding when already present is assumed non-fatal. + * Likewise, removing when not present is assumed non-fatal. + * It is assumed that callers to poll routines can handle the failure + * that results from hitting system limit, although an error is written + * here to record the problem. + * + * @param errornum The errno set by epoll_ctl + * @param adding True for adding to poll list, false for removing + * @return -1 on error or 0 for possibly revised return code + */ +static int +poll_resolve_error(DCB *dcb, int errornum, bool adding) +{ + if (adding) + { + if (EEXIST == errornum) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_resolve_error] Error : epoll_ctl could not add, " + "already exists for DCB %p.", + pthread_self(), + dcb))); + // Assume another thread added and no serious harm done + return 0; + } + if (ENOSPC == errornum) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_resolve_error] The limit imposed by " + "/proc/sys/fs/epoll/max_user_watches was " + "encountered while trying to register (EPOLL_CTL_ADD) a new " + "file descriptor on an epoll instance for dcb %p.", + pthread_self(), + dcb))); + /* Failure - assume handled by callers */ + return -1; + } + } + else + { + /* Must be removing */ + if (ENOENT == errornum) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [poll_resolve_error] Error : epoll_ctl could not remove, " + "not found, for dcb %p.", + pthread_self(), + dcb))); + // Assume another thread removed and no serious harm done + return 0; + } + } + /* Common checks for add or remove - crash MaxScale */ + if (EBADF == errornum) raise(SIGABRT); + if (EINVAL == errornum) raise(SIGABRT); + if (ENOMEM == errornum) raise(SIGABRT); + if (EPERM == errornum) raise(SIGABRT); + /* Undocumented error number */ + raise(SIGABRT); + /* The following statement should never be reached, but avoids compiler warning */ + return -1; +} + #define BLOCKINGPOLL 0 /*< Set BLOCKING POLL to 1 if using a single thread and to make * debugging easier. */ @@ -440,7 +521,6 @@ poll_waitevents(void *arg) struct epoll_event events[MAX_EVENTS]; int i, nfds, timeout_bias = 1; intptr_t thread_id = (intptr_t)arg; -DCB *zombies = NULL; int poll_spins = 0; /** Add this thread to the bitmask of running polling threads */ @@ -611,7 +691,7 @@ int poll_spins = 0; if (thread_data) thread_data[thread_id].state = THREAD_ZPROCESSING; - zombies = dcb_process_zombies(thread_id); + dcb_process_zombies(thread_id); if (thread_data) thread_data[thread_id].state = THREAD_IDLE; @@ -796,16 +876,6 @@ unsigned long qtime; eno = gw_getsockerrno(dcb->fd); if (eno == 0) { -#if MUTEX_BLOCK - simple_mutex_lock(&dcb->dcb_write_lock, true); - ss_info_dassert(!dcb->dcb_write_active, - "Write already active"); - dcb->dcb_write_active = TRUE; - atomic_add(&pollStats.n_write, 1); - dcb->func.write_ready(dcb); - dcb->dcb_write_active = FALSE; - simple_mutex_unlock(&dcb->dcb_write_lock); -#else atomic_add(&pollStats.n_write, 1); /** Read session id to thread's local storage */ LOGIF_MAYBE(LT, (dcb_get_ses_log_info( @@ -813,7 +883,6 @@ unsigned long qtime; &tls_log_info.li_sesid, &tls_log_info.li_enabled_logs))); dcb->func.write_ready(dcb); -#endif } else { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -829,12 +898,6 @@ unsigned long qtime; } if (ev & EPOLLIN) { -#if MUTEX_BLOCK - simple_mutex_lock(&dcb->dcb_read_lock, true); - ss_info_dassert(!dcb->dcb_read_active, "Read already active"); - dcb->dcb_read_active = TRUE; -#endif - if (dcb->state == DCB_STATE_LISTENING) { LOGIF(LD, (skygw_log_write( @@ -868,11 +931,6 @@ unsigned long qtime; &tls_log_info.li_enabled_logs))); dcb->func.read(dcb); } -#if MUTEX_BLOCK - dcb->dcb_read_active = FALSE; - simple_mutex_unlock( - &dcb->dcb_read_lock); -#endif } if (ev & EPOLLERR) { @@ -1604,7 +1662,7 @@ RESULT_ROW *row; } /** - * Return a resultset that has the current set of services in it + * Return a result set that has the current set of services in it * * @return A Result set */ diff --git a/server/core/secrets.c b/server/core/secrets.c index 3eae2cc48..b0e0c5542 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -22,6 +22,7 @@ #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -52,31 +53,26 @@ int i; } /** - * secrets_readKeys - * - * This routine reads data from a binary file and extracts the AES encryption key - * and the AES Init Vector - * + * This routine reads data from a binary file named ".secrets" and extracts the AES encryption key + * and the AES Init Vector. + * If the path parameter is not null the custom path is interpreted as a folder + * containing the .secrets file. Otherwise the default location is used. * @return The keys structure or NULL on error */ static MAXKEYS * -secrets_readKeys() +secrets_readKeys(char* path) { -char secret_file[255]; +char secret_file[PATH_MAX+1]; char *home; MAXKEYS *keys; struct stat secret_stats; int fd; int len; static int reported = 0; - - home = getenv("MAXSCALE_HOME"); - - if (home == NULL) { - home = "/usr/local/mariadb-maxscale"; - } - snprintf(secret_file, 255, "%s/etc/.secrets", home); - + if(path != NULL) + snprintf(secret_file, PATH_MAX, "%s/.secrets", path); + else + snprintf(secret_file, PATH_MAX, "%s/.secrets", get_datadir()); /* Try to access secrets file */ if (access(secret_file, R_OK) == -1) { @@ -225,11 +221,20 @@ static int reported = 0; * @param secret_file The file with secret keys * @return 0 on success and 1 on failure */ -int secrets_writeKeys(char *secret_file) +int secrets_writeKeys(char *path) { int fd,randfd; unsigned int randval; MAXKEYS key; +char secret_file[PATH_MAX + 10]; + +if(strlen(path) > PATH_MAX) +{ + skygw_log_write(LOGFILE_ERROR,"Error: Pathname too long."); + return 1; +} + + sprintf(secret_file,"%s/.secrets",path); /* Open for writing | Create | Truncate the file for writing */ if ((fd = open(secret_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR)) < 0) @@ -332,7 +337,7 @@ char *ptr; unsigned char encrypted[80]; int enlen; - keys = secrets_readKeys(); + keys = secrets_readKeys(NULL); if (!keys) return strdup(crypt); /* @@ -369,12 +374,12 @@ int enlen; * Encrypt a password that can be stored in the MaxScale configuration file. * * Note the return is always a malloc'd string that the caller must free - * + * @param path Path the the .secrets file * @param password The password to encrypt * @return The encrypted password */ char * -encryptPassword(char *password) +encryptPassword(char* path, char *password) { MAXKEYS *keys; AES_KEY aeskey; @@ -383,7 +388,7 @@ char *hex_output; unsigned char padded_passwd[80]; unsigned char encrypted[80]; - if ((keys = secrets_readKeys()) == NULL) + if ((keys = secrets_readKeys(path)) == NULL) return NULL; memset(padded_passwd, 0, 80); diff --git a/server/core/server.c b/server/core/server.c index 9455adb94..34875fe91 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -33,6 +33,7 @@ * 30/08/14 Massimiliano Pinto Addition of new service status description * 30/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS description * 01/06/15 Massimiliano Pinto Addition of server_update_address/port + * 19/06/15 Martin Brampton Extra code for persistent connections * * @endverbatim */ @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -54,6 +56,8 @@ extern __thread log_info_t tls_log_info; static SPINLOCK server_spin = SPINLOCK_INIT; static SERVER *allServers = NULL; +static void spin_reporter(void *, char *, int); + /** * Allocate a new server withn the gateway * @@ -71,6 +75,10 @@ SERVER *server; if ((server = (SERVER *)calloc(1, sizeof(SERVER))) == NULL) return NULL; +#if defined(SS_DEBUG) + server->server_chk_top = CHK_NUM_SERVER; + server->server_chk_tail = CHK_NUM_SERVER; +#endif server->name = strdup(servname); server->protocol = strdup(protocol); server->port = port; @@ -79,6 +87,9 @@ SERVER *server; server->rlag = -2; server->master_id = -1; server->depth = -1; + server->persistent = NULL; + server->persistmax = 0; + spinlock_init(&server->persistlock); spinlock_acquire(&server_spin); server->next = allServers; @@ -96,39 +107,104 @@ SERVER *server; * @return Returns true if the server was freed */ int -server_free(SERVER *server) +server_free(SERVER *tofreeserver) { -SERVER *ptr; +SERVER *server; /* First of all remove from the linked list */ spinlock_acquire(&server_spin); - if (allServers == server) + if (allServers == tofreeserver) { - allServers = server->next; + allServers = tofreeserver->next; } else { - ptr = allServers; - while (ptr && ptr->next != server) + server = allServers; + while (server && server->next != tofreeserver) { - ptr = ptr->next; + server = server->next; } - if (ptr) - ptr->next = server->next; + if (server) + server->next = tofreeserver->next; } spinlock_release(&server_spin); /* Clean up session and free the memory */ - free(server->name); - free(server->protocol); - if (server->unique_name) - free(server->unique_name); - if (server->server_string) - free(server->server_string); - free(server); + free(tofreeserver->name); + free(tofreeserver->protocol); + if (tofreeserver->unique_name) + free(tofreeserver->unique_name); + if (tofreeserver->server_string) + free(tofreeserver->server_string); + if (tofreeserver->persistent) + dcb_persistent_clean_count(tofreeserver->persistent, true); + free(tofreeserver); return 1; } +/** + * Get a DCB from the persistent connection pool, if possible + * + * @param server The server to set the name on + * @param user The name of the user needing the connection + * @param protocol The name of the protocol needed for the connection + */ +DCB * +server_get_persistent(SERVER *server, char *user, const char *protocol) +{ + DCB *dcb, *previous = NULL; + + if (server->persistent && dcb_persistent_clean_count(server->persistent, false) && server->persistent) + { + spinlock_acquire(&server->persistlock); + dcb = server->persistent; + while (dcb) { + if (dcb->user + && dcb->protoname + && !dcb-> dcb_errhandle_called + && !(dcb->flags & DCBF_HUNG) + && 0 == strcmp(dcb->user, user) + && 0 == strcmp(dcb->protoname, protocol)) + { + if (NULL == previous) + { + server->persistent = dcb->nextpersistent; + } + else + { + previous->nextpersistent = dcb->nextpersistent; + } + free(dcb->user); + dcb->user = NULL; + spinlock_release(&server->persistlock); + atomic_add(&server->stats.n_persistent, -1); + atomic_add(&server->stats.n_current, 1); + return dcb; + } + else + { + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [server_get_persistent] Rejected dcb " + "%p from pool, user %s looking for %s, protocol %s " + "looking for %s, hung flag %s, error handle called %s.", + pthread_self(), + dcb, + dcb->user ? dcb->user : "NULL", + user, + dcb->protoname ? dcb->protoname : "NULL", + protocol, + (dcb->flags & DCBF_HUNG) ? "true" : "false", + dcb-> dcb_errhandle_called ? "true" : "false"))); + } + previous = dcb; + dcb = dcb->nextpersistent; + } + spinlock_release(&server->persistlock); + } + return NULL; +} + /** * Set a unique name for the server * @@ -204,6 +280,8 @@ printServer(SERVER *server) printf("\tPort: %d\n", server->port); printf("\tTotal connections: %d\n", server->stats.n_connections); printf("\tCurrent connections: %d\n", server->stats.n_current); + printf("\tPersistent connections: %d\n", server->stats.n_persistent); + printf("\tPersistent actual max: %d\n", server->persistmax); } /** @@ -215,14 +293,14 @@ printServer(SERVER *server) void printAllServers() { -SERVER *ptr; +SERVER *server; spinlock_acquire(&server_spin); - ptr = allServers; - while (ptr) + server = allServers; + while (server) { - printServer(ptr); - ptr = ptr->next; + printServer(server); + server = server->next; } spinlock_release(&server_spin); } @@ -236,60 +314,73 @@ SERVER *ptr; void dprintAllServers(DCB *dcb) { -SERVER *ptr; +SERVER *server; char *stat; spinlock_acquire(&server_spin); - ptr = allServers; - while (ptr) + server = allServers; + while (server) { - dcb_printf(dcb, "Server %p (%s)\n", ptr, ptr->unique_name); + dcb_printf(dcb, "Server %p (%s)\n", server, server->unique_name); dcb_printf(dcb, "\tServer: %s\n", - ptr->name); - stat = server_status(ptr); + server->name); + stat = server_status(server); dcb_printf(dcb, "\tStatus: %s\n", stat); free(stat); dcb_printf(dcb, "\tProtocol: %s\n", - ptr->protocol); + server->protocol); dcb_printf(dcb, "\tPort: %d\n", - ptr->port); - if (ptr->server_string) + server->port); + if (server->server_string) dcb_printf(dcb, "\tServer Version:\t\t\t%s\n", - ptr->server_string); + server->server_string); dcb_printf(dcb, "\tNode Id: %d\n", - ptr->node_id); + server->node_id); dcb_printf(dcb, "\tMaster Id: %d\n", - ptr->master_id); - if (ptr->slaves) { + server->master_id); + if (server->slaves) { int i; dcb_printf(dcb, "\tSlave Ids: "); - for (i = 0; ptr->slaves[i]; i++) + for (i = 0; server->slaves[i]; i++) { if (i == 0) - dcb_printf(dcb, "%li", ptr->slaves[i]); + dcb_printf(dcb, "%li", server->slaves[i]); else - dcb_printf(dcb, ", %li ", ptr->slaves[i]); + dcb_printf(dcb, ", %li ", server->slaves[i]); } dcb_printf(dcb, "\n"); } dcb_printf(dcb, "\tRepl Depth: %d\n", - ptr->depth); - if (SERVER_IS_SLAVE(ptr) || SERVER_IS_RELAY_SERVER(ptr)) { - if (ptr->rlag >= 0) { - dcb_printf(dcb, "\tSlave delay:\t\t%d\n", ptr->rlag); + server->depth); + if (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) { + if (server->rlag >= 0) { + dcb_printf(dcb, "\tSlave delay:\t\t%d\n", server->rlag); } } - if (ptr->node_ts > 0) { - dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", ptr->node_ts); + if (server->node_ts > 0) { + dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", server->node_ts); } dcb_printf(dcb, "\tNumber of connections: %d\n", - ptr->stats.n_connections); + server->stats.n_connections); dcb_printf(dcb, "\tCurrent no. of conns: %d\n", - ptr->stats.n_current); + server->stats.n_current); dcb_printf(dcb, "\tCurrent no. of operations: %d\n", - ptr->stats.n_current_ops); - ptr = ptr->next; + server->stats.n_current_ops); + if (server->persistpoolmax) + { + dcb_printf(dcb, "\tPersistent pool size: %d\n", + server->stats.n_persistent); + dcb_printf(dcb, "\tPersistent measured pool size: %d\n", + dcb_persistent_clean_count(server->persistent, false)); + dcb_printf(dcb, "\tPersistent max size achieved: %d\n", + server->persistmax); + dcb_printf(dcb, "\tPersistent pool size limit: %d\n", + server->persistpoolmax); + dcb_printf(dcb, "\tPersistent max time (secs): %d\n", + server->persistmaxtime); + } + server = server->next; } spinlock_release(&server_spin); } @@ -303,74 +394,74 @@ char *stat; void dprintAllServersJson(DCB *dcb) { -SERVER *ptr; +SERVER *server; char *stat; int len = 0; int el = 1; spinlock_acquire(&server_spin); - ptr = allServers; - while (ptr) + server = allServers; + while (server) { - ptr = ptr->next; + server = server->next; len++; } - ptr = allServers; + server = allServers; dcb_printf(dcb, "[\n"); - while (ptr) + while (server) { dcb_printf(dcb, " {\n \"server\": \"%s\",\n", - ptr->name); - stat = server_status(ptr); + server->name); + stat = server_status(server); dcb_printf(dcb, " \"status\": \"%s\",\n", stat); free(stat); dcb_printf(dcb, " \"protocol\": \"%s\",\n", - ptr->protocol); + server->protocol); dcb_printf(dcb, " \"port\": \"%d\",\n", - ptr->port); - if (ptr->server_string) + server->port); + if (server->server_string) dcb_printf(dcb, " \"version\": \"%s\",\n", - ptr->server_string); + server->server_string); dcb_printf(dcb, " \"nodeId\": \"%d\",\n", - ptr->node_id); + server->node_id); dcb_printf(dcb, " \"masterId\": \"%d\",\n", - ptr->master_id); - if (ptr->slaves) { + server->master_id); + if (server->slaves) { int i; dcb_printf(dcb, " \"slaveIds\": [ "); - for (i = 0; ptr->slaves[i]; i++) + for (i = 0; server->slaves[i]; i++) { if (i == 0) - dcb_printf(dcb, "%li", ptr->slaves[i]); + dcb_printf(dcb, "%li", server->slaves[i]); else - dcb_printf(dcb, ", %li ", ptr->slaves[i]); + dcb_printf(dcb, ", %li ", server->slaves[i]); } dcb_printf(dcb, "],\n"); } dcb_printf(dcb, " \"replDepth\": \"%d\",\n", - ptr->depth); - if (SERVER_IS_SLAVE(ptr) || SERVER_IS_RELAY_SERVER(ptr)) { - if (ptr->rlag >= 0) { - dcb_printf(dcb, " \"slaveDelay\": \"%d\",\n", ptr->rlag); + server->depth); + if (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) { + if (server->rlag >= 0) { + dcb_printf(dcb, " \"slaveDelay\": \"%d\",\n", server->rlag); } } - if (ptr->node_ts > 0) { - dcb_printf(dcb, " \"lastReplHeartbeat\": \"%lu\",\n", ptr->node_ts); + if (server->node_ts > 0) { + dcb_printf(dcb, " \"lastReplHeartbeat\": \"%lu\",\n", server->node_ts); } dcb_printf(dcb, " \"totalConnections\": \"%d\",\n", - ptr->stats.n_connections); + server->stats.n_connections); dcb_printf(dcb, " \"currentConnections\": \"%d\",\n", - ptr->stats.n_current); + server->stats.n_current); dcb_printf(dcb, " \"currentOps\": \"%d\"\n", - ptr->stats.n_current_ops); + server->stats.n_current_ops); if (el < len) { dcb_printf(dcb, " },\n"); } else { dcb_printf(dcb, " }\n"); } - ptr = ptr->next; + server = server->next; el++; } dcb_printf(dcb, "]\n"); @@ -440,6 +531,57 @@ SERVER_PARAM *param; dcb_printf(dcb, "\tCurrent no. of conns: %d\n", server->stats.n_current); dcb_printf(dcb, "\tCurrent no. of operations: %d\n", server->stats.n_current_ops); + if (server->persistpoolmax) + { + dcb_printf(dcb, "\tPersistent pool size: %d\n", + server->stats.n_persistent); + dcb_printf(dcb, "\tPersistent measured pool size: %d\n", + dcb_persistent_clean_count(server->persistent, false)); + dcb_printf(dcb, "\tPersistent actual size max: %d\n", + server->persistmax); + dcb_printf(dcb, "\tPersistent pool size limit: %d\n", + server->persistpoolmax); + dcb_printf(dcb, "\tPersistent max time (secs): %d\n", + server->persistmaxtime); + } +} + +/** + * Display an entry from the spinlock statistics data + * + * @param dcb The DCB to print to + * @param desc Description of the statistic + * @param value The statistic value + */ +static void +spin_reporter(void *dcb, char *desc, int value) +{ + dcb_printf((DCB *)dcb, "\t\t%-40s %d\n", desc, value); +} + +/** + * Diagnostic to print all DCBs in persistent pool for a server + * + * @param pdcb DCB to print results to + * @param server SERVER for which DCBs are to be printed + */ +void +dprintPersistentDCBs(DCB *pdcb, SERVER *server) +{ +DCB *dcb; + + spinlock_acquire(&server->persistlock); +#if SPINLOCK_PROFILE + dcb_printf(pdcb, "DCB List Spinlock Statistics:\n"); + spinlock_stats(&server->persistlock, spin_reporter, pdcb); +#endif + dcb = server->persistent; + while (dcb) + { + dprintOneDCB(pdcb, dcb); + dcb = dcb->nextpersistent; + } + spinlock_release(&server->persistlock); } /** @@ -449,12 +591,12 @@ SERVER_PARAM *param; void dListServers(DCB *dcb) { -SERVER *ptr; +SERVER *server; char *stat; spinlock_acquire(&server_spin); - ptr = allServers; - if (ptr) + server = allServers; + if (server) { dcb_printf(dcb, "Servers.\n"); dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); @@ -462,15 +604,15 @@ char *stat; "Server", "Address", "Status"); dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); } - while (ptr) + while (server) { - stat = server_status(ptr); + stat = server_status(server); dcb_printf(dcb, "%-18s | %-15s | %5d | %11d | %s\n", - ptr->unique_name, ptr->name, - ptr->port, - ptr->stats.n_current, stat); + server->unique_name, server->name, + server->port, + server->stats.n_current, stat); free(stat); - ptr = ptr->next; + server = server->next; } if (allServers) dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n"); @@ -489,7 +631,7 @@ server_status(SERVER *server) { char *status = NULL; - if ((status = (char *)malloc(256)) == NULL) + if (NULL == server || (status = (char *)malloc(256)) == NULL) return NULL; status[0] = 0; if (server->status & SERVER_MAINT) @@ -641,7 +783,7 @@ SERVER_PARAM *param; } /** - * Retreive a parameter value from a server + * Retrieve a parameter value from a server * * @param server The server we are looking for a parameter of * @param name The name of the parameter we require @@ -675,16 +817,16 @@ int *rowno = (int *)data; int i = 0;; char *stat, buf[20]; RESULT_ROW *row; -SERVER *ptr; +SERVER *server; spinlock_acquire(&server_spin); - ptr = allServers; - while (i < *rowno && ptr) + server = allServers; + while (i < *rowno && server) { i++; - ptr = ptr->next; + server = server->next; } - if (ptr == NULL) + if (server == NULL) { spinlock_release(&server_spin); free(data); @@ -692,13 +834,13 @@ SERVER *ptr; } (*rowno)++; row = resultset_make_row(set); - resultset_row_set(row, 0, ptr->unique_name); - resultset_row_set(row, 1, ptr->name); - sprintf(buf, "%d", ptr->port); + resultset_row_set(row, 0, server->unique_name); + resultset_row_set(row, 1, server->name); + sprintf(buf, "%d", server->port); resultset_row_set(row, 2, buf); - sprintf(buf, "%d", ptr->stats.n_current); + sprintf(buf, "%d", server->stats.n_current); resultset_row_set(row, 3, buf); - stat = server_status(ptr); + stat = server_status(server); resultset_row_set(row, 4, stat); free(stat); spinlock_release(&server_spin); diff --git a/server/core/service.c b/server/core/service.c index 4584ded24..224a602b3 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -36,7 +36,8 @@ * 06/02/15 Mark Riddoch Added caching of authentication data * 18/02/15 Mark Riddoch Added result set management * 03/03/15 Massimiliano Pinto Added config_enable_feedback_task() call in serviceStartAll - * + * 19/06/15 Martin Brampton More meaningful names for temp variables + * @endverbatim */ #include @@ -61,12 +62,17 @@ #include #include #include +#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; extern __thread log_info_t tls_log_info; +static RSA *rsa_512 = NULL; +static RSA *rsa_1024 = NULL; + /** To be used with configuration type checks */ typedef struct typelib_st { int tl_nelems; @@ -112,7 +118,7 @@ SERVICE *service; return NULL; if ((service->router = load_module(router, MODULE_ROUTER)) == NULL) { - char* home = get_maxscale_home(); + char* home = get_libdir(); char* ldpath = getenv("LD_LIBRARY_PATH"); LOGIF(LE, (skygw_log_write_flush( @@ -120,12 +126,13 @@ SERVICE *service; "Error : Unable to load %s module \"%s\".\n\t\t\t" " Ensure that lib%s.so exists in one of the " "following directories :\n\t\t\t " - "- %s/modules\n\t\t\t - %s", + "- %s\n%s%s", MODULE_ROUTER, router, router, home, - ldpath))); + ldpath?"\t\t\t - ":"", + ldpath?ldpath:""))); free(service); return NULL; } @@ -133,7 +140,14 @@ SERVICE *service; service->routerModule = strdup(router); service->users_from_all = false; service->resources = NULL; - + service->ssl_mode = SSL_DISABLED; + service->ssl_init_done = false; + service->ssl_ca_cert = NULL; + service->ssl_cert = NULL; + service->ssl_key = NULL; + service->ssl_cert_verify_depth = DEFAULT_SSL_CERT_VERIFY_DEPTH; + /** Support the highest possible SSL/TLS methods available as the default */ + service->ssl_method_type = SERVICE_SSL_TLS_MAX; if (service->name == NULL || service->routerModule == NULL) { if (service->name) @@ -163,19 +177,19 @@ SERVICE *service; int service_isvalid(SERVICE *service) { -SERVICE *ptr; +SERVICE *checkservice; int rval = 0; spinlock_acquire(&service_spin); - ptr = allServices; - while (ptr) + checkservice = allServices; + while (checkservice) { - if (ptr == service) + if (checkservice == service) { rval = 1; break; } - ptr = ptr->next; + checkservice = checkservice->next; } spinlock_release(&service_spin); return rval; @@ -229,11 +243,7 @@ GWPROTOCOL *funcs; { /* Try loading authentication data from file cache */ char *ptr, path[4097]; - strcpy(path, "/usr/local/mariadb-maxscale"); - if ((ptr = getenv("MAXSCALE_HOME")) != NULL) - { - strncpy(path, ptr, 4096); - } + strcpy(path, get_cachedir()); strncat(path, "/", 4096); strncat(path, service->name, 4096); strncat(path, "/.cache/dbusers", 4096); @@ -249,7 +259,7 @@ GWPROTOCOL *funcs; { hashtable_free(service->users->data); free(service->users); - dcb_free(port->listener); + dcb_close(port->listener); port->listener = NULL; goto retblock; } @@ -257,15 +267,11 @@ GWPROTOCOL *funcs; else { /* Save authentication data to file cache */ - char *ptr, path[4097]; + char *ptr, path[PATH_MAX + 1]; int mkdir_rval = 0; - strcpy(path, "/usr/local/mariadb-maxscale"); - if ((ptr = getenv("MAXSCALE_HOME")) != NULL) - { - strncpy(path, ptr, 4096); - } + strncpy(path, get_cachedir(),PATH_MAX); strncat(path, "/", 4096); - strncat(path, service->name, 4096); + strncat(path, service->name, PATH_MAX); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); @@ -273,14 +279,17 @@ GWPROTOCOL *funcs; if(mkdir_rval) { - skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", - path, - errno, - strerror(errno)); + if(errno != EEXIST) + { + skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", + path, + errno, + strerror(errno)); + } mkdir_rval = 0; } - strncat(path, "/.cache", 4096); + strncat(path, "/.cache", PATH_MAX); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); @@ -288,13 +297,16 @@ GWPROTOCOL *funcs; if(mkdir_rval) { - skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", - path, - errno, - strerror(errno)); + if(errno != EEXIST) + { + skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", + path, + errno, + strerror(errno)); + } mkdir_rval = 0; } - strncat(path, "/dbusers", 4096); + strncat(path, "/dbusers", PATH_MAX); dbusers_save(service->users, path); } if (loaded == 0) @@ -318,7 +330,7 @@ GWPROTOCOL *funcs; "Loaded %d MySQL Users for service [%s].", loaded, service->name))); } - } + } else { if (service->users == NULL) { @@ -330,12 +342,8 @@ GWPROTOCOL *funcs; if ((funcs=(GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { - if (service->users->data) - { - hashtable_free(service->users->data); - } - free(service->users); - dcb_free(port->listener); + users_free(service->users); + dcb_close(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -369,11 +377,7 @@ GWPROTOCOL *funcs; "Error : Failed to create session to service %s.", service->name))); - if (service->users->data) - { - hashtable_free(service->users->data); - } - free(service->users); + users_free(service->users); dcb_close(port->listener); port->listener = NULL; goto retblock; @@ -387,11 +391,7 @@ GWPROTOCOL *funcs; port->port, port->protocol, service->name))); - if (service->users->data) - { - hashtable_free(service->users->data); - } - free(service->users); + users_free(service->users); dcb_close(port->listener); port->listener = NULL; } @@ -417,6 +417,17 @@ serviceStart(SERVICE *service) SERV_PROTOCOL *port; int listeners = 0; +if(service->ssl_mode != SSL_DISABLED) +{ + if(serviceInitSSL(service) != 0) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "%s: SSL initialization failed. Service not started.", + service->name))); + service->state = SERVICE_STATE_FAILED; + return 0; + } +} if ((service->router_instance = service->router->createInstance(service, service->routerOptions)) == NULL) { @@ -518,11 +529,16 @@ int listeners = 0; port = service->ports; while (port) { - poll_remove_dcb(port->listener); - port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; - listeners++; - - port = port->next; + if(port->listener && + port->listener->session->state == SESSION_STATE_LISTENER) + { + if(poll_remove_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; + listeners++; + } + } + port = port->next; } service->state = SERVICE_STATE_STOPPED; @@ -546,13 +562,18 @@ int listeners = 0; port = service->ports; while (port) { - if (poll_add_dcb(port->listener) == 0) { - port->listener->session->state = SESSION_STATE_LISTENER; - listeners++; - } - port = port->next; + if(port->listener && + port->listener->session->state == SESSION_STATE_LISTENER_STOPPED) + { + if(poll_add_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER; + listeners++; + } + } + port = port->next; } - + service->state = SERVICE_STATE_STARTED; return listeners; } @@ -860,6 +881,99 @@ serviceOptimizeWildcard(SERVICE *service, int action) return 1; } +/** + * Set the locations of the server's SSL certificate, server's private key and the CA + * certificate which both the client and the server should trust. + * @param service Service to configure + * @param cert SSL certificate + * @param key SSL private key + * @param ca_cert SSL CA certificate + */ +void +serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert) +{ + if(service->ssl_cert) + free(service->ssl_cert); + service->ssl_cert = strdup(cert); + + if(service->ssl_key) + free(service->ssl_key); + service->ssl_key = strdup(key); + + if(service->ssl_ca_cert) + free(service->ssl_ca_cert); + service->ssl_ca_cert = strdup(ca_cert); +} + +/** + * Set the maximum SSL/TLS version the service will support + * @param service Service to configure + * @param version SSL/TLS version string + * @return 0 on success, -1 on invalid version string + */ +int +serviceSetSSLVersion(SERVICE *service, char* version) +{ + if(strcasecmp(version,"SSLV3") == 0) + service->ssl_method_type = SERVICE_SSLV3; + else if(strcasecmp(version,"TLSV10") == 0) + service->ssl_method_type = SERVICE_TLS10; +#ifdef OPENSSL_1_0 + else if(strcasecmp(version,"TLSV11") == 0) + service->ssl_method_type = SERVICE_TLS11; + else if(strcasecmp(version,"TLSV12") == 0) + service->ssl_method_type = SERVICE_TLS12; +#endif + else if(strcasecmp(version,"MAX") == 0) + service->ssl_method_type = SERVICE_SSL_TLS_MAX; + else return -1; + return 0; +} + +/** + * Set the service's SSL certificate verification depth. Depth of 0 means the peer + * certificate, 1 is the CA and 2 is a higher CA and so on. + * @param service Service to configure + * @param depth Certificate verification depth + * @return 0 on success, -1 on incorrect depth value + */ +int serviceSetSSLVerifyDepth(SERVICE* service, int depth) +{ + if(depth < 0) + return -1; + + service->ssl_cert_verify_depth = depth; + return 0; +} + +/** + * Enable or disable the service SSL capability of a service. + * The SSL mode string passed as a parameter should be one of required, enabled + * or disabled. Required requires all connections to use SSL encryption, enabled + * allows both SSL and non-SSL connections and disabled does not use SSL encryption. + * If the service SSL mode is set to enabled, then the client will decide whether + * SSL encryption is used. + * @param service Service to configure + * @param action Mode string. One of required, enabled or disabled. + * @return 0 on success, -1 on error + */ +int +serviceSetSSL(SERVICE *service, char* action) +{ + int rval = 0; + + if(strcasecmp(action,"required") == 0) + service->ssl_mode = SSL_REQUIRED; + else if(strcasecmp(action,"enabled") == 0) + service->ssl_mode = SSL_ENABLED; + else if(strcasecmp(action,"disabled") == 0) + service->ssl_mode = SSL_DISABLED; + else + rval = -1; + + return rval; +} + /** * Whether to strip escape characters from the name of the database the client * is connecting to. @@ -1023,6 +1137,8 @@ int i; printf("\tUsers data: %p\n", (void *)service->users); printf("\tTotal connections: %d\n", service->stats.n_sessions); printf("\tCurrently connected: %d\n", service->stats.n_current); + printf("\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled": + (service->ssl_mode == SSL_ENABLED ? "Enabled":"Required")); } /** @@ -1132,6 +1248,8 @@ int i; service->stats.n_sessions); dcb_printf(dcb, "\tCurrently connected: %d\n", service->stats.n_current); + dcb_printf(dcb,"\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled": + (service->ssl_mode == SSL_ENABLED ? "Enabled":"Required")); } /** @@ -1142,11 +1260,11 @@ int i; void dListServices(DCB *dcb) { -SERVICE *ptr; +SERVICE *service; spinlock_acquire(&service_spin); - ptr = allServices; - if (ptr) + service = allServices; + if (service) { dcb_printf(dcb, "Services.\n"); dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); @@ -1154,13 +1272,13 @@ SERVICE *ptr; "Service Name", "Router Module"); dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); } - while (ptr) + while (service) { - ss_dassert(ptr->stats.n_current >= 0); + ss_dassert(service->stats.n_current >= 0); dcb_printf(dcb, "%-25s | %-20s | %6d | %5d\n", - ptr->name, ptr->routerModule, - ptr->stats.n_current, ptr->stats.n_sessions); - ptr = ptr->next; + service->name, service->routerModule, + service->stats.n_current, service->stats.n_sessions); + service = service->next; } if (allServices) dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n\n"); @@ -1175,12 +1293,12 @@ SERVICE *ptr; void dListListeners(DCB *dcb) { -SERVICE *ptr; +SERVICE *service; SERV_PROTOCOL *lptr; spinlock_acquire(&service_spin); - ptr = allServices; - if (ptr) + service = allServices; + if (service) { dcb_printf(dcb, "Listeners.\n"); dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n"); @@ -1188,13 +1306,13 @@ SERV_PROTOCOL *lptr; "Service Name", "Protocol Module", "Address"); dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n"); } - while (ptr) + while (service) { - lptr = ptr->ports; + lptr = service->ports; while (lptr) { dcb_printf(dcb, "%-20s | %-18s | %-15s | %5d | %s\n", - ptr->name, lptr->protocol, + service->name, lptr->protocol, (lptr && lptr->address) ? lptr->address : "*", lptr->port, (!lptr->listener || @@ -1205,7 +1323,7 @@ SERV_PROTOCOL *lptr; lptr = lptr->next; } - ptr = ptr->next; + service = service->next; } if (allServices) dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n\n"); @@ -1260,7 +1378,14 @@ void *router_obj; } } - +/** + * Refresh the database users for the service + * This function replaces the MySQL users used by the service with the latest + * version found on the backend servers. There is a limit on how often the users + * can be reloaded and if this limit is exceeded, the reload will fail. + * @param service Service to reload + * @return 0 on success and 1 on error + */ int service_refresh_users(SERVICE *service) { int ret = 1; /* check for another running getUsers request */ @@ -1613,15 +1738,15 @@ void service_shutdown() int serviceSessionCountAll() { -SERVICE *ptr; +SERVICE *service; int rval = 0; spinlock_acquire(&service_spin); - ptr = allServices; - while (ptr) + service = allServices; + while (service) { - rval += ptr->stats.n_current; - ptr = ptr->next; + rval += service->stats.n_current; + service = service->next; } spinlock_release(&service_spin); return rval; @@ -1642,16 +1767,16 @@ int *rowno = (int *)data; int i = 0;; char buf[20]; RESULT_ROW *row; -SERVICE *ptr; +SERVICE *service; SERV_PROTOCOL *lptr = NULL; spinlock_acquire(&service_spin); - ptr = allServices; - if (ptr) - lptr = ptr->ports; - while (i < *rowno && ptr) + service = allServices; + if (service) + lptr = service->ports; + while (i < *rowno && service) { - lptr = ptr->ports; + lptr = service->ports; while (i < *rowno && lptr) { if ((lptr = lptr->next) != NULL) @@ -1659,8 +1784,8 @@ SERV_PROTOCOL *lptr = NULL; } if (i < *rowno) { - ptr = ptr->next; - if (ptr && (lptr = ptr->ports) != NULL) + service = service->next; + if (service && (lptr = service->ports) != NULL) i++; } } @@ -1672,7 +1797,7 @@ SERV_PROTOCOL *lptr = NULL; } (*rowno)++; row = resultset_make_row(set); - resultset_row_set(row, 0, ptr->name); + resultset_row_set(row, 0, service->name); resultset_row_set(row, 1, lptr->protocol); resultset_row_set(row, 2, (lptr && lptr->address) ? lptr->address : "*"); sprintf(buf, "%d", lptr->port); @@ -1727,16 +1852,16 @@ int *rowno = (int *)data; int i = 0;; char buf[20]; RESULT_ROW *row; -SERVICE *ptr; +SERVICE *service; spinlock_acquire(&service_spin); - ptr = allServices; - while (i < *rowno && ptr) + service = allServices; + while (i < *rowno && service) { i++; - ptr = ptr->next; + service = service->next; } - if (ptr == NULL) + if (service == NULL) { spinlock_release(&service_spin); free(data); @@ -1744,11 +1869,11 @@ SERVICE *ptr; } (*rowno)++; row = resultset_make_row(set); - resultset_row_set(row, 0, ptr->name); - resultset_row_set(row, 1, ptr->routerModule); - sprintf(buf, "%d", ptr->stats.n_current); + resultset_row_set(row, 0, service->name); + resultset_row_set(row, 1, service->routerModule); + sprintf(buf, "%d", service->stats.n_current); resultset_row_set(row, 2, buf); - sprintf(buf, "%d", ptr->stats.n_sessions); + sprintf(buf, "%d", service->stats.n_sessions); resultset_row_set(row, 3, buf); spinlock_release(&service_spin); return row; @@ -1780,3 +1905,140 @@ int *data; return set; } + + +/** + * The RSA ket generation callback function for OpenSSL. + * @param s SSL structure + * @param is_export Not used + * @param keylength Length of the key + * @return Pointer to RSA structure + */ + RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength) + { + RSA *rsa_tmp=NULL; + + switch (keylength) { + case 512: + if (rsa_512) + rsa_tmp = rsa_512; + else { /* generate on the fly, should not happen in this example */ + rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL); + rsa_512 = rsa_tmp; /* Remember for later reuse */ + } + break; + case 1024: + if (rsa_1024) + rsa_tmp=rsa_1024; + break; + default: + /* Generating a key on the fly is very costly, so use what is there */ + if (rsa_1024) + rsa_tmp=rsa_1024; + else + rsa_tmp=rsa_512; /* Use at least a shorter key */ + } + return(rsa_tmp); + } + + /** + * Initialize the servce's SSL context. This sets up the generated RSA + * encryption keys, chooses the server encryption level and configures the server + * certificate, private key and certificate authority file. + * @param service + * @return + */ +int serviceInitSSL(SERVICE* service) +{ + DH* dh; + RSA* rsa; + + if(!service->ssl_init_done) + { + switch(service->ssl_method_type) + { + case SERVICE_SSLV3: + service->method = (SSL_METHOD*)SSLv3_server_method(); + break; + case SERVICE_TLS10: + service->method = (SSL_METHOD*)TLSv1_server_method(); + break; +#ifdef OPENSSL_1_0 + case SERVICE_TLS11: + service->method = (SSL_METHOD*)TLSv1_1_server_method(); + break; + case SERVICE_TLS12: + service->method = (SSL_METHOD*)TLSv1_2_server_method(); + break; +#endif + /** Rest of these use the maximum available SSL/TLS methods */ + case SERVICE_SSL_MAX: + service->method = (SSL_METHOD*)SSLv23_server_method(); + break; + case SERVICE_TLS_MAX: + service->method = (SSL_METHOD*)SSLv23_server_method(); + break; + case SERVICE_SSL_TLS_MAX: + service->method = (SSL_METHOD*)SSLv23_server_method(); + break; + default: + service->method = (SSL_METHOD*)SSLv23_server_method(); + break; + } + + service->ctx = SSL_CTX_new(service->method); + + /** Enable all OpenSSL bug fixes */ + SSL_CTX_set_options(service->ctx,SSL_OP_ALL); + + /** Generate the 512-bit and 1024-bit RSA keys */ + if(rsa_512 == NULL) + { + rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL); + if (rsa_512 == NULL) + skygw_log_write(LE,"Error: 512-bit RSA key generation failed."); + } + if(rsa_1024 == NULL) + { + rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL); + if (rsa_1024 == NULL) + skygw_log_write(LE,"Error: 1024-bit RSA key generation failed."); + } + + if(rsa_512 != NULL && rsa_1024 != NULL) + SSL_CTX_set_tmp_rsa_callback(service->ctx,tmp_rsa_callback); + + /** Load the server sertificate */ + if (SSL_CTX_use_certificate_file(service->ctx, service->ssl_cert, SSL_FILETYPE_PEM) <= 0) { + skygw_log_write(LE,"Error: Failed to set server SSL certificate."); + return -1; + } + + /* Load the private-key corresponding to the server certificate */ + if (SSL_CTX_use_PrivateKey_file(service->ctx, service->ssl_key, SSL_FILETYPE_PEM) <= 0) { + skygw_log_write(LE,"Error: Failed to set server SSL key."); + return -1; + } + + /* Check if the server certificate and private-key matches */ + if (!SSL_CTX_check_private_key(service->ctx)) { + skygw_log_write(LE,"Error: Server SSL certificate and key do not match."); + return -1; + } + + + /* Load the RSA CA certificate into the SSL_CTX structure */ + if (!SSL_CTX_load_verify_locations(service->ctx, service->ssl_ca_cert, NULL)) { + skygw_log_write(LE,"Error: Failed to set Certificate Authority file."); + return -1; + } + + /* Set to require peer (client) certificate verification */ + SSL_CTX_set_verify(service->ctx,SSL_VERIFY_PEER,NULL); + + /* Set the verification depth */ + SSL_CTX_set_verify_depth(service->ctx,service->ssl_cert_verify_depth); + service->ssl_init_done = true; + } + return 0; +} diff --git a/server/core/session.c b/server/core/session.c index 8a34f9907..abc5d9b0d 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -962,7 +962,7 @@ void session_close_timeouts(void* data) ses->service->conn_timeout > 0 && hkheartbeat - ses->client->last_read > ses->service->conn_timeout * 10) { - ses->client->func.hangup(ses->client); + dcb_close(ses->client); } spinlock_acquire(&session_spin); diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 5c1f9a8ad..2d32d55b1 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -44,19 +44,4 @@ add_test(Internal-TestUsers test_users) add_test(Internal-TestAdminUsers test_adminusers) add_test(Internal-TestMemlog testmemlog) add_test(TestFeedback testfeedback) -set_tests_properties(Internal-TestMySQLUsers - Internal-TestHash - Internal-TestHint - Internal-TestSpinlock - Internal-TestFilter - Internal-TestBuffer - Internal-TestDCB - Internal-TestModutil - Internal-TestPoll - Internal-TestService - Internal-TestServer - Internal-TestUsers - Internal-TestAdminUsers - Internal-TestMemlog - TestFeedback PROPERTIES ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/) set_tests_properties(TestFeedback PROPERTIES TIMEOUT 30) diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c index ae52bd8b7..f9c34e500 100644 --- a/server/core/test/testadminusers.c +++ b/server/core/test/testadminusers.c @@ -30,7 +30,7 @@ #include #include #include - +#include #include @@ -40,7 +40,7 @@ * Test that the username password admin/mariadb is accepted if no users * have been created and that no other users are accepted * - * WARNING: $MAXSCALE_HOME/etc/passwd must be removed before this test is run + * WARNING: The passwd file must be removed before this test is run */ static int test1() @@ -269,9 +269,8 @@ int result = 0; char *home, buf[1024]; /* Unlink any existing password file before running this test */ - if ((home = getenv("MAXSCALE_HOME")) == NULL || strlen(home) >= 1024) - home = "/usr/local/mariadb-maxscale"; - sprintf(buf, "%s/etc/passwd", home); + + sprintf(buf, "%s/passwd", default_cachedir); if(!is_valid_posix_path(buf)) exit(1); if (strcmp(buf, "/etc/passwd") != 0) diff --git a/server/core/test/testdcb.c b/server/core/test/testdcb.c index 2703763f0..13462fc87 100644 --- a/server/core/test/testdcb.c +++ b/server/core/test/testdcb.c @@ -64,7 +64,7 @@ int buflen; ss_info_dassert(!dcb_isvalid(dcb), "Freed DCB must not be valid"); ss_dfprintf(stderr, "\t..done\nMake clone DCB a zombie"); clone->state = DCB_STATE_NOPOLLING; - dcb_add_to_zombieslist(clone); + dcb_close(clone); ss_info_dassert(dcb_get_zombies() == clone, "Clone DCB must be start of zombie list now"); ss_dfprintf(stderr, "\t..done\nProcess the zombies list"); dcb_process_zombies(0); diff --git a/server/core/test/testfeedback.c b/server/core/test/testfeedback.c index 6549305c3..3b281537e 100644 --- a/server/core/test/testfeedback.c +++ b/server/core/test/testfeedback.c @@ -73,17 +73,8 @@ int main(int argc, char** argv) char* cnf; hkinit(); - home = getenv("MAXSCALE_HOME"); - if(home == NULL) - { - FAILTEST("MAXSCALE_HOME was not defined."); - } - printf("Home: %s\n",home); - - cnf = malloc(strlen(home) + strlen("/etc/MaxScale.cnf") + 1); - strcpy(cnf,home); - strcat(cnf,"/etc/MaxScale.cnf"); + cnf = strdup("/etc/MaxScale.cnf"); printf("Config: %s\n",cnf); @@ -116,4 +107,4 @@ int main(int argc, char** argv) } mysql_library_end(); return 0; -} \ No newline at end of file +} diff --git a/server/core/users.c b/server/core/users.c index 086a6b81c..1ce4abefe 100644 --- a/server/core/users.c +++ b/server/core/users.c @@ -20,6 +20,7 @@ #include #include #include +#include /** * @file users.c User table maintenance routines @@ -59,12 +60,16 @@ users_alloc() USERS *rval; if ((rval = calloc(1, sizeof(USERS))) == NULL) - return NULL; + { + skygw_log_write(LE,"[%s:%d] Error: Memory allocation failed.",__FUNCTION__,__LINE__); + return NULL; + } if ((rval->data = hashtable_alloc(USERS_HASHTABLE_DEFAULT_SIZE, user_hash, strcmp)) == NULL) { - free(rval); - return NULL; + skygw_log_write(LE,"[%s:%d] Error: Memory allocation failed.",__FUNCTION__,__LINE__); + free(rval); + return NULL; } hashtable_memory_fns(rval->data, (HASHMEMORYFN)strdup, (HASHMEMORYFN)strdup, (HASHMEMORYFN)free, (HASHMEMORYFN)free); @@ -80,8 +85,15 @@ USERS *rval; void users_free(USERS *users) { + if(users == NULL) + { + skygw_log_write(LE,"[%s:%d] Error: NULL parameter.",__FUNCTION__,__LINE__); + return; + } + + if(users->data) hashtable_free(users->data); - free(users); + free(users); } /** diff --git a/server/include/atomic.h b/server/include/atomic.h index f139d15d5..18f03bb83 100644 --- a/server/include/atomic.h +++ b/server/include/atomic.h @@ -26,9 +26,14 @@ * * Date Who Description * 10/06/13 Mark Riddoch Initial implementation + * 23/06/15 Martin Brampton Alternative for C++ * * @endverbatim */ +#ifdef __cplusplus +extern "C" int atomic_add(int *variable, int value); +#else extern int atomic_add(int *variable, int value); #endif +#endif diff --git a/server/include/dbusers.h b/server/include/dbusers.h index e7b94a575..f0367a227 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -47,6 +47,7 @@ #define MYSQL_PASSWORD_LEN 41 #define MYSQL_HOST_MAXLEN 60 #define MYSQL_DATABASE_MAXLEN 128 +#define MYSQL_TABLE_MAXLEN 64 /** * MySQL user and host data structure diff --git a/server/include/dcb.h b/server/include/dcb.h index 7eedfbec3..7339286a1 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #define ERRHANDLE @@ -55,15 +58,16 @@ struct service; * 08/05/2014 Mark Riddoch Addition of writeq high and low watermarks * 27/08/2014 Mark Riddoch Addition of write event queuing * 23/09/2014 Mark Riddoch New poll processing queue + * 19/06/2015 Martin Brampton Provision of persistent connections * * @endverbatim */ struct dcb; - /** + /** * @verbatim - * The operations that can be performed on the descriptor + * The operations that can be performed on the descriptor * * read EPOLLIN handler for the socket * write MaxScale data write entry point @@ -77,7 +81,7 @@ struct dcb; * close MaxScale close entry point for the socket * listen Create a listener for the protocol * auth Authentication entry point - * session Session handling entry point + * session Session handling entry point * @endverbatim * * This forms the "module object" for protocol modules within the gateway. @@ -132,7 +136,7 @@ typedef struct { #define DCBFD_CLOSED -1 /** - * The statitics gathered on a descriptor control block + * The statistics gathered on a descriptor control block */ typedef struct dcbstats { int n_reads; /*< Number of reads on this descriptor */ @@ -221,12 +225,10 @@ typedef struct dcb_callback { * gateway may be selected to execute the required actions when a network event occurs. */ typedef struct dcb { -#if defined(SS_DEBUG) - skygw_chk_t dcb_chk_top; -#endif + skygw_chk_t dcb_chk_top; bool dcb_errhandle_called; /*< this can be called only once */ dcb_role_t dcb_role; - SPINLOCK dcb_initlock; + SPINLOCK dcb_initlock; DCBEVENTQ evq; /**< The event queue for this DCB */ int fd; /**< The descriptor */ dcb_state_t state; /**< Current descriptor state */ @@ -234,6 +236,7 @@ typedef struct dcb { char *remote; /**< Address of remote end */ char *user; /**< User name for connection */ struct sockaddr_in ipv4; /**< remote end IPv4 address */ + char *protoname; /**< Name of the protocol */ void *protocol; /**< The protocol specific state */ struct session *session; /**< The owning session */ GWPROTOCOL func; /**< The functions for this descriptor */ @@ -247,8 +250,10 @@ typedef struct dcb { SPINLOCK authlock; /**< Generic Authorization spinlock */ DCBSTATS stats; /**< DCB related statistics */ - unsigned int dcb_server_status; /*< the server role indicator from SERVER */ + unsigned int dcb_server_status; /*< the server role indicator from SERVER */ struct dcb *next; /**< Next DCB in the chain of allocated DCB's */ + struct dcb *nextpersistent; /**< Next DCB in the persistent pool for SERVER */ + time_t persistentstart; /**< Time when DCB placed in persistent pool */ struct service *service; /**< The related service */ void *data; /**< Specific client data */ DCBMM memdata; /**< The data related to DCB memory management */ @@ -261,14 +266,13 @@ typedef struct dcb { SPINLOCK polloutlock; int polloutbusy; int writecheck; - unsigned long last_read; /*< Last time the DCB received data */ + unsigned long last_read; /*< Last time the DCB received data */ unsigned int high_water; /**< High water mark */ unsigned int low_water; /**< Low water mark */ struct server *server; /**< The associated backend server */ -#if defined(SS_DEBUG) - int dcb_port; /**< port of target server */ - skygw_chk_t dcb_chk_tail; -#endif + SSL* ssl; /*< SSL struct for connection */ + int dcb_port; /**< port of target server */ + skygw_chk_t dcb_chk_tail; } DCB; /** @@ -311,13 +315,14 @@ DCB *dcb_alloc(dcb_role_t); void dcb_free(DCB *); DCB *dcb_connect(struct server *, struct session *, const char *); DCB *dcb_clone(DCB *); -int dcb_read(DCB *, GWBUF **); +int dcb_read(DCB *, GWBUF **, int); int dcb_drain_writeq(DCB *); void dcb_close(DCB *); DCB *dcb_process_zombies(int); /* Process Zombies except the one behind the pointer */ void printAllDCBs(); /* Debug to print all DCB in the system */ void printDCB(DCB *); /* Debug print routine */ void dprintAllDCBs(DCB *); /* Debug to print all DCB in the system */ +void dprintOneDCB(DCB *, DCB *); /* Debug to print one DCB */ void dprintDCB(DCB *, DCB *); /* Debug to print a DCB in the system */ void dListDCBs(DCB *); /* List all DCBs in the system */ void dListClients(DCB *); /* List al the client DCBs */ @@ -325,19 +330,25 @@ const char *gw_dcb_state2string(int); /* DCB state to string */ void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */ int dcb_isclient(DCB *); /* the DCB is the client of the session */ void dcb_hashtable_stats(DCB *, void *); /**< Print statisitics */ -void dcb_add_to_zombieslist(DCB* dcb); int dcb_add_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), void *); int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), void *); int dcb_isvalid(DCB *); /* Check the DCB is in the linked list */ int dcb_count_by_usage(DCB_USAGE); /* Return counts of DCBs */ +int dcb_persistent_clean_count(DCB *, bool); /* Clean persistent and return count */ -bool dcb_set_state(DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state); void dcb_call_foreach (struct server* server, DCB_REASON reason); size_t dcb_get_session_id(DCB* dcb); bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs); - +char *dcb_role_name(DCB *); /* Return the name of a role */ +int dcb_create_SSL(DCB* dcb); +int dcb_accept_SSL(DCB* dcb); +int dcb_connect_SSL(DCB* dcb); +int gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes); +int dcb_write_SSL(DCB *dcb,GWBUF *queue); +int dcb_read_SSL(DCB *dcb,GWBUF **head); +int dcb_drain_writeq_SSL(DCB *dcb); /** diff --git a/server/include/externcmd.h b/server/include/externcmd.h new file mode 100644 index 000000000..1ab44f908 --- /dev/null +++ b/server/include/externcmd.h @@ -0,0 +1,20 @@ +#ifndef _EXTERN_CMD_HG +#define _EXTERN_CMD_HG + +#include +#include +#include +#include +#include +#define MAXSCALE_EXTCMD_ARG_MAX 256 + +typedef struct extern_cmd_t{ + char* parameters[MAXSCALE_EXTCMD_ARG_MAX]; /*< Command arguments */ + int n_exec; /*< Number of times executed */ + pid_t child; /*< PID of the child process */ +}EXTERNCMD; + +EXTERNCMD* externcmd_allocate(char* argstr); +void externcmd_free(EXTERNCMD* cmd); +int externcmd_execute(EXTERNCMD* cmd); +#endif diff --git a/server/include/gw.h b/server/include/gw.h index 9c8507210..b51bb0e2a 100644 --- a/server/include/gw.h +++ b/server/include/gw.h @@ -1,3 +1,25 @@ +#ifndef _GW_HG +#define _GW_HG + +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + + #include #include #include @@ -16,8 +38,8 @@ #include #include #include - #include +#include #define EXIT_FAILURE 1 @@ -65,3 +87,5 @@ int gw_write(DCB *dcb, const void *buf, size_t nbytes); int gw_getsockerrno(int fd); int parse_bindconfig(char *, unsigned short, struct sockaddr_in *); int setipaddress(struct in_addr *, char *); +char* get_libdir(); +#endif diff --git a/server/include/gwdirs.h.in b/server/include/gwdirs.h.in new file mode 100644 index 000000000..d9c17e152 --- /dev/null +++ b/server/include/gwdirs.h.in @@ -0,0 +1,48 @@ +#ifndef _GW_DIRS_HG +#define _GW_DIRS_HG + +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2015 + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#include +#include +/** Default file locations, configured by CMake */ +static const char* default_cnf_fname = "maxscale.cnf"; +static const char* default_configdir = "/etc/"; +static const char* default_piddir = "@MAXSCALE_VARDIR@/run/maxscale/"; /*< This should be changed to just /run eventually, + * the /var/run folder is an old standard and the newe FSH 3.0 + * uses /run for PID files.*/ +static const char* default_logdir = "@MAXSCALE_VARDIR@/log/maxscale/"; +static const char* default_datadir = "@MAXSCALE_VARDIR@/lib/maxscale/"; +static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@"; +static const char* default_cachedir = "@MAXSCALE_VARDIR@/cache/maxscale/"; +static const char* default_langdir = "@MAXSCALE_VARDIR@/lib/maxscale/"; + +static char* configdir = NULL; +static char* logdir = NULL; +static char* libdir = NULL; +static char* cachedir = NULL; +static char* maxscaledatadir = NULL; +static char* langdir = NULL; +static char* piddir = NULL; +char* get_libdir(); +char* get_datadir(); +char* get_cachedir(); +#endif diff --git a/server/include/modules.h b/server/include/modules.h index 51e10b29d..96d322402 100644 --- a/server/include/modules.h +++ b/server/include/modules.h @@ -68,7 +68,6 @@ extern void unload_all_modules(); extern void printModules(); extern void dprintAllModules(DCB *); extern RESULTSET *moduleGetList(); -extern char *get_maxscale_home(void); extern void module_feedback_send(void*); extern void moduleShowFeedbackReport(DCB *dcb); diff --git a/server/include/monitor.h b/server/include/monitor.h index e114f3424..442efb27f 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -17,6 +17,7 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ +#include #include #include #include @@ -69,19 +70,14 @@ typedef struct { void *(*startMonitor)(void *, void*); void (*stopMonitor)(void *); - void (*registerServer)(void *, SERVER *); - void (*unregisterServer)(void *, SERVER *); - void (*defaultUser)(void *, char *, char *); void (*diagnostics)(DCB *, void *); - void (*setInterval)(void *, size_t); - void (*setNetworkTimeout)(void *, int, int); } MONITOR_OBJECT; /** * The monitor API version number. Any change to the monitor module API * must change these versions usign the rules defined in modinfo.h */ -#define MONITOR_VERSION {2, 0, 0} +#define MONITOR_VERSION {3, 0, 0} /** Monitor's poll frequency */ #define MON_BASE_INTERVAL_MS 100 @@ -112,12 +108,47 @@ typedef enum #define DEFAULT_READ_TIMEOUT 1 #define DEFAULT_WRITE_TIMEOUT 2 + +#define MONITOR_RUNNING 1 +#define MONITOR_STOPPING 2 +#define MONITOR_STOPPED 3 + +#define MONITOR_INTERVAL 10000 // in milliseconds +#define MONITOR_DEFAULT_ID 1UL // unsigned long value +#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server + + +/** + * The linked list of servers that are being monitored by the monitor module. + */ +typedef struct monitor_servers { + SERVER *server; /**< The server being monitored */ + MYSQL *con; /**< The MySQL connection */ + bool log_version_err; + int mon_err_count; + unsigned int mon_prev_status; + unsigned int pending_status; /**< Pending Status flag bitmap */ + struct monitor_servers + *next; /**< The next server in the list */ +} MONITOR_SERVERS; + /** * Representation of the running monitor. */ typedef struct monitor { char *name; /**< The name of the monitor module */ + char* user; /*< Monitor username */ + char* password; /*< Monitor password */ + SPINLOCK lock; + MONITOR_SERVERS* databases; /*< List of databases the monitor monitors */ monitor_state_t state; /**< The state of the monitor */ + int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ + int read_timeout; /**< Timeout in seconds to read from the server. + * There are retries and the total effective timeout value is three times the option value. + */ + int write_timeout; /**< Timeout in seconds for each attempt to write to the server. + * There are retries and the total effective timeout value is two times the option value. + */ MONITOR_OBJECT *module; /**< The "monitor object" */ void *handle; /**< Handle returned from startMonitor */ size_t interval; /**< The monitor interval */ diff --git a/server/include/secrets.h b/server/include/secrets.h index 505e79154..cb2912827 100644 --- a/server/include/secrets.h +++ b/server/include/secrets.h @@ -53,5 +53,5 @@ typedef struct maxkeys { extern int secrets_writeKeys(char *filename); extern char *decryptPassword(char *); -extern char *encryptPassword(char *); +extern char *encryptPassword(char*,char *); #endif diff --git a/server/include/server.h b/server/include/server.h index 28011927f..b3d43c98f 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -44,6 +44,7 @@ * 27/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS * 19/02/15 Mark Riddoch Addition of serverGetList * 01/06/15 Massimiliano Pinto Addition of server_update_address/port + * 19/06/15 Martin Brampton Extra fields for persistent connections, CHK_SERVER * * @endverbatim */ @@ -67,6 +68,7 @@ typedef struct { int n_connections; /**< Number of connections */ int n_current; /**< Current connections */ int n_current_ops; /**< Current active operations */ + int n_persistent; /**< Current persistent pool */ } SERVER_STATS; /** @@ -76,6 +78,9 @@ typedef struct { * between the gateway and the server. */ typedef struct server { +#if defined(SS_DEBUG) + skygw_chk_t server_chk_top; +#endif char *unique_name; /**< Unique name for the server */ char *name; /**< Server name/IP address*/ unsigned short port; /**< Port to listen on */ @@ -95,6 +100,14 @@ typedef struct server { 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 */ + DCB *persistent; /**< List of unused persistent connections to the server */ + SPINLOCK persistlock; /**< Lock for adjusting the persistent connections list */ + long persistpoolmax; /**< Maximum size of persistent connections pool */ + long persistmaxtime; /**< Maximum number of seconds connection can live */ + int persistmax; /**< Maximum pool size actually achieved since startup */ +#if defined(SS_DEBUG) + skygw_chk_t server_chk_tail; +#endif } SERVER; /** @@ -181,6 +194,7 @@ extern void printAllServers(); extern void dprintAllServers(DCB *); extern void dprintAllServersJson(DCB *); extern void dprintServer(DCB *, SERVER *); +extern void dprintPersistentDCBs(DCB *, SERVER *); extern void dListServers(DCB *); extern char *server_status(SERVER *); extern void server_set_status(SERVER *, int); @@ -190,6 +204,7 @@ extern void serverAddParameter(SERVER *, char *, char *); extern char *serverGetParameter(SERVER *, char *); extern void server_update(SERVER *, char *, char *, char *); extern void server_set_unique_name(SERVER *, char *); +extern DCB *server_get_persistent(SERVER *, char *, const char *); extern void server_update_address(SERVER *, char *); extern void server_update_port(SERVER *, unsigned short); extern RESULTSET *serverGetList(); diff --git a/server/include/service.h b/server/include/service.h index f26c99806..b64270b9e 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -26,7 +26,10 @@ #include #include #include - +#include +#include +#include +#include /** * @file service.h * @@ -105,6 +108,26 @@ typedef struct server_ref_t{ SERVER* server; }SERVER_REF; +typedef enum { + SSL_DISABLED, + SSL_ENABLED, + SSL_REQUIRED +} ssl_mode_t; + +enum{ + SERVICE_SSLV3, + SERVICE_TLS10, +#ifdef OPENSSL_1_0 + SERVICE_TLS11, + SERVICE_TLS12, +#endif + SERVICE_SSL_MAX, + SERVICE_TLS_MAX, + SERVICE_SSL_TLS_MAX +}; + +#define DEFAULT_SSL_CERT_VERIFY_DEPTH 100 /*< The default certificate verification depth */ + /** * Defines a service within the gateway. * @@ -149,8 +172,19 @@ typedef struct service { FILTER_DEF **filters; /**< Ordered list of filters */ int n_filters; /**< Number of filters */ int conn_timeout; /*< Session timeout in seconds */ + ssl_mode_t ssl_mode; /*< one of DISABLED, ENABLED or REQUIRED */ char *weightby; struct service *next; /**< The next service in the linked list */ + SSL_CTX *ctx; + SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods + * see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */ + int ssl_cert_verify_depth; /*< SSL certificate verification depth */ + int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */ + char* ssl_cert; /*< SSL certificate */ + char* ssl_key; /*< SSL private key */ + char* ssl_ca_cert; /*< SSL CA certificate */ + bool ssl_init_done; /*< If SSL has already been initialized for this service */ + } SERVICE; typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOST} count_spec_t; @@ -178,6 +212,11 @@ extern int serviceRestart(SERVICE *); extern int serviceSetUser(SERVICE *, char *, char *); extern int serviceGetUser(SERVICE *, char **, char **); extern void serviceSetFilters(SERVICE *, char *); +extern int serviceSetSSL(SERVICE *service, char* action); +extern int serviceInitSSL(SERVICE* service); +extern int serviceSetSSLVersion(SERVICE *service, char* version); +extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth); +extern void serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert); extern int serviceEnableRootUser(SERVICE *, int ); extern int serviceSetTimeout(SERVICE *, int ); extern void serviceWeightBy(SERVICE *, char *); diff --git a/server/include/session.h b/server/include/session.h index 908277e41..7a9381507 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -174,6 +174,7 @@ void dprintSession(struct dcb *, SESSION *); void dListSessions(struct dcb *); char *session_state(int); bool session_link_dcb(SESSION *, struct dcb *); +int session_unlink_dcb(SESSION*, DCB*); SESSION* get_session_by_router_ses(void* rses); void session_enable_log(SESSION* ses, logfile_id_t id); void session_disable_log(SESSION* ses, logfile_id_t id); diff --git a/server/inih/CMakeLists.txt b/server/inih/CMakeLists.txt index 24393e05d..95a17866e 100644 --- a/server/inih/CMakeLists.txt +++ b/server/inih/CMakeLists.txt @@ -1 +1,2 @@ -add_library(inih ini.c) \ No newline at end of file +add_definitions(-DINI_MAX_LINE=1024) +add_library(inih ini.c) diff --git a/server/MaxScale_BinlogServer_template.cnf b/server/maxscale_binlogserver_template.cnf similarity index 95% rename from server/MaxScale_BinlogServer_template.cnf rename to server/maxscale_binlogserver_template.cnf index dddd74163..f9cc112aa 100644 --- a/server/MaxScale_BinlogServer_template.cnf +++ b/server/maxscale_binlogserver_template.cnf @@ -1,5 +1,5 @@ # -# Example MaxScale.cnf for the Binlog Server. +# Example maxscale.cnf for the Binlog Server. # # @@ -37,14 +37,13 @@ threads=6 # The MaxScale Binlog Server Service. # # The name of this service will be used as the directory name -# in $MAXSCALE_HOME where the binlogs will be saved. +# in the cache directory where the binlogs will be saved. # If this name is changed, it must be changed in the listener # configuration below. [Binlog_Service] # type must be service # router must be binlogrouter -# (corresponding to the so file in $MAXSCALE_HOME/modules). type=service router=binlogrouter diff --git a/server/MaxScale_template.cnf b/server/maxscale_template.cnf similarity index 90% rename from server/MaxScale_template.cnf rename to server/maxscale_template.cnf index feec5695d..0928a750d 100644 --- a/server/MaxScale_template.cnf +++ b/server/maxscale_template.cnf @@ -128,6 +128,10 @@ module=regexfilter match=fetch replace=select +[hint] +type=filter +module=hintfilter + ## A series of service definition # @@ -218,11 +222,21 @@ router=readwritesplit servers=server1,server2,server3 user=myuser passwd=mypwd -#use_sql_variables_in= -#max_slave_connections=100% +max_slave_connections=100% +#use_sql_variables_in=master #max_slave_replication_lag=21 -#router_options=slave_selection_criteria= -#filters=fetch|qla +#filters=hint|fetch|qla +#router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +# Uncomment this to disable the saving of session modifying comments. Some scripting +# languages use connection pooling and will use the same session. MaxScale sees them +# as the same session and stores them for the slave recovery process. +#router_options=disable_sescmd_history=true,disable_slave_recovery=true + +# This will allow the master server to be used for read queries. By default +# MaxScale will only use the master for write queries. +#router_options=master_accept_reads=true + [Debug Interface] type=service @@ -265,27 +279,26 @@ service=Read Connection Router protocol=MySQLClient address=192.168.100.102 port=4008 -#socket=/tmp/readconn.sock +socket=/var/lib/maxscale/readconn.sock [RW Split Listener] type=listener service=RW Split Router protocol=MySQLClient port=4006 -#socket=/tmp/rwsplit.sock +#socket=/var/lib/maxscale/rwsplit.sock [Debug Listener] type=listener service=Debug Interface protocol=telnetd -#address=127.0.0.1 +address=127.0.0.1 port=4442 [CLI Listener] type=listener service=CLI protocol=maxscaled -#address=localhost port=6603 ## Definition of the servers @@ -314,18 +327,18 @@ port=6603 [server1] type=server -address=192.168.100.101 -port=3000 +address=127.0.0.1 +port=3306 protocol=MySQLBackend [server2] type=server -address=192.168.100.102 -port=3000 +address=127.0.0.1 +port=3306 protocol=MySQLBackend [server3] type=server -address=192.168.100.103 -port=3000 +address=127.0.0.1 +port=3306 protocol=MySQLBackend diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index 0af1ea566..6bf6a5daa 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -3,48 +3,48 @@ if(BUILD_RABBITMQ) include_directories(${RABBITMQ_HEADERS}) add_library(mqfilter SHARED mqfilter.c) target_link_libraries(mqfilter query_classifier log_manager utils ${RABBITMQ_LIBRARIES}) - install(TARGETS mqfilter DESTINATION modules) + install(TARGETS mqfilter DESTINATION ${MAXSCALE_LIBDIR}) endif() add_library(regexfilter SHARED regexfilter.c) target_link_libraries(regexfilter log_manager utils) -install(TARGETS regexfilter DESTINATION modules) +install(TARGETS regexfilter DESTINATION ${MAXSCALE_LIBDIR}) add_library(testfilter SHARED testfilter.c) target_link_libraries(testfilter log_manager utils) -install(TARGETS testfilter DESTINATION modules) +install(TARGETS testfilter DESTINATION ${MAXSCALE_LIBDIR}) add_library(qlafilter SHARED qlafilter.c) target_link_libraries(qlafilter log_manager utils) -install(TARGETS qlafilter DESTINATION modules) +install(TARGETS qlafilter DESTINATION ${MAXSCALE_LIBDIR}) add_library(tee SHARED tee.c) target_link_libraries(tee log_manager utils) -install(TARGETS tee DESTINATION modules) +install(TARGETS tee DESTINATION ${MAXSCALE_LIBDIR}) add_library(topfilter SHARED topfilter.c) target_link_libraries(topfilter log_manager utils) -install(TARGETS topfilter DESTINATION modules) +install(TARGETS topfilter DESTINATION ${MAXSCALE_LIBDIR}) add_library(dbfwfilter SHARED dbfwfilter.c) target_link_libraries(dbfwfilter log_manager utils query_classifier) -install(TARGETS dbfwfilter DESTINATION modules) +install(TARGETS dbfwfilter DESTINATION ${MAXSCALE_LIBDIR}) add_library(namedserverfilter SHARED namedserverfilter.c) target_link_libraries(namedserverfilter log_manager utils) -install(TARGETS namedserverfilter DESTINATION modules) +install(TARGETS namedserverfilter DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_SLAVELAG) add_library(slavelag SHARED slavelag.c) target_link_libraries(slavelag log_manager utils query_classifier) - install(TARGETS slavelag DESTINATION modules) + install(TARGETS slavelag DESTINATION ${MAXSCALE_LIBDIR}) endif() if(BUILD_TOOLS) add_executable(ruleparser dbfwfilter.c) target_compile_definitions(ruleparser PUBLIC "BUILD_RULE_PARSER") target_link_libraries(ruleparser ${EMBEDDED_LIB} log_manager utils query_classifier fullcore) - install(TARGETS ruleparser DESTINATION tools) + install(TARGETS ruleparser DESTINATION ${MAXSCALE_BINDIR}) endif() add_subdirectory(hint) diff --git a/server/modules/filter/dbfwfilter.c b/server/modules/filter/dbfwfilter.c index 2f1bb8ed9..e3850f841 100644 --- a/server/modules/filter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter.c @@ -1661,7 +1661,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue { char *ptr,*where,*msg = NULL; char emsg[512]; - int qlen; + unsigned char* memptr = (unsigned char*)queue->start; bool is_sql, is_real, matches; skygw_query_op_t optype = QUERY_OP_UNDEFINED; @@ -1683,7 +1683,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue } optype = query_classifier_get_operation(queue); is_real = skygw_is_real_query(queue); - qlen = gw_mysql_get_byte3(memptr) - 1; + } if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ @@ -2214,15 +2214,13 @@ int main(int argc, char** argv) return 1; } - if((home = getenv("MAXSCALE_HOME")) == NULL) + home = malloc(sizeof(char)*(PATH_MAX+1)); + if(getcwd(home,PATH_MAX) == NULL) { - home = malloc(sizeof(char)*(PATH_MAX+1)); - if(getcwd(home,PATH_MAX) == NULL) - { - free(home); - home = NULL; - } + free(home); + home = NULL; } + printf("Log files written to: %s\n",home?home:"/tpm"); int argc_ = 2; diff --git a/server/modules/filter/hint/CMakeLists.txt b/server/modules/filter/hint/CMakeLists.txt index f47cb637e..c2d5e9ea1 100644 --- a/server/modules/filter/hint/CMakeLists.txt +++ b/server/modules/filter/hint/CMakeLists.txt @@ -1,4 +1,4 @@ add_library(hintfilter SHARED hintfilter.c hintparser.c) -set_target_properties(hintfilter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib) +set_target_properties(hintfilter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${MAXSCALE_LIBDIR}) target_link_libraries(hintfilter ssl log_manager utils) -install(TARGETS hintfilter DESTINATION modules) \ No newline at end of file +install(TARGETS hintfilter DESTINATION ${MAXSCALE_LIBDIR}) diff --git a/server/modules/filter/namedserverfilter.c b/server/modules/filter/namedserverfilter.c index b7a5b91c1..822b5eaf2 100644 --- a/server/modules/filter/namedserverfilter.c +++ b/server/modules/filter/namedserverfilter.c @@ -35,7 +35,7 @@ extern __thread log_info_t tls_log_info; * that routes to a named server if a regular expression match is found. * @verbatim * - * A simple regular expression query rewrite filter. + * A simple regular expression based query routing filter. * Two parameters should be defined in the filter configuration * match= * server= diff --git a/server/modules/filter/qlafilter.c b/server/modules/filter/qlafilter.c index 5362cf61e..3c358cda1 100644 --- a/server/modules/filter/qlafilter.c +++ b/server/modules/filter/qlafilter.c @@ -50,6 +50,7 @@ #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -304,7 +305,9 @@ char *remote, *userName; sprintf(my_session->filename, "%s.%d", my_instance->filebase, my_instance->sessions); - my_instance->sessions++; + + // Multiple sessions can try to update my_instance->sessions simultaneously + atomic_add(&(my_instance->sessions), 1); if (my_session->active) { diff --git a/server/modules/filter/regexfilter.c b/server/modules/filter/regexfilter.c index 9c301870b..c7fb91879 100644 --- a/server/modules/filter/regexfilter.c +++ b/server/modules/filter/regexfilter.c @@ -23,7 +23,7 @@ #include #include #include - +#include #include "maxconfig.h" /** Defined in log_manager.cc */ @@ -97,6 +97,7 @@ typedef struct { */ typedef struct { DOWNSTREAM down; /* The downstream filter */ + SPINLOCK lock; int no_change; /* No. of unchanged requests */ int replacements; /* No. of changed requests */ int active; /* Is filter active */ @@ -356,13 +357,17 @@ char *sql, *newsql; { queue = modutil_replace_SQL(queue, newsql); queue = gwbuf_make_contiguous(queue); + spinlock_acquire(&my_session->lock); log_match(my_instance,my_instance->match,sql,newsql); + spinlock_release(&my_session->lock); free(newsql); my_session->replacements++; } else { + spinlock_acquire(&my_session->lock); log_nomatch(my_instance,my_instance->match,sql); + spinlock_release(&my_session->lock); my_session->no_change++; } free(sql); diff --git a/server/modules/filter/tee.c b/server/modules/filter/tee.c index b13b3dacd..1557166f9 100644 --- a/server/modules/filter/tee.c +++ b/server/modules/filter/tee.c @@ -1263,7 +1263,7 @@ int internal_route(DCB* dcb) GWBUF* clone_query(TEE_INSTANCE* my_instance, TEE_SESSION* my_session, GWBUF* buffer) { GWBUF* clone = NULL; - int length, residual = 0; + int residual = 0; char* ptr; if (my_session->branch_session && @@ -1291,7 +1291,6 @@ GWBUF* clone_query(TEE_INSTANCE* my_instance, TEE_SESSION* my_session, GWBUF* bu (my_instance->nomatch == NULL || regexec(&my_instance->nore,ptr,0,NULL, 0) != 0)) { - length = modutil_MySQL_query_len(buffer, &residual); clone = gwbuf_clone_all(buffer); my_session->residual = residual; } diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 50b5c4e0e..cd4f31b92 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -1,4 +1,3 @@ - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(harness_ui harness_ui.c harness_common.c) add_executable(harness harness_util.c harness_common.c) @@ -25,7 +24,3 @@ add_test(TestTeeRecursion ${CMAKE_CURRENT_SOURCE_DIR}/tee_recursion.sh ${TEST_PASSWORD} ${TEST_HOST} ${TEST_PORT}) - -set_tests_properties(TestHintfilter TestRegexfilter TestFwfilter1 TestFwfilter2 TestTeeRecursion -PROPERTIES -ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/) diff --git a/server/modules/filter/test/tee_recursion.sh b/server/modules/filter/test/tee_recursion.sh index 015bb9a8e..a37dae0c4 100755 --- a/server/modules/filter/test/tee_recursion.sh +++ b/server/modules/filter/test/tee_recursion.sh @@ -17,8 +17,8 @@ function execute_test() 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) + LAST_LOG=$(ls $BINDIR/ -1|grep error|sort|uniq|tail -n 1) + TEST_RESULT=$(cat $BINDIR/$LAST_LOG | grep -i recursive) if [[ "$TEST_RESULT" != "" ]] then return 0 @@ -49,8 +49,8 @@ USER=$3 PWD=$4 HOST=$5 PORT=$6 -CONF=$BINDIR/etc/MaxScale.cnf -OLDCONF=$BINDIR/etc/MaxScale.cnf.old +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 diff --git a/server/modules/filter/testfilter.c b/server/modules/filter/testfilter.c index 8f0c0f573..2f7fd7532 100644 --- a/server/modules/filter/testfilter.c +++ b/server/modules/filter/testfilter.c @@ -19,6 +19,7 @@ #include #include #include +#include /** * @file testfilter.c - a very simple test filter. @@ -145,7 +146,7 @@ TEST_SESSION *my_session; if ((my_session = calloc(1, sizeof(TEST_SESSION))) != NULL) { - my_instance->sessions++; + atomic_add(&my_instance->sessions,1); my_session->count = 0; } diff --git a/server/modules/filter/topfilter.c b/server/modules/filter/topfilter.c index 737dd790a..38eab05c6 100644 --- a/server/modules/filter/topfilter.c +++ b/server/modules/filter/topfilter.c @@ -47,6 +47,7 @@ #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -296,7 +297,7 @@ char *remote, *user; } sprintf(my_session->filename, "%s.%d", my_instance->filebase, my_instance->sessions); - my_instance->sessions++; + atomic_add(&my_instance->sessions,1); my_session->top = (TOPNQ **)calloc(my_instance->topN + 1, sizeof(TOPNQ *)); for (i = 0; i < my_instance->topN; i++) @@ -348,11 +349,14 @@ TOPN_SESSION *my_session = (TOPN_SESSION *)session; struct timeval diff; int i; FILE *fp; +int statements; gettimeofday(&my_session->disconnect, NULL); timersub((&my_session->disconnect), &(my_session->connect), &diff); if ((fp = fopen(my_session->filename, "w")) != NULL) { + statements = my_session->n_statements != 0?my_session->n_statements:1; + fprintf(fp, "Top %d longest running queries in session.\n", my_instance->topN); fprintf(fp, "==========================================\n\n"); @@ -378,14 +382,14 @@ FILE *fp; fprintf(fp, "Username %s\n", my_session->userName); fprintf(fp, "\nTotal of %d statements executed.\n", - my_session->n_statements); + statements); fprintf(fp, "Total statement execution time %5d.%d seconds\n", (int)my_session->total.tv_sec, (int)my_session->total.tv_usec / 1000); fprintf(fp, "Average statement execution time %9.3f seconds\n", (double)((my_session->total.tv_sec * 1000) + (my_session->total.tv_usec / 1000)) - / (1000 * my_session->n_statements)); + / (1000 * statements)); fprintf(fp, "Total connection time %5d.%d seconds\n", (int)diff.tv_sec, (int)diff.tv_usec / 1000); fclose(fp); diff --git a/server/modules/include/blr.h b/server/modules/include/blr.h index afd473dcb..93b7adea1 100644 --- a/server/modules/include/blr.h +++ b/server/modules/include/blr.h @@ -29,6 +29,8 @@ * 25/05/15 Massimiliano Pinto Added BLRM_SLAVE_STOPPED state * 05/06/15 Massimiliano Pinto Addition of m_errno, m_errmsg fields * 08/06/15 Massimiliano Pinto Modification of MYSQL_ERROR_CODE and MYSQL_ERROR_MSG + * 11/05/15 Massimiliano Pinto Added mariadb10_compat to master and slave structs + * 12/06/15 Massimiliano Pinto Added mariadb10 new events * 23/06/15 Massimiliano Pinto Addition of MASTER_SERVER_CFG struct * 24/06/15 Massimiliano Pinto Added BLRM_UNCONFIGURED state * @@ -37,7 +39,7 @@ #include #include #include - +#include #include #include @@ -49,7 +51,77 @@ #define BINLOG_EVENT_HDR_LEN 19 -/* How often to call the binlog status function (seconds) */ +/** + * Binlog event types + */ +#define START_EVENT_V3 0x01 +#define QUERY_EVENT 0x02 +#define STOP_EVENT 0x03 +#define ROTATE_EVENT 0x04 +#define INTVAR_EVENT 0x05 +#define LOAD_EVENT 0x06 +#define SLAVE_EVENT 0x07 +#define CREATE_FILE_EVENT 0x08 +#define APPEND_BLOCK_EVENT 0x09 +#define EXEC_LOAD_EVENT 0x0A +#define DELETE_FILE_EVENT 0x0B +#define NEW_LOAD_EVENT 0x0C +#define RAND_EVENT 0x0D +#define USER_VAR_EVENT 0x0E +#define FORMAT_DESCRIPTION_EVENT 0x0F +#define XID_EVENT 0x10 +#define BEGIN_LOAD_QUERY_EVENT 0x11 +#define EXECUTE_LOAD_QUERY_EVENT 0x12 +#define TABLE_MAP_EVENT 0x13 +#define WRITE_ROWS_EVENTv0 0x14 +#define UPDATE_ROWS_EVENTv0 0x15 +#define DELETE_ROWS_EVENTv0 0x16 +#define WRITE_ROWS_EVENTv1 0x17 +#define UPDATE_ROWS_EVENTv1 0x18 +#define DELETE_ROWS_EVENTv1 0x19 +#define INCIDENT_EVENT 0x1A +#define HEARTBEAT_EVENT 0x1B +#define IGNORABLE_EVENT 0x1C +#define ROWS_QUERY_EVENT 0x1D +#define WRITE_ROWS_EVENTv2 0x1E +#define UPDATE_ROWS_EVENTv2 0x1F +#define DELETE_ROWS_EVENTv2 0x20 +#define GTID_EVENT 0x21 +#define ANONYMOUS_GTID_EVENT 0x22 +#define PREVIOUS_GTIDS_EVENT 0x23 + +#define MAX_EVENT_TYPE 0x23 + +/* New MariaDB event numbers start from 0xa0 */ +#define MARIADB_NEW_EVENTS_BEGIN 0xa0 +#define MARIADB_ANNOTATE_ROWS_EVENT 0xa0 +/* New MariaDB 10 event numbers start from here */ +#define MARIADB10_BINLOG_CHECKPOINT_EVENT 0xa1 +#define MARIADB10_GTID_EVENT 0xa2 +#define MARIADB10_GTID_GTID_LIST_EVENT 0xa3 + +#define MAX_EVENT_TYPE_MARIADB10 0xa3 + +/* Maximum event type so far */ +#define MAX_EVENT_TYPE_END MAX_EVENT_TYPE_MARIADB10 + +/** + * Binlog event flags + */ +#define LOG_EVENT_BINLOG_IN_USE_F 0x0001 +#define LOG_EVENT_FORCED_ROTATE_F 0x0002 +#define LOG_EVENT_THREAD_SPECIFIC_F 0x0004 +#define LOG_EVENT_SUPPRESS_USE_F 0x0008 +#define LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F 0x0010 +#define LOG_EVENT_ARTIFICIAL_F 0x0020 +#define LOG_EVENT_RELAY_LOG_F 0x0040 +#define LOG_EVENT_IGNORABLE_F 0x0080 +#define LOG_EVENT_NO_FILTER_F 0x0100 +#define LOG_EVENT_MTS_ISOLATE_F 0x0200 + +/** + * How often to call the binlog status function (seconds) + */ #define BLR_STATS_FREQ 60 #define BLR_NSTATS_MINUTES 30 @@ -194,6 +266,7 @@ typedef struct router_slave { uint32_t lastEventTimestamp;/*< Last event timestamp sent */ SPINLOCK catch_lock; /*< Event catchup lock */ unsigned int cstate; /*< Catch up state */ + bool mariadb10_compat;/*< MariaDB 10.0 compatibility */ SPINLOCK rses_lock; /*< Protects rses_deleted */ pthread_t pthread; struct router_instance @@ -228,7 +301,7 @@ typedef struct { uint64_t n_fakeevents; /*< Fake events not written to disk */ uint64_t n_artificial; /*< Artificial events not written to disk */ int n_badcrc; /*< No. of bad CRC's from master */ - uint64_t events[0x24]; /*< Per event counters */ + uint64_t events[MAX_EVENT_TYPE_END + 1]; /*< Per event counters */ uint64_t lastsample; int minno; int minavgs[BLR_NSTATS_MINUTES]; @@ -252,6 +325,7 @@ typedef struct { GWBUF *selectvercom; /*< select @@version_comment */ GWBUF *selecthostname;/*< select @@hostname */ GWBUF *map; /*< select @@max_allowed_packet */ + GWBUF *mariadb10; /*< set @mariadb_slave_capability */ uint8_t *fde_event; /*< Format Description Event */ int fde_len; /*< Length of fde_event */ } MASTER_RESPONSES; @@ -260,33 +334,34 @@ typedef struct { * The per instance data for the router. */ typedef struct router_instance { - SERVICE *service; /*< Pointer to the service using this router */ - ROUTER_SLAVE *slaves; /*< Link list of all the slave connections */ - SPINLOCK lock; /*< Spinlock for the instance data */ - char *uuid; /*< UUID for the router to use w/master */ - int masterid; /*< Server ID of the master */ - int serverid; /*< Server ID to use with master */ - int initbinlog; /*< Initial binlog file number */ - char *user; /*< User name to use with master */ - char *password; /*< Password to use with master */ - char *fileroot; /*< Root of binlog filename */ - bool master_chksum;/*< Does the master provide checksums */ - char *master_uuid; /*< UUID of the master */ - DCB *master; /*< DCB for master connection */ - DCB *client; /*< DCB for dummy client */ - SESSION *session; /*< Fake session for master connection */ - unsigned int master_state; /*< State of the master FSM */ - uint8_t lastEventReceived; - uint32_t lastEventTimestamp; /*< Timestamp from last event */ - GWBUF *residual; /*< Any residual binlog event */ - MASTER_RESPONSES saved_master; /*< Saved master responses */ - char *binlogdir; /*< The directory with the binlog files */ - SPINLOCK binlog_lock; /*< Lock to control update of the binlog position */ - char binlog_name[BINLOG_FNAMELEN+1]; + SERVICE *service; /*< Pointer to the service using this router */ + ROUTER_SLAVE *slaves; /*< Link list of all the slave connections */ + SPINLOCK lock; /*< Spinlock for the instance data */ + char *uuid; /*< UUID for the router to use w/master */ + int masterid; /*< Server ID of the master */ + int serverid; /*< Server ID to use with master */ + int initbinlog; /*< Initial binlog file number */ + char *user; /*< User name to use with master */ + char *password; /*< Password to use with master */ + char *fileroot; /*< Root of binlog filename */ + bool master_chksum; /*< Does the master provide checksums */ + bool mariadb10_compat; /*< MariaDB 10.0 compatibility */ + char *master_uuid; /*< UUID of the master */ + DCB *master; /*< DCB for master connection */ + DCB *client; /*< DCB for dummy client */ + SESSION *session; /*< Fake session for master connection */ + unsigned int master_state; /*< State of the master FSM */ + uint8_t lastEventReceived; + uint32_t lastEventTimestamp; /*< Timestamp from last event */ + GWBUF *residual; /*< Any residual binlog event */ + MASTER_RESPONSES saved_master; /*< Saved master responses */ + char *binlogdir; /*< The directory with the binlog files */ + SPINLOCK binlog_lock; /*< Lock to control update of the binlog position */ + char binlog_name[BINLOG_FNAMELEN+1]; /*< Name of the current binlog file */ - uint64_t binlog_position; + uint64_t binlog_position; /*< Current binlog position */ - int binlog_fd; /*< File descriptor of the binlog + int binlog_fd; /*< File descriptor of the binlog * file being written */ uint64_t last_written; /*< Position of last event written */ @@ -308,8 +383,7 @@ typedef struct router_instance { int handling_threads; unsigned long m_errno; /*< master response mysql errno */ char *m_errmsg; /*< master response mysql error message */ - struct router_instance - *next; + struct router_instance *next; } ROUTER_INSTANCE; /** @@ -337,15 +411,16 @@ typedef struct router_instance { #define BLRM_REGISTER 0x0013 #define BLRM_BINLOGDUMP 0x0014 #define BLRM_SLAVE_STOPPED 0x0015 +#define BLRM_MARIADB10 0x0016 -#define BLRM_MAXSTATE 0x0015 +#define BLRM_MAXSTATE 0x0016 static char *blrm_states[] = { "Unconfigured", "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()", "select @@version_comment", "select @@hostname", - "select @@mx_allowed_packet", "Register slave", "Binlog Dump" , "Slave stopped"}; + "select @@max_allowed_packet", "Register slave", "Binlog Dump", "Slave stopped", "Set MariaDB slave capability" }; #define BLRS_CREATED 0x0000 #define BLRS_UNREGISTERED 0x0001 @@ -379,61 +454,6 @@ static char *blrs_states[] = { "Created", "Unregistered", "Registered", #define COM_REGISTER_SLAVE 0x15 #define COM_BINLOG_DUMP 0x12 -/** - * Binlog event types - */ -#define START_EVENT_V3 0x01 -#define QUERY_EVENT 0x02 -#define STOP_EVENT 0x03 -#define ROTATE_EVENT 0x04 -#define INTVAR_EVENT 0x05 -#define LOAD_EVENT 0x06 -#define SLAVE_EVENT 0x07 -#define CREATE_FILE_EVENT 0x08 -#define APPEND_BLOCK_EVENT 0x09 -#define EXEC_LOAD_EVENT 0x0A -#define DELETE_FILE_EVENT 0x0B -#define NEW_LOAD_EVENT 0x0C -#define RAND_EVENT 0x0D -#define USER_VAR_EVENT 0x0E -#define FORMAT_DESCRIPTION_EVENT 0x0F -#define XID_EVENT 0x10 -#define BEGIN_LOAD_QUERY_EVENT 0x11 -#define EXECUTE_LOAD_QUERY_EVENT 0x12 -#define TABLE_MAP_EVENT 0x13 -#define WRITE_ROWS_EVENTv0 0x14 -#define UPDATE_ROWS_EVENTv0 0x15 -#define DELETE_ROWS_EVENTv0 0x16 -#define WRITE_ROWS_EVENTv1 0x17 -#define UPDATE_ROWS_EVENTv1 0x18 -#define DELETE_ROWS_EVENTv1 0x19 -#define INCIDENT_EVENT 0x1A -#define HEARTBEAT_EVENT 0x1B -#define IGNORABLE_EVENT 0x1C -#define ROWS_QUERY_EVENT 0x1D -#define WRITE_ROWS_EVENTv2 0x1E -#define UPDATE_ROWS_EVENTv2 0x1F -#define DELETE_ROWS_EVENTv2 0x20 -#define GTID_EVENT 0x21 -#define ANONYMOUS_GTID_EVENT 0x22 -#define PREVIOUS_GTIDS_EVENT 0x23 - -#define MAX_EVENT_TYPE 0x23 - -/** - * Binlog event flags - */ -#define LOG_EVENT_BINLOG_IN_USE_F 0x0001 -#define LOG_EVENT_FORCED_ROTATE_F 0x0002 -#define LOG_EVENT_THREAD_SPECIFIC_F 0x0004 -#define LOG_EVENT_SUPPRESS_USE_F 0x0008 -#define LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F 0x0010 -#define LOG_EVENT_ARTIFICIAL_F 0x0020 -#define LOG_EVENT_RELAY_LOG_F 0x0040 -#define LOG_EVENT_IGNORABLE_F 0x0080 -#define LOG_EVENT_NO_FILTER_F 0x0100 -#define LOG_EVENT_MTS_ISOLATE_F 0x0200 - /** * Macros to extract common fields */ @@ -457,7 +477,7 @@ static char *blrs_states[] = { "Created", "Unregistered", "Registered", /* * Externals within the router */ -extern void blr_start_master(ROUTER_INSTANCE *); +extern void blr_start_master(void *); extern void blr_master_response(ROUTER_INSTANCE *, GWBUF *); extern void blr_master_reconnect(ROUTER_INSTANCE *); extern int blr_master_connected(ROUTER_INSTANCE *); @@ -479,4 +499,5 @@ extern int blr_statistics(ROUTER_INSTANCE *, ROUTER_SLAVE *, GWBUF *); extern int blr_ping(ROUTER_INSTANCE *, ROUTER_SLAVE *, GWBUF *); extern int blr_send_custom_error(DCB *, int, int, char *); extern int blr_file_next_exists(ROUTER_INSTANCE *, ROUTER_SLAVE *); +uint32_t extract_field(uint8_t *src, int bits); #endif diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 46bbe296c..bf6493e5d 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -54,7 +54,9 @@ #include #include #include - +#include +#include +#include #include #include #include @@ -89,6 +91,10 @@ #define COM_QUIT_PACKET_SIZE (4+1) struct dcb; +#define MYSQL_FAILED_AUTH 1 +#define MYSQL_FAILED_AUTH_DB 2 +#define MYSQL_FAILED_AUTH_SSL 3 + typedef enum { MYSQL_ALLOC, MYSQL_PENDING_CONNECT, @@ -97,6 +103,11 @@ typedef enum { MYSQL_AUTH_RECV, MYSQL_AUTH_FAILED, MYSQL_HANDSHAKE_FAILED, + MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */ + MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */ + MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */ + MYSQL_AUTH_SSL_HANDSHAKE_ONGOING, /*< SSL_accept has been called but the + * SSL handshake hasn't been completed */ MYSQL_IDLE } mysql_auth_state_t; @@ -123,7 +134,6 @@ typedef struct mysql_session { #endif } MYSQL_session; - /** Protocol packing macros. */ #define gw_mysql_set_byte2(__buffer, __int) do { \ (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ @@ -290,6 +300,7 @@ typedef struct { unsigned long tid; /*< MySQL Thread ID, in * handshake */ unsigned int charset; /*< MySQL character set at connect time */ + bool use_ssl; #if defined(SS_DEBUG) skygw_chk_t protocol_chk_tail; #endif @@ -309,7 +320,7 @@ typedef struct { #define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11) #define MYSQL_GET_NATTR(payload) ((int)payload[4]) -#endif /** _MYSQL_PROTOCOL_H */ + MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd); void mysql_protocol_done (DCB* dcb); @@ -405,4 +416,4 @@ void init_response_status ( int* npackets, ssize_t* nbytes); - +#endif /** _MYSQL_PROTOCOL_H */ \ No newline at end of file diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 9aa55016f..c78cde5b4 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -252,6 +252,7 @@ typedef struct rwsplit_config_st { int rw_max_sescmd_history_size; bool disable_sescmd_hist; bool disable_slave_recovery; + bool master_reads; /*< Use master for reads */ } rwsplit_config_t; diff --git a/server/modules/include/shardrouter.h b/server/modules/include/shardrouter.h index dfd83d520..63572671e 100644 --- a/server/modules/include/shardrouter.h +++ b/server/modules/include/shardrouter.h @@ -245,4 +245,6 @@ 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)); +bool subsvc_is_valid(SUBSERVICE*); + #endif /*< _SHARDROUTER_H */ diff --git a/server/modules/monitor/CMakeLists.txt b/server/modules/monitor/CMakeLists.txt index a99a66142..b32f46c30 100644 --- a/server/modules/monitor/CMakeLists.txt +++ b/server/modules/monitor/CMakeLists.txt @@ -1,16 +1,16 @@ -add_library(mysqlmon SHARED mysql_mon.c) +add_library(mysqlmon SHARED mysql_mon.c monitor_common.c) target_link_libraries(mysqlmon log_manager utils) -install(TARGETS mysqlmon DESTINATION modules) +install(TARGETS mysqlmon DESTINATION ${MAXSCALE_LIBDIR}) -add_library(galeramon SHARED galera_mon.c) +add_library(galeramon SHARED galeramon.c monitor_common.c) target_link_libraries(galeramon log_manager utils) -install(TARGETS galeramon DESTINATION modules) +install(TARGETS galeramon DESTINATION ${MAXSCALE_LIBDIR}) -add_library(ndbclustermon SHARED ndbcluster_mon.c) +add_library(ndbclustermon SHARED ndbclustermon.c monitor_common.c) target_link_libraries(ndbclustermon log_manager utils) -install(TARGETS ndbclustermon DESTINATION modules) +install(TARGETS ndbclustermon DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_MMMON) - add_library(mmmon SHARED mm_mon.c) + add_library(mmmon SHARED mmmon.c monitor_common.c) target_link_libraries(mmmon log_manager utils) - install(TARGETS mmmon DESTINATION modules) + install(TARGETS mmmon DESTINATION ${MAXSCALE_LIBDIR}) endif() diff --git a/server/modules/monitor/galera_mon.c b/server/modules/monitor/galeramon.c similarity index 60% rename from server/modules/monitor/galera_mon.c rename to server/modules/monitor/galeramon.c index e38b402d7..0e104a355 100644 --- a/server/modules/monitor/galera_mon.c +++ b/server/modules/monitor/galeramon.c @@ -34,24 +34,13 @@ * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write * 20/04/15 Guillaume Lefranc Added availableWhenDonor feature * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -60,7 +49,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.4.0"; +static char *version_str = "V2.0.0"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -71,27 +60,16 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUsers(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); -static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *); +static MONITOR_SERVERS *get_candidate_master(MONITOR*); static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *, MONITOR_SERVERS *, int); static void disableMasterFailback(void *, int); -static void setNetworkTimeout(void *arg, int type, int value); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); +bool isGaleraEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, - stopMonitor, - registerServer, - unregisterServer, - defaultUsers, - diagnostics, - setInterval, - setNetworkTimeout + stopMonitor, + diagnostics }; /** @@ -142,47 +120,94 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg != NULL) - { - handle = (MYSQL_MONITOR *)arg; - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->databases = NULL; - handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; - handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; - handle->disableMasterFailback = 0; - handle->availableWhenDonor = 0; - handle->disableMasterRoleSetting = 0; - handle->master = NULL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; - spinlock_init(&handle->lock); - } + MONITOR* mon = arg; + GALERA_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false,script_error = false; + if (handle != NULL) + { + handle->shutdown = 0; + } + else + { + if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + handle->disableMasterFailback = 0; + handle->availableWhenDonor = 0; + handle->disableMasterRoleSetting = 0; + handle->master = NULL; + handle->script = NULL; + handle->use_priority = false; + memset(handle->events,false,sizeof(handle->events)); + spinlock_init(&handle->lock); + } - while(params) + while(params) + { + if(!strcmp(params->name,"disable_master_failback")) + handle->disableMasterFailback = config_truth_value(params->value); + else if(!strcmp(params->name,"available_when_donor")) + handle->availableWhenDonor = config_truth_value(params->value); + else if(!strcmp(params->name,"disable_master_role_setting")) + handle->disableMasterRoleSetting = config_truth_value(params->value); + else if(!strcmp(params->name,"use_priority")) + handle->use_priority = config_truth_value(params->value); + else if(!strcmp(params->name,"script")) { - if(!strcmp(params->name,"disable_master_failback")) - handle->disableMasterFailback = config_truth_value(params->value); - else if(!strcmp(params->name,"available_when_donor")) - handle->availableWhenDonor = config_truth_value(params->value); - else if(!strcmp(params->name,"disable_master_role_setting")) - handle->disableMasterRoleSetting = config_truth_value(params->value); - params = params->next; - } + if(handle->script) + { + free(handle->script); + handle->script = NULL; + } - handle->tid = (THREAD)thread_start(monitorMain, handle); - return handle; + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + script_error = true; + if(access(params->value,F_OK) == 0) + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be found: %s", + params->value); + } + } + } + else if(!strcmp(params->name,"events")) + { + if(mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value) != 0) + script_error = true; + else + have_events = true; + } + params = params->next; + } + if(script_error) + { + skygw_log_write(LE,"Error: Errors were found in the script configuration parameters " + "for the monitor '%s'. The script will not be used.",mon->name); + free(handle->script); + handle->script = NULL; + } + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } + + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -193,82 +218,13 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; handle->shutdown = 1; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - /** * Diagnostic interface * @@ -278,7 +234,8 @@ MONITOR_SERVERS *ptr, *lptr; static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -295,16 +252,16 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); dcb_printf(dcb,"\tMaster Failback:\t%s\n", (handle->disableMasterFailback == 1) ? "off" : "on"); dcb_printf(dcb,"\tAvailable when Donor:\t%s\n", (handle->availableWhenDonor == 1) ? "on" : "off"); dcb_printf(dcb,"\tMaster Role Setting Disabled:\t%s\n", (handle->disableMasterRoleSetting == 1) ? "on" : "off"); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -315,27 +272,6 @@ char *sep; dcb_printf(dcb, "\n"); } -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUsers(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Monitor an individual server * @@ -343,14 +279,14 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) { + GALERA_MONITOR* handle = (GALERA_MONITOR*)mon->handle; MYSQL_ROW row; -MYSQL_RES *result; -int num_fields; +MYSQL_RES *result,*result2; int isjoined = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; +char *uname = mon->user; +char *passwd = mon->password; unsigned long int server_version = 0; char *server_string; @@ -372,18 +308,17 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; if(database->con) mysql_close(database->con); database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); if (mysql_real_connect(database->con, database->server->name, uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) @@ -428,9 +363,6 @@ char *server_string; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); - /* get server version from current server */ - server_version = mysql_get_server_version(database->con); - /* get server version string */ server_string = (char *)mysql_get_server_info(database->con); if (server_string) { @@ -443,7 +375,14 @@ char *server_string; if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'wsrep_local_state'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { if (strcmp(row[1], "4") == 0) @@ -452,14 +391,22 @@ char *server_string; /* Check if the node is a donor and is using xtrabackup, in this case it can stay alive */ else if (strcmp(row[1], "2") == 0 && handle->availableWhenDonor == 1) { if (mysql_query(database->con, "SHOW VARIABLES LIKE 'wsrep_sst_method'") == 0 - && (result = mysql_store_result(database->con)) != NULL) + && (result2 = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + mysql_free_result(result2); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW VARIABLES LIKE 'wsrep_sst_method'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } while ((row = mysql_fetch_row(result))) { if (strncmp(row[1], "xtrabackup", 10) == 0) isjoined = 1; } + mysql_free_result(result2); } } } @@ -471,7 +418,15 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long local_index = -1; - num_fields = mysql_num_fields(result); + + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'wsrep_local_index'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { local_index = strtol(row[1], NULL, 10); @@ -499,14 +454,20 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +GALERA_MONITOR *handle; MONITOR_SERVERS *ptr; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; -int master_stickiness = handle->disableMasterFailback; +int master_stickiness; int is_cluster=0; int log_no_members = 1; +monitor_event_t evtype; + spinlock_acquire(&mon->lock); + handle = (GALERA_MONITOR *)mon->handle; + spinlock_release(&mon->lock); + master_stickiness = handle->disableMasterFailback; if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( @@ -535,7 +496,7 @@ int log_no_members = 1; * round. */ - if (nrounds != 0 && ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= MON_BASE_INTERVAL_MS) + if (nrounds != 0 && ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; continue; @@ -546,14 +507,16 @@ int log_no_members = 1; /* reset cluster members counter */ is_cluster=0; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { - monitorDatabase(handle, ptr); + ptr->mon_prev_status = ptr->server->status; + + monitorDatabase(mon, ptr); /* clear bits for non member nodes */ - if ( ! SERVER_IN_MAINT(ptr->server) && (ptr->server->node_id < 0 || ! SERVER_IS_JOINED(ptr->server))) { + if ( ! SERVER_IN_MAINT(ptr->server) && (! SERVER_IS_JOINED(ptr->server))) { ptr->server->depth = -1; /* clear M/S status */ @@ -586,6 +549,7 @@ int log_no_members = 1; /** Increase this server'e error count */ dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); ptr->mon_err_count += 1; + } else { @@ -604,7 +568,7 @@ int log_no_members = 1; */ /* get the candidate master, following MIN(node_id) rule */ - candidate_master = get_candidate_master(handle->databases); + candidate_master = get_candidate_master(mon); /* Select the master, based on master_stickiness */ if (1 == handle->disableMasterRoleSetting) { @@ -614,7 +578,7 @@ int log_no_members = 1; handle->master = set_cluster_master(handle->master, candidate_master, master_stickiness); } - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (!SERVER_IS_JOINED(ptr->server) || SERVER_IN_MAINT(ptr->server)) { @@ -663,20 +627,32 @@ int log_no_members = 1; log_no_members = 1; } } - } -} -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); + + ptr = mon->databases; + + while(ptr) + { + + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr)) + { + evtype = mon_get_event_type(ptr); + if(isGaleraEvent(evtype)) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + } + ptr = ptr->next; + } + } } /** @@ -688,32 +664,45 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; * @param servers The monitored servers list * @return The candidate master on success, NULL on failure */ -static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *servers) { - MONITOR_SERVERS *ptr = servers; +static MONITOR_SERVERS *get_candidate_master(MONITOR* mon) { + MONITOR_SERVERS *moitor_servers = mon->databases; MONITOR_SERVERS *candidate_master = NULL; + GALERA_MONITOR* handle = mon->handle; long min_id = -1; - - /* set min_id to the lowest value of ptr->server->node_id */ - while(ptr) { - if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) { - ptr->server->depth = 0; - if ((ptr->server->node_id < min_id) && min_id >= 0) { - min_id = ptr->server->node_id; - candidate_master = ptr; - } else { - if (min_id < 0) { - min_id = ptr->server->node_id; - candidate_master = ptr; - } + int minval = INT_MAX; + int currval; + char* value; + /* set min_id to the lowest value of moitor_servers->server->node_id */ + while(moitor_servers) { + if (!SERVER_IN_MAINT(moitor_servers->server) && SERVER_IS_JOINED(moitor_servers->server)) { + + moitor_servers->server->depth = 0; + + if(handle->use_priority && (value = serverGetParameter(moitor_servers->server,"priority")) != NULL) + { + currval = atoi(value); + if(currval < minval && currval > 0) + { + minval = currval; + candidate_master = moitor_servers; + } + } + else if(moitor_servers->server->node_id >= 0 && + (!handle->use_priority || /** Server priority disabled*/ + candidate_master == NULL || /** No candidate chosen */ + serverGetParameter(candidate_master->server,"priority") == NULL)) /** Candidate has no priority */ + { + if (min_id < 0 || moitor_servers->server->node_id < min_id) { + min_id = moitor_servers->server->node_id; + candidate_master = moitor_servers; + } } } - - ptr = ptr->next; + moitor_servers = moitor_servers->next; } return candidate_master; } - /** * set the master server in the cluster * @@ -761,7 +750,7 @@ static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *current_master, MONI static void disableMasterFailback(void *arg, int disable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->disableMasterFailback, &disable, sizeof(int)); } @@ -777,115 +766,43 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void availableWhenDonor(void *arg, int disable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +GALERA_MONITOR *handle = (GALERA_MONITOR *)arg; memcpy(&handle->availableWhenDonor, &disable, sizeof(int)); } +static monitor_event_t galera_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + SYNCED_DOWN_EVENT, + SYNCED_UP_EVENT, + DONOR_DOWN_EVENT, + DONOR_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_SYNCED_EVENT, + LOST_DONOR_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_SYNCED_EVENT, + NEW_DONOR_EVENT, + MAX_MONITOR_EVENT +}; /** - * Set the timeouts to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set - */ -static void -setNetworkTimeout(void *arg, int type, int value) + * Check if the Galera monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isGaleraEvent(monitor_event_t event) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - -/** - * Check if current monitored server status has changed - * - * @param mon_srv The monitored server - * @return true if status has changed or false - */ -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Check if current monitored server has a loggable failure status - * - * @param mon_srv The monitored server - * @return true if failed status can be logged or false - */ -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); - - if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} + int i; + for(i = 0;galera_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == galera_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/galeramon.h b/server/modules/monitor/galeramon.h new file mode 100644 index 000000000..5a209f01e --- /dev/null +++ b/server/modules/monitor/galeramon.h @@ -0,0 +1,68 @@ +#ifndef _GALERAMON_H +#define _GALERAMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file galeramon.h - The Galera cluster monitor + * + * @verbatim + * Revision History + * + * Date Who Description + * 07/05/15 Markus Makela Initial Implementation of galeramon.h + * @endverbatim + */ + + +/** + * The handle for an instance of a MySQL Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + unsigned long id; /**< Monitor ID */ + int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */ + int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ + int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ + MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ + char* script; + bool use_priority; /*< Use server priorities */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ +} GALERA_MONITOR; + +#endif diff --git a/server/modules/monitor/mm_mon.c b/server/modules/monitor/mmmon.c similarity index 70% rename from server/modules/monitor/mm_mon.c rename to server/modules/monitor/mmmon.c index bddb82db9..40c2c11fb 100644 --- a/server/modules/monitor/mm_mon.c +++ b/server/modules/monitor/mmmon.c @@ -17,31 +17,21 @@ */ /** - * @file mysql_mon.c - A MySQL Multi Muster cluster monitor + * @file mm_mon.c - A Multi-Master Multi Muster cluster monitor * * @verbatim * Revision History * * Date Who Description * 08/09/14 Massimiliano Pinto Initial implementation + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -49,37 +39,26 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.0.1"; +static char *version_str = "V1.1.1"; MODULE_INFO info = { MODULE_API_MONITOR, MODULE_BETA_RELEASE, MONITOR_VERSION, - "A MySQL Multi Master monitor" + "A Multi-Master Multi Master monitor" }; static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUser(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); static void detectStaleMaster(void *, int); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); -static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *); -static void monitor_set_pending_status(MONITOR_SERVERS *, int); -static void monitor_clear_pending_status(MONITOR_SERVERS *, int); +static MONITOR_SERVERS *get_current_master(MONITOR *); +bool isMySQLEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, - registerServer, - unregisterServer, - defaultUser, - diagnostics, - setInterval + diagnostics }; /** @@ -102,7 +81,7 @@ ModuleInit() { LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, - "Initialise the MySQL Monitor module %s.", + "Initialise the Multi-Master Monitor module %s.", version_str))); } @@ -131,38 +110,84 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg) - { - handle = arg; /* Must be a restart */ - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->databases = NULL; - handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; - handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; - handle->replicationHeartbeat = 0; - handle->detectStaleMaster = 0; - handle->master = NULL; - spinlock_init(&handle->lock); - } + MONITOR* mon = (MONITOR*)arg; + MM_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false,script_error = false; - while(params) + if (handle) + { + handle->shutdown = 0; + } + else + { + if ((handle = (MM_MONITOR *)malloc(sizeof(MM_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + handle->master = NULL; + handle->script = NULL; + memset(handle->events,false,sizeof(handle->events)); + spinlock_init(&handle->lock); + } + + while(params) + { + if(!strcmp(params->name,"detect_stale_master")) { - if(!strcmp(params->name,"detect_stale_master")) - handle->detectStaleMaster = config_truth_value(params->value); - params = params->next; + handle->detectStaleMaster = config_truth_value(params->value); } - - handle->tid = (THREAD)thread_start(monitorMain, handle); - return handle; + else if(!strcmp(params->name,"script")) + { + if(handle->script) + { + free(handle->script); + } + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + script_error = true; + if(access(params->value,F_OK) == 0) + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be found: %s", + params->value); + } + handle->script = NULL; + } + } + else if(!strcmp(params->name,"events")) + { + if(mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value) != 0) + script_error = true; + else + have_events = true; + } + params = params->next; + } + if(script_error) + { + skygw_log_write(LE,"Error: Errors were found in the script configuration parameters " + "for the monitor '%s'. The script will not be used.",mon->name); + free(handle->script); + handle->script = NULL; + } + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -173,109 +198,12 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MM_MONITOR *handle = (MM_MONITOR *)arg; handle->shutdown = 1; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - db->mon_err_count = 0; - db->mon_prev_status = 0; - /* pending status is updated by monitorMain */ - db->pending_status = 0; - - spinlock_acquire(&handle->lock); - - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUser(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Daignostic interface * @@ -284,7 +212,8 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; */ static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MM_MONITOR *handle = (MM_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -301,11 +230,11 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -327,15 +256,15 @@ char *sep; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR* mon, MONITOR_SERVERS *database) { + MM_MONITOR *handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isslave = 0; int ismaster = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; +char *uname = mon->user; +char *passwd = mon->password; unsigned long int server_version = 0; char *server_string; @@ -352,19 +281,18 @@ char *server_string; if (SERVER_IN_MAINT(database->server)) return; - /** Store prevous status */ + /** Store previous status */ database->mon_prev_status = database->server->status; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; int read_timeout = 1; if(database->con) mysql_close(database->con); database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); if (mysql_real_connect(database->con, database->server->name, @@ -438,7 +366,15 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long server_id = -1; - num_fields = mysql_num_fields(result); + + if(mysql_field_count(database->con) != 1) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for 'SELECT @@server_id'. Expected 1 column." + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { server_id = strtol(row[0], NULL, 10); @@ -464,7 +400,16 @@ char *server_string; { int i = 0; long master_id = -1; - num_fields = mysql_num_fields(result); + + if(mysql_field_count(database->con) < 42) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: \"SHOW ALL SLAVES STATUS\" " + "returned less than the expected amount of columns. Expected 42 columns" + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -503,7 +448,31 @@ char *server_string; && (result = mysql_store_result(database->con)) != NULL) { long master_id = -1; - num_fields = mysql_num_fields(result); + + if(mysql_field_count(database->con) < 40) + { + mysql_free_result(result); + + if(server_version < 5*10000 + 5*100) + { + if(database->log_version_err) + { + skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " + " for versions less than 5.5 does not have master_server_id, " + "replication tree cannot be resolved for server %s." + " MySQL Version: %s",database->server->unique_name,version_str); + database->log_version_err = false; + } + } + else + { + skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " + "returned less than the expected amount of columns. Expected 40 columns." + " MySQL Version: %s",version_str); + } + return; + } + while ((row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ @@ -535,7 +504,14 @@ char *server_string; if (mysql_query(database->con, "SHOW GLOBAL VARIABLES LIKE 'read_only'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW GLOBAL VARIABLES LIKE 'read_only'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { if (strncasecmp(row[1], "OFF", 3) == 0) { @@ -585,12 +561,18 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MM_MONITOR *handle; MONITOR_SERVERS *ptr; -int detect_stale_master = handle->detectStaleMaster; +int detect_stale_master; MONITOR_SERVERS *root_master; size_t nrounds = 0; +spinlock_acquire(&mon->lock); +handle = (MM_MONITOR *)mon->handle; +spinlock_release(&mon->lock); +detect_stale_master = handle->detectStaleMaster; + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( @@ -620,7 +602,7 @@ size_t nrounds = 0; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; @@ -629,7 +611,7 @@ size_t nrounds = 0; nrounds += 1; /* start from the first server in the list */ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { @@ -637,7 +619,7 @@ size_t nrounds = 0; ptr->pending_status = ptr->server->status; /* monitor current node */ - monitorDatabase(handle, ptr); + monitorDatabase(mon, ptr); if (mon_status_changed(ptr)) { @@ -669,11 +651,11 @@ size_t nrounds = 0; } /* Get Master server pointer */ - root_master = get_current_master(handle); + root_master = get_current_master(mon); /* Update server status from monitor pending status on that server*/ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (! SERVER_IN_MAINT(ptr->server)) { @@ -690,21 +672,30 @@ size_t nrounds = 0; } ptr = ptr->next; } + + ptr = mon->databases; + monitor_event_t evtype; + while(ptr) + { + if(mon_status_changed(ptr)) + { + evtype = mon_get_event_type(ptr); + if(isMySQLEvent(evtype)) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + } + ptr = ptr->next; + } } } - -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} /** * Enable/Disable the MySQL Replication Stale Master dectection, allowing a previouvsly detected master to still act as a Master. @@ -717,70 +708,11 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; static void detectStaleMaster(void *arg, int enable) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MM_MONITOR *handle = (MM_MONITOR *)mon->handle; memcpy(&handle->detectStaleMaster, &enable, sizeof(int)); } -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); - - if (SERVER_IS_DOWN(mon_srv->server) && errcount%modval == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Set a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status |= bit; -} - -/** - * Clear a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status &= ~bit; -} - /******* * This function returns the master server * from a set of MySQL Multi Master monitored servers @@ -791,10 +723,11 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) * @return The server at root level with SERVER_MASTER bit */ -static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *handle) { +static MONITOR_SERVERS *get_current_master(MONITOR *mon) { + MM_MONITOR* handle = mon->handle; MONITOR_SERVERS *ptr; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { @@ -831,3 +764,32 @@ MONITOR_SERVERS *ptr; } } + +static monitor_event_t mysql_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + MAX_MONITOR_EVENT +}; +/** + * Check if the MM monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isMySQLEvent(monitor_event_t event) +{ + int i; + for(i = 0;mysql_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == mysql_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/mmmon.h b/server/modules/monitor/mmmon.h new file mode 100644 index 000000000..8a9edfb56 --- /dev/null +++ b/server/modules/monitor/mmmon.h @@ -0,0 +1,52 @@ +#ifndef _MMMON_H +#define _MMMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2015 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * The handle for an instance of a Multi-Master Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + unsigned long id; /**< Monitor ID */ + int detectStaleMaster; /**< Monitor flag for Stale Master detection */ + MONITOR_SERVERS *master; /**< Master server for Master/Slave replication */ + char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ +} MM_MONITOR; + +#endif diff --git a/server/modules/monitor/monitor_common.c b/server/modules/monitor/monitor_common.c new file mode 100644 index 000000000..19980c5dd --- /dev/null +++ b/server/modules/monitor/monitor_common.c @@ -0,0 +1,409 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2015 + */ + +#include + +monitor_event_t mon_name_to_event(char* tok); + +/** + * Set a pending status bit in the monitor server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status |= bit; +} + +/** + * Clear a pending status bit in the monitor server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status &= ~bit; +} + + + monitor_event_t mon_get_event_type(MONITOR_SERVERS* node) +{ + unsigned int prev = node->mon_prev_status; + + if((prev & (SERVER_MASTER|SERVER_RUNNING)) == (SERVER_MASTER|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return MASTER_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return MASTER_UP_EVENT; + } + if((prev & (SERVER_SLAVE|SERVER_RUNNING)) == (SERVER_SLAVE|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return SLAVE_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return SLAVE_UP_EVENT; + } + + /** Galera specific events */ + if((prev & (SERVER_JOINED|SERVER_RUNNING)) == (SERVER_JOINED|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return SYNCED_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_JOINED(node->server)) + { + return SYNCED_UP_EVENT; + } + + /** NDB events*/ + if((prev & (SERVER_NDB|SERVER_RUNNING)) == (SERVER_NDB|SERVER_RUNNING) && + SERVER_IS_DOWN(node->server)) + { + return NDB_DOWN_EVENT; + } + if((prev & (SERVER_RUNNING)) == 0 && + SERVER_IS_RUNNING(node->server) && SERVER_IS_NDB(node->server)) + { + return NDB_UP_EVENT; + } + + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_MASTER(node->server)) + { + return NEW_MASTER_EVENT; + } + if((prev & (SERVER_RUNNING)) == SERVER_RUNNING && + SERVER_IS_RUNNING(node->server) && SERVER_IS_SLAVE(node->server)) + { + return NEW_SLAVE_EVENT; + } + + /** Status loss events */ + if((prev & (SERVER_RUNNING|SERVER_MASTER)) == (SERVER_RUNNING|SERVER_MASTER) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_MASTER(node->server)) + { + return LOST_MASTER_EVENT; + } + if((prev & (SERVER_RUNNING|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_SLAVE(node->server)) + { + return LOST_SLAVE_EVENT; + } + if((prev & (SERVER_RUNNING|SERVER_JOINED)) == (SERVER_RUNNING|SERVER_JOINED) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_JOINED(node->server)) + { + return LOST_SYNCED_EVENT; + } + if((prev & (SERVER_RUNNING|SERVER_NDB)) == (SERVER_RUNNING|SERVER_NDB) && + SERVER_IS_RUNNING(node->server) && !SERVER_IS_NDB(node->server)) + { + return LOST_NDB_EVENT; + } + + + /** Generic server failure */ + if((prev & SERVER_RUNNING) == 0 && + SERVER_IS_RUNNING(node->server)) + { + return SERVER_UP_EVENT; + } + if((prev & SERVER_RUNNING) == SERVER_RUNNING && + SERVER_IS_DOWN(node->server)) + { + return SERVER_DOWN_EVENT; + } + + /** Something else, most likely a state that does not matter. + * For example SERVER_DOWN -> SERVER_MASTER|SERVER_DOWN still results in a + * server state equal to not running.*/ + return UNDEFINED_MONITOR_EVENT; +} + +char* mon_get_event_name(MONITOR_SERVERS* node) +{ + switch(mon_get_event_type(node)) + { +case UNDEFINED_MONITOR_EVENT: + return "undefined"; + +case MASTER_DOWN_EVENT: + return "master_down"; + +case MASTER_UP_EVENT: + return "master_up"; + +case SLAVE_DOWN_EVENT: + return "slave_down"; + +case SLAVE_UP_EVENT: + return "slave_up"; + +case SERVER_DOWN_EVENT: + return "server_down"; + +case SERVER_UP_EVENT: + return "server_up"; + +case SYNCED_DOWN_EVENT: + return "synced_down"; + +case SYNCED_UP_EVENT: + return "synced_up"; + +case DONOR_DOWN_EVENT: + return "donor_down"; + +case DONOR_UP_EVENT: + return "donor_up"; + +case NDB_DOWN_EVENT: + return "ndb_down"; + +case NDB_UP_EVENT: + return "ndb_up"; + +case LOST_MASTER_EVENT: + return "lost_master"; + +case LOST_SLAVE_EVENT: + return "lost_slave"; + +case LOST_SYNCED_EVENT: + return "lost_synced"; + +case LOST_DONOR_EVENT: + return "lost_donor"; + +case LOST_NDB_EVENT: + return "lost_ndb"; + +case NEW_MASTER_EVENT: + return "new_master"; + +case NEW_SLAVE_EVENT: + return "new_slave"; + +case NEW_SYNCED_EVENT: + return "new_synced"; + +case NEW_DONOR_EVENT: + return "new_donor"; + + case NEW_NDB_EVENT: + return "new_ndb"; + + default: + return "MONITOR_EVENT_FAILURE"; + + } + + +} + +void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len) +{ + MONITOR_SERVERS* ptr = start; + bool first = true; + int slen = strlen(str); + char arr[256]; + while(ptr && slen < len) + { + if(!first) + { + strncat(str,",",len); + } + first = false; + sprintf(arr,"%s:%d",ptr->server->name,ptr->server->port); + strcat(str,arr); + ptr = ptr->next; + slen = strlen(str); + } +} + +/** + * Check if current monitored server status has changed + * + * @param mon_srv The monitored server + * @return true if status has changed or false + */ +bool mon_status_changed( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + + /** This is the first time the server was set with a status*/ + if (mon_srv->mon_prev_status == -1) + return false; + + if (mon_srv->mon_prev_status != mon_srv->server->status) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} + +/** + * Check if current monitored server has a loggable failure status + * + * @param mon_srv The monitored server + * @return true if failed status can be logged or false + */ +bool mon_print_fail_status( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + int errcount = mon_srv->mon_err_count; + + if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} + +void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script) +{ + char argstr[PATH_MAX + MON_ARG_MAX + 1]; + EXTERNCMD* cmd; + + snprintf(argstr,PATH_MAX + MON_ARG_MAX, + "%s --event=%s --initiator=%s:%d --nodelist=", + script, + mon_get_event_name(ptr), + ptr->server->name, + ptr->server->port); + + mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1); + if((cmd = externcmd_allocate(argstr)) == NULL) + { + skygw_log_write(LE,"Failed to execute script: %s",script); + return; + } + + if(externcmd_execute(cmd)) + { + skygw_log_write(LOGFILE_ERROR, + "Error: Failed to execute script " + "'%s' on server state change event %s.", + script,mon_get_event_type(ptr)); + } + externcmd_free(cmd); +} + +/** + * Parse a string of event names to an array with enabled events. + * @param events Pointer to an array of boolean values + * @param count Size of the array + * @param string String to parse + * @return 0 on success. 1 when an error has occurred or an unexpected event was + * found. + */ +int mon_parse_event_string(bool* events, size_t count,char* string) +{ + char *tok,*saved; + monitor_event_t event; + + tok = strtok_r(string,",| ",&saved); + + if(tok == NULL) + return -1; + + while(tok) + { + event = mon_name_to_event(tok); + if(event == UNDEFINED_MONITOR_EVENT) + { + skygw_log_write(LE,"Error: Invalid event name %s",tok); + return -1; + } + events[event] = true; + tok = strtok_r(NULL,",| ",&saved); + } + + return 0; +} + +monitor_event_t mon_name_to_event(char* tok) +{ + if(!strcasecmp("master_down",tok)) + return MASTER_DOWN_EVENT; + else if(!strcasecmp("master_up",tok)) + return MASTER_UP_EVENT; + else if(!strcasecmp("slave_down",tok)) + return SLAVE_DOWN_EVENT; + else if(!strcasecmp("slave_up",tok)) + return SLAVE_UP_EVENT; + else if(!strcasecmp("server_down",tok)) + return SERVER_DOWN_EVENT; + else if(!strcasecmp("server_up",tok)) + return SERVER_UP_EVENT; + else if(!strcasecmp("synced_down",tok)) + return SYNCED_DOWN_EVENT; + else if(!strcasecmp("synced_up",tok)) + return SYNCED_UP_EVENT; + else if(!strcasecmp("donor_down",tok)) + return DONOR_DOWN_EVENT; + else if(!strcasecmp("donor_up",tok)) + return DONOR_UP_EVENT; + else if(!strcasecmp("ndb_down",tok)) + return NDB_DOWN_EVENT; + else if(!strcasecmp("ndb_up",tok)) + return NDB_UP_EVENT; + else if(!strcasecmp("lost_master",tok)) + return LOST_MASTER_EVENT; + else if(!strcasecmp("lost_slave",tok)) + return LOST_SLAVE_EVENT; + else if(!strcasecmp("lost_synced",tok)) + return LOST_SYNCED_EVENT; + else if(!strcasecmp("lost_donor",tok)) + return LOST_DONOR_EVENT; + else if(!strcasecmp("lost_ndb",tok)) + return LOST_NDB_EVENT; + else if(!strcasecmp("new_master",tok)) + return NEW_MASTER_EVENT; + else if(!strcasecmp("new_slave",tok)) + return NEW_SLAVE_EVENT; + else if(!strcasecmp("new_synced",tok)) + return NEW_SYNCED_EVENT; + else if(!strcasecmp("new_donor",tok)) + return NEW_DONOR_EVENT; + else if(!strcasecmp("new_ndb",tok)) + return NEW_NDB_EVENT; + else + return UNDEFINED_MONITOR_EVENT; + + } + diff --git a/server/modules/monitor/monitor_common.h b/server/modules/monitor/monitor_common.h new file mode 100644 index 000000000..bd4acc956 --- /dev/null +++ b/server/modules/monitor/monitor_common.h @@ -0,0 +1,75 @@ +#ifndef _MONITOR_COMMON_HG +#define _MONITOR_COMMON_HG +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2015 + */ + +#include +#include +#include +#include +#include +/** + * @file monitor_common.h - The generic monitor structures all monitors use + * + * Revision History + * + * Date Who Description + * 07/05/15 Markus Makela Initial Implementation + * @endverbatim + */ + +#define MON_ARG_MAX 8192 + +/** Monitor events that are caused by servers moving from + * one state to another.*/ +typedef enum { + UNDEFINED_MONITOR_EVENT, + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + SYNCED_DOWN_EVENT, + SYNCED_UP_EVENT, + DONOR_DOWN_EVENT, + DONOR_UP_EVENT, + NDB_DOWN_EVENT, + NDB_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_SYNCED_EVENT, + LOST_DONOR_EVENT, + LOST_NDB_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_SYNCED_EVENT, + NEW_DONOR_EVENT, + NEW_NDB_EVENT, + MAX_MONITOR_EVENT +}monitor_event_t; +void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len); +monitor_event_t mon_get_event_type(MONITOR_SERVERS* node); +char* mon_get_event_name(MONITOR_SERVERS* node); +void monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit); +void monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit); +bool mon_status_changed(MONITOR_SERVERS* mon_srv); +bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); +void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script); +int mon_parse_event_string(bool* events, size_t count,char* string); +#endif diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index 2251e1d8d..e31f2d57a 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -46,24 +46,13 @@ * 10/11/14 Massimiliano Pinto Addition of setNetworkTimeout for connect, read, write * 18/11/14 Massimiliano Pinto One server only in configuration becomes master. * servers=server1 must be present in mysql_mon and in router sections as well. + * 08/05/15 Markus Makela Added launchable scripts * * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -83,33 +72,20 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUser(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); static void defaultId(void *, unsigned long); -static void setNetworkTimeout(void *, int, int); -static bool mon_status_changed(MONITOR_SERVERS* mon_srv); -static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); static MONITOR_SERVERS *getServerByNodeId(MONITOR_SERVERS *, long); static MONITOR_SERVERS *getSlaveOfNodeId(MONITOR_SERVERS *, long); -static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *, int); +static MONITOR_SERVERS *get_replication_tree(MONITOR *, int); static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); -static void set_slave_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); +static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *); static int add_slave_to_master(long *, int, long); -static void monitor_set_pending_status(MONITOR_SERVERS *, int); -static void monitor_clear_pending_status(MONITOR_SERVERS *, int); - +bool isMySQLEvent(monitor_event_t event); +static bool report_version_err = true; static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, - registerServer, - unregisterServer, - defaultUser, - diagnostics, - setInterval, - setNetworkTimeout + diagnostics }; /** @@ -156,48 +132,95 @@ GetModuleObject() * This function creates a thread to execute the actual monitoring. * * @param arg The current handle - NULL if first start + * @param opt Configuration parameters * @return A handle to use when interacting with the monitor */ static void * startMonitor(void *arg, void* opt) { -MYSQL_MONITOR *handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg) - { - handle = arg; /* Must be a restart */ - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->databases = NULL; - handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; - handle->id = config_get_gateway_id(); - handle->interval = MONITOR_INTERVAL; - handle->replicationHeartbeat = 0; - handle->detectStaleMaster = 0; - handle->master = NULL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; - spinlock_init(&handle->lock); - } + MONITOR* monitor = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR*)monitor->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false,script_error = false; - while(params) + if (handle) + { + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = config_get_gateway_id(); + handle->replicationHeartbeat = 0; + handle->detectStaleMaster = 0; + handle->master = NULL; + handle->script = NULL; + handle->mysql51_replication = false; + memset(handle->events,false,sizeof(handle->events)); + spinlock_init(&handle->lock); + } + + while(params) + { + if(!strcmp(params->name,"detect_stale_master")) + handle->detectStaleMaster = config_truth_value(params->value); + else if(!strcmp(params->name,"detect_replication_lag")) + handle->replicationHeartbeat = config_truth_value(params->value); + else if(!strcmp(params->name,"script")) { - if(!strcmp(params->name,"detect_stale_master")) - handle->detectStaleMaster = config_truth_value(params->value); - else if(!strcmp(params->name,"detect_replication_lag")) - handle->replicationHeartbeat = config_truth_value(params->value); - params = params->next; + if(handle->script) + free(handle->script); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + script_error = true; + if(access(params->value,F_OK) == 0) + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be found: %s", + params->value); + } + handle->script = NULL; + } } - - handle->tid = (THREAD)thread_start(monitorMain, handle); - return handle; + else if(!strcmp(params->name,"events")) + { + if(mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value) != 0) + script_error = true; + else + have_events = true; + } + else if(!strcmp(params->name,"mysql51_replication")) + { + handle->mysql51_replication = config_truth_value(params->value); + } + params = params->next; + } + if(script_error) + { + skygw_log_write(LE,"Error: Errors were found in the script configuration parameters " + "for the monitor '%s'. The script will not be used.",monitor->name); + free(handle->script); + handle->script = NULL; + } + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } + handle->tid = (THREAD)thread_start(monitorMain, monitor); + return handle; } /** @@ -208,107 +231,11 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; - handle->shutdown = 1; - thread_wait((void *)handle->tid); -} - -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - db->mon_err_count = 0; - db->mon_prev_status = 0; - /* pending status is updated by get_replication_tree */ - db->pending_status = 0; - - spinlock_acquire(&handle->lock); - - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUser(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); + handle->shutdown = 1; + thread_wait((void *)handle->tid); } /** @@ -319,47 +246,367 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; */ static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *db; -char *sep; + MONITOR* mon = (MONITOR*)arg; + MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; + MONITOR_SERVERS *db; + char *sep; - switch (handle->status) - { - case MONITOR_RUNNING: - dcb_printf(dcb, "\tMonitor running\n"); - break; - case MONITOR_STOPPING: - dcb_printf(dcb, "\tMonitor stopping\n"); - break; - case MONITOR_STOPPED: - dcb_printf(dcb, "\tMonitor stopped\n"); - break; - } + switch (handle->status) + { + case MONITOR_RUNNING: + dcb_printf(dcb, "\tMonitor running\n"); + break; + case MONITOR_STOPPING: + dcb_printf(dcb, "\tMonitor stopping\n"); + break; + case MONITOR_STOPPED: + dcb_printf(dcb, "\tMonitor stopped\n"); + break; + } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); - dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id); - dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled"); - dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); - dcb_printf(dcb, "\tMonitored servers: "); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); + dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id); + dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled"); + dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled"); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); + dcb_printf(dcb, "\tMonitored servers: "); + + db = mon->databases; + sep = ""; + while (db) + { + dcb_printf(dcb, + "%s%s:%d", + sep, + db->server->name, + db->server->port); + sep = ", "; + db = db->next; + } + dcb_printf(dcb, "\n"); +} +/** + * Connect to a database + * @param mon Monitor + * @param database Monitored database + * @return true if connection was successful, false if there was an error + */ +static inline bool connect_to_db(MONITOR* mon,MONITOR_SERVERS *database) +{ + char *uname = mon->user; + char *passwd = mon->password; + char *dpwd = decryptPassword(passwd); + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; - db = handle->databases; - sep = ""; - while (db) - { - dcb_printf(dcb, - "%s%s:%d", - sep, - db->server->name, - db->server->port); - sep = ", "; - db = db->next; - } - dcb_printf(dcb, "\n"); + if(database->con) + mysql_close(database->con); + database->con = mysql_init(NULL); + + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + + bool result = (mysql_real_connect(database->con, + database->server->name, + uname, + dpwd, + NULL, + database->server->port, + NULL, + 0) != NULL); + free(dpwd); + return result; } +static inline void monitor_mysql100_db(MONITOR_SERVERS* database) +{ + bool isslave = false; + MYSQL_RES* result; + MYSQL_ROW row; + + if (mysql_query(database->con, "SHOW ALL SLAVES STATUS") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + int i = 0; + long master_id = -1; + + if(mysql_field_count(database->con) < 42) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: \"SHOW ALL SLAVES STATUS\" " + "returned less than the expected amount of columns. Expected 42 columns." + " MySQL Version: %s",version_str); + return; + } + + while ((row = mysql_fetch_row(result))) + { + /* get Slave_IO_Running and Slave_SQL_Running values*/ + if (strncmp(row[12], "Yes", 3) == 0 + && strncmp(row[13], "Yes", 3) == 0) { + isslave += 1; + } + + /* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building + * the replication tree, slaves ids will be added to master(s) and we will have at least the + * root master server. + * Please note, there could be no slaves at all if Slave_SQL_Running == 'No' + */ + if (strncmp(row[12], "Yes", 3) == 0) { + /* get Master_Server_Id values */ + master_id = atol(row[41]); + if (master_id == 0) + master_id = -1; + } + + i++; + } + /* store master_id of current node */ + memcpy(&database->server->master_id, &master_id, sizeof(long)); + + mysql_free_result(result); + + /* If all configured slaves are running set this node as slave */ + if (isslave > 0 && isslave == i) + isslave = 1; + else + isslave = 0; + } + + /* Remove addition info */ + monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); + monitor_clear_pending_status(database, SERVER_STALE_STATUS); + + /* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER + * will be assigned in the monitorMain() via get_replication_tree() routine + */ + + /* Set the Slave Role */ + if (isslave) + { + monitor_set_pending_status(database, SERVER_SLAVE); + /* Avoid any possible stale Master state */ + monitor_clear_pending_status(database, SERVER_MASTER); + } else { + /* Avoid any possible Master/Slave stale state */ + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); + } +} + +static inline void monitor_mysql55_db(MONITOR_SERVERS* database) +{ + bool isslave = false; + MYSQL_RES* result; + MYSQL_ROW row; + + if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + long master_id = -1; + if(mysql_field_count(database->con) < 40) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " + "returned less than the expected amount of columns. Expected 40 columns." + " MySQL Version: %s",version_str); + return; + } + + while ((row = mysql_fetch_row(result))) + { + /* get Slave_IO_Running and Slave_SQL_Running values*/ + if (strncmp(row[10], "Yes", 3) == 0 + && strncmp(row[11], "Yes", 3) == 0) { + isslave = 1; + } + + /* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building + * the replication tree, slaves ids will be added to master(s) and we will have at least the + * root master server. + * Please note, there could be no slaves at all if Slave_SQL_Running == 'No' + */ + if (strncmp(row[10], "Yes", 3) == 0) { + /* get Master_Server_Id values */ + master_id = atol(row[39]); + if (master_id == 0) + master_id = -1; + } + } + /* store master_id of current node */ + memcpy(&database->server->master_id, &master_id, sizeof(long)); + + mysql_free_result(result); + } + + /* Remove addition info */ + monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); + monitor_clear_pending_status(database, SERVER_STALE_STATUS); + + /* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER + * will be assigned in the monitorMain() via get_replication_tree() routine + */ + + /* Set the Slave Role */ + if (isslave) + { + monitor_set_pending_status(database, SERVER_SLAVE); + /* Avoid any possible stale Master state */ + monitor_clear_pending_status(database, SERVER_MASTER); + } else { + /* Avoid any possible Master/Slave stale state */ + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); + } +} + +static inline void monitor_mysql51_db(MONITOR_SERVERS* database) +{ + bool isslave = false; + MYSQL_RES* result; + MYSQL_ROW row; + + if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + if(mysql_field_count(database->con) < 38) + { + mysql_free_result(result); + + skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " + "returned less than the expected amount of columns. Expected 38 columns." + " MySQL Version: %s",version_str); + return; + } + + while ((row = mysql_fetch_row(result))) + { + /* get Slave_IO_Running and Slave_SQL_Running values*/ + if (strncmp(row[10], "Yes", 3) == 0 + && strncmp(row[11], "Yes", 3) == 0) { + isslave = 1; + } + } + mysql_free_result(result); + } + + /* Remove addition info */ + monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); + monitor_clear_pending_status(database, SERVER_STALE_STATUS); + + /* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER + * will be assigned in the monitorMain() via get_replication_tree() routine + */ + + /* Set the Slave Role */ + if (isslave) + { + monitor_set_pending_status(database, SERVER_SLAVE); + /* Avoid any possible stale Master state */ + monitor_clear_pending_status(database, SERVER_MASTER); + } else { + /* Avoid any possible Master/Slave stale state */ + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); + } +} + +/** + * Build the replication tree for a MySQL 5.1 cluster + * + * This function queries each server with SHOW SLAVE HOSTS to determine which servers + * have slaves replicating from them. + * @param mon Monitor + * @return Lowest server ID master in the monitor + */ +static MONITOR_SERVERS *build_mysql51_replication_tree(MONITOR *mon) +{ + MONITOR_SERVERS* database = mon->databases; + MONITOR_SERVERS *ptr,*rval = NULL; + int i; + while(database) + { + bool ismaster = false; + MYSQL_RES* result; + MYSQL_ROW row; + int nslaves = 0; + if(database->con) + { + if (mysql_query(database->con, "SHOW SLAVE HOSTS") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + if(mysql_field_count(database->con) < 4) + { + mysql_free_result(result); + skygw_log_write_flush(LE,"Error: \"SHOW SLAVE HOSTS\" " + "returned less than the expected amount of columns. Expected 4 columns." + " MySQL Version: %s",version_str); + return NULL; + } + + if(mysql_num_rows(result) > 0) + { + ismaster = true; + while (nslaves < MONITOR_MAX_NUM_SLAVES && (row = mysql_fetch_row(result))) + { + /* get Slave_IO_Running and Slave_SQL_Running values*/ + database->server->slaves[nslaves] = atol(row[0]); + nslaves++; + LOGIF(LD,(skygw_log_write_flush(LD,"Found slave at %s:%d",row[1],row[2]))); + } + database->server->slaves[nslaves] = 0; + } + + mysql_free_result(result); + } + + + /* Set the Slave Role */ + if (ismaster) + { + LOGIF(LD,(skygw_log_write(LD,"Master server found at %s:%d with %d slaves", + database->server->name, + database->server->port, + nslaves))); + monitor_set_pending_status(database, SERVER_MASTER); + if(rval == NULL || rval->server->node_id > database->server->node_id) + rval = database; + } + } + database = database->next; + } + + database = mon->databases; + + /** Set master server IDs */ + while(database) + { + ptr = mon->databases; + + while(ptr) + { + for(i = 0;ptr->server->slaves[i];i++) + { + if(ptr->server->slaves[i] == database->server->node_id) + { + database->server->master_id = ptr->server->node_id; + break; + } + } + ptr = ptr->next; + } + if(database->server->master_id <= 0 && SERVER_IS_SLAVE(database->server)) + { + monitor_set_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); + } + database = database->next; + } + return rval; +} /** * Monitor an individual server * @@ -367,237 +614,146 @@ char *sep; * @param database The database to probe */ static void -monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) +monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) { -MYSQL_ROW row; -MYSQL_RES *result; -int num_fields; -int isslave = 0; -char *uname = handle->defaultUser; -char *passwd = handle->defaultPasswd; -unsigned long int server_version = 0; -char *server_string; + MYSQL_MONITOR* handle = mon->handle; + MYSQL_ROW row; + MYSQL_RES *result; + int isslave = 0; + char *uname = mon->user; + unsigned long int server_version = 0; + char *server_string; - if (database->server->monuser != NULL) + if (database->server->monuser != NULL) + { + uname = database->server->monuser; + } + + if (uname == NULL) + return; + + /* Don't probe servers in maintenance mode */ + if (SERVER_IN_MAINT(database->server)) + return; + + /** Store previous status */ + database->mon_prev_status = database->server->status; + + if (database->con == NULL || mysql_ping(database->con) != 0) + { + if(connect_to_db(mon,database)) { - uname = database->server->monuser; - passwd = database->server->monpw; + server_clear_status(database->server, SERVER_AUTH_ERROR); + monitor_clear_pending_status(database, SERVER_AUTH_ERROR); } - - if (uname == NULL) - return; - - /* Don't probe servers in maintenance mode */ - if (SERVER_IN_MAINT(database->server)) - return; - - /** Store previous status */ - database->mon_prev_status = database->server->status; - - if (database->con == NULL || mysql_ping(database->con) != 0) + else { - char *dpwd = decryptPassword(passwd); - int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; - if(database->con) - mysql_close(database->con); - database->con = mysql_init(NULL); + /* The current server is not running + * + * Store server NOT running in server and monitor server pending struct + * + */ + if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR) + { + server_set_status(database->server, SERVER_AUTH_ERROR); + monitor_set_pending_status(database, SERVER_AUTH_ERROR); + } + server_clear_status(database->server, SERVER_RUNNING); + monitor_clear_pending_status(database, SERVER_RUNNING); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); - - if (mysql_real_connect(database->con, - database->server->name, - uname, - dpwd, - NULL, - database->server->port, - NULL, - 0) == NULL) - { - free(dpwd); - - /* The current server is not running - * - * Store server NOT running in server and monitor server pending struct - * - */ - if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR) - { - server_set_status(database->server, SERVER_AUTH_ERROR); - monitor_set_pending_status(database, SERVER_AUTH_ERROR); - } - server_clear_status(database->server, SERVER_RUNNING); - monitor_clear_pending_status(database, SERVER_RUNNING); + /* Also clear M/S state in both server and monitor server pending struct */ + server_clear_status(database->server, SERVER_SLAVE); + server_clear_status(database->server, SERVER_MASTER); + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); - /* Also clear M/S state in both server and monitor server pending struct */ - server_clear_status(database->server, SERVER_SLAVE); - server_clear_status(database->server, SERVER_MASTER); - monitor_clear_pending_status(database, SERVER_SLAVE); - monitor_clear_pending_status(database, SERVER_MASTER); + /* Clean addition status too */ + server_clear_status(database->server, SERVER_SLAVE_OF_EXTERNAL_MASTER); + server_clear_status(database->server, SERVER_STALE_STATUS); + monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); + monitor_clear_pending_status(database, SERVER_STALE_STATUS); - /* Clean addition status too */ - server_clear_status(database->server, SERVER_SLAVE_OF_EXTERNAL_MASTER); - server_clear_status(database->server, SERVER_STALE_STATUS); - monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); - monitor_clear_pending_status(database, SERVER_STALE_STATUS); + /* Log connect failure only once */ + if (mon_status_changed(database) && mon_print_fail_status(database)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor was unable to connect to " + "server %s:%d : \"%s\"", + database->server->name, + database->server->port, + mysql_error(database->con)))); + } - /* Log connect failure only once */ - if (mon_status_changed(database) && mon_print_fail_status(database)) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor was unable to connect to " - "server %s:%d : \"%s\"", - database->server->name, - database->server->port, - mysql_error(database->con)))); - } - - return; - } - else - { - server_clear_status(database->server, SERVER_AUTH_ERROR); - monitor_clear_pending_status(database, SERVER_AUTH_ERROR); - } - free(dpwd); + return; } - /* Store current status in both server and monitor server pending struct */ - server_set_status(database->server, SERVER_RUNNING); - monitor_set_pending_status(database, SERVER_RUNNING); + } + /* Store current status in both server and monitor server pending struct */ + server_set_status(database->server, SERVER_RUNNING); + monitor_set_pending_status(database, SERVER_RUNNING); - /* get server version from current server */ - server_version = mysql_get_server_version(database->con); + /* get server version from current server */ + server_version = mysql_get_server_version(database->con); - /* get server version string */ - server_string = (char *)mysql_get_server_info(database->con); - if (server_string) { - database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1); - if (database->server->server_string) - strcpy(database->server->server_string, server_string); - } + /* get server version string */ + server_string = (char *)mysql_get_server_info(database->con); + if (server_string) { + database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1); + if (database->server->server_string) + strcpy(database->server->server_string, server_string); + } + + /* get server_id form current node */ + if (mysql_query(database->con, "SELECT @@server_id") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + long server_id = -1; - /* get server_id form current node */ - if (mysql_query(database->con, "SELECT @@server_id") == 0 - && (result = mysql_store_result(database->con)) != NULL) - { - long server_id = -1; - num_fields = mysql_num_fields(result); - while ((row = mysql_fetch_row(result))) - { - server_id = strtol(row[0], NULL, 10); - if ((errno == ERANGE && (server_id == LONG_MAX - || server_id == LONG_MIN)) || (errno != 0 && server_id == 0)) - { - server_id = -1; - } - database->server->node_id = server_id; - } - mysql_free_result(result); - } - - /* Check if the Slave_SQL_Running and Slave_IO_Running status is - * set to Yes - */ - - /* Check first for MariaDB 10.x.x and get status for multimaster replication */ - if (server_version >= 100000) { - - if (mysql_query(database->con, "SHOW ALL SLAVES STATUS") == 0 - && (result = mysql_store_result(database->con)) != NULL) - { - int i = 0; - long master_id = -1; - num_fields = mysql_num_fields(result); - while ((row = mysql_fetch_row(result))) - { - /* get Slave_IO_Running and Slave_SQL_Running values*/ - if (strncmp(row[12], "Yes", 3) == 0 - && strncmp(row[13], "Yes", 3) == 0) { - isslave += 1; - } - - /* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building - * the replication tree, slaves ids will be added to master(s) and we will have at least the - * root master server. - * Please note, there could be no slaves at all if Slave_SQL_Running == 'No' - */ - if (strncmp(row[12], "Yes", 3) == 0) { - /* get Master_Server_Id values */ - master_id = atol(row[41]); - if (master_id == 0) - master_id = -1; - } - - i++; - } - /* store master_id of current node */ - memcpy(&database->server->master_id, &master_id, sizeof(long)); - - mysql_free_result(result); - - /* If all configured slaves are running set this node as slave */ - if (isslave > 0 && isslave == i) - isslave = 1; - else - isslave = 0; - } - } else { - if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 - && (result = mysql_store_result(database->con)) != NULL) - { - long master_id = -1; - num_fields = mysql_num_fields(result); - while ((row = mysql_fetch_row(result))) - { - /* get Slave_IO_Running and Slave_SQL_Running values*/ - if (strncmp(row[10], "Yes", 3) == 0 - && strncmp(row[11], "Yes", 3) == 0) { - isslave = 1; - } - - /* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building - * the replication tree, slaves ids will be added to master(s) and we will have at least the - * root master server. - * Please note, there could be no slaves at all if Slave_SQL_Running == 'No' - */ - if (strncmp(row[10], "Yes", 3) == 0) { - /* get Master_Server_Id values */ - master_id = atol(row[39]); - if (master_id == 0) - master_id = -1; - } - } - /* store master_id of current node */ - memcpy(&database->server->master_id, &master_id, sizeof(long)); - - mysql_free_result(result); - } - } - - /* Remove addition info */ - monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); - monitor_clear_pending_status(database, SERVER_STALE_STATUS); - - /* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER - * will be assigned in the monitorMain() via get_replication_tree() routine - */ - - /* Set the Slave Role */ - if (isslave) + if(mysql_field_count(database->con) != 1) { - monitor_set_pending_status(database, SERVER_SLAVE); - /* Avoid any possible stale Master state */ - monitor_clear_pending_status(database, SERVER_MASTER); - } else { - /* Avoid any possible Master/Slave stale state */ - monitor_clear_pending_status(database, SERVER_SLAVE); - monitor_clear_pending_status(database, SERVER_MASTER); + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for 'SELECT @@server_id'. Expected 1 column." + " MySQL Version: %s",version_str); + return; } + + while ((row = mysql_fetch_row(result))) + { + server_id = strtol(row[0], NULL, 10); + if ((errno == ERANGE && (server_id == LONG_MAX + || server_id == LONG_MIN)) || (errno != 0 && server_id == 0)) + { + server_id = -1; + } + database->server->node_id = server_id; + } + mysql_free_result(result); + } + + /* Check first for MariaDB 10.x.x and get status for multi-master replication */ + if (server_version >= 100000) + { + monitor_mysql100_db(database); + } + else if(server_version >= 5*10000 + 5*100) + { + monitor_mysql55_db(database); + } + else + { + if(handle->mysql51_replication) + { + monitor_mysql51_db(database); + } + else if(report_version_err) + { + report_version_err = false; + skygw_log_write(LE,"Error: MySQL version is lower than 5.5 and 'mysql51_replication' option is not enabled," + " replication tree cannot be resolved. To enable MySQL 5.1 replication detection, " + "add 'mysql51_replication=true' to the monitor section."); + } + } + } /** @@ -608,15 +764,22 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*) arg; +MYSQL_MONITOR *handle; MONITOR_SERVERS *ptr; -int replication_heartbeat = handle->replicationHeartbeat; -int detect_stale_master = handle->detectStaleMaster; +int replication_heartbeat; +int detect_stale_master; int num_servers=0; MONITOR_SERVERS *root_master = NULL; size_t nrounds = 0; int log_no_master = 1; +spinlock_acquire(&mon->lock); +handle = (MYSQL_MONITOR *)mon->handle; +spinlock_release(&mon->lock); +replication_heartbeat = handle->replicationHeartbeat; +detect_stale_master = handle->detectStaleMaster; + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( @@ -645,7 +808,7 @@ int log_no_master = 1; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; @@ -656,15 +819,17 @@ int log_no_master = 1; num_servers = 0; /* start from the first server in the list */ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { + ptr->mon_prev_status = ptr->server->status; + /* copy server status into monitor pending_status */ ptr->pending_status = ptr->server->status; /* monitor current node */ - monitorDatabase(handle, ptr); + monitorDatabase(mon, ptr); /* reset the slave list of current node */ if (ptr->server->slaves) { @@ -697,7 +862,12 @@ int log_no_master = 1; !(SERVER_IS_IN_CLUSTER(ptr->server))) { dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); - } + + } + + + + } if (mon_status_changed(ptr)) @@ -733,7 +903,7 @@ int log_no_master = 1; ptr = ptr->next; } - ptr = handle->databases; + ptr = mon->databases; /* if only one server is configured, that's is Master */ if (num_servers == 1) { if (SERVER_IS_RUNNING(ptr->server)) { @@ -750,12 +920,16 @@ int log_no_master = 1; } } else { /* Compute the replication tree */ - root_master = get_replication_tree(handle, num_servers); + if(handle->mysql51_replication) + root_master = build_mysql51_replication_tree(mon); + else + root_master = get_replication_tree(mon, num_servers); + } /* Update server status from monitor pending status on that server*/ - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if (! SERVER_IN_MAINT(ptr->server)) { @@ -792,7 +966,30 @@ int log_no_master = 1; ptr = ptr->next; } - /* log master detection failure od first master becomes available after failure */ + ptr = mon->databases; + monitor_event_t evtype; + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr)) + { + evtype = mon_get_event_type(ptr); + if(isMySQLEvent(evtype)) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + } + ptr = ptr->next; + } + + /* log master detection failure of first master becomes available after failure */ if (root_master && mon_status_changed(root_master) && !(root_master->server->status & SERVER_STALE_STATUS)) @@ -832,7 +1029,7 @@ int log_no_master = 1; SERVER_IS_RELAY_SERVER(root_master->server))) { set_master_heartbeat(handle, root_master); - ptr = handle->databases; + ptr = mon->databases; while (ptr) { if( (! SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server)) @@ -841,7 +1038,7 @@ int log_no_master = 1; (SERVER_IS_SLAVE(ptr->server) || SERVER_IS_RELAY_SERVER(ptr->server))) { - set_slave_heartbeat(handle, ptr); + set_slave_heartbeat(mon, ptr); } } ptr = ptr->next; @@ -863,19 +1060,6 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; memcpy(&handle->id, &id, sizeof(unsigned long)); } -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} - /** * Enable/Disable the MySQL Replication hearbeat, detecting slave lag behind master. * @@ -904,54 +1088,6 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; memcpy(&handle->detectStaleMaster, &enable, sizeof(int)); } -/** - * Check if current monitored server status has changed - * - * @param mon_srv The monitored server - * @return true if status has changed or false - */ -static bool mon_status_changed( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - - if (mon_srv->mon_prev_status != mon_srv->server->status) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - -/** - * Check if current monitored server has a loggable failure status - * - * @param mon_srv The monitored server - * @return true if failed status can be logged or false - */ -static bool mon_print_fail_status( - MONITOR_SERVERS* mon_srv) -{ - bool succp; - int errcount = mon_srv->mon_err_count; - uint8_t modval; - - modval = 1<<(MIN(errcount/10, 7)); - - if (SERVER_IS_DOWN(mon_srv->server) && errcount == 0) - { - succp = true; - } - else - { - succp = false; - } - return succp; -} - /** * Fetch a MySQL node by node_id * @@ -1113,13 +1249,13 @@ static void set_master_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *databas * @param handle The monitor handle * @param database The number database server */ -static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { +static void set_slave_heartbeat(MONITOR* mon, MONITOR_SERVERS *database) { + MYSQL_MONITOR *handle = (MYSQL_MONITOR*)mon->handle; unsigned long id = handle->id; time_t heartbeat; char select_heartbeat_query[256] = ""; MYSQL_ROW row; MYSQL_RES *result; - int num_fields; if (handle->master == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -1139,7 +1275,6 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database if (handle->master !=NULL && (mysql_query(database->con, select_heartbeat_query) == 0 && (result = mysql_store_result(database->con)) != NULL)) { int rows_found = 0; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { int rlag = -1; @@ -1164,7 +1299,7 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database if (rlag >= 0) { /* store rlag only if greater than monitor sampling interval */ - database->server->rlag = (rlag > (handle->interval / 1000)) ? rlag : 0; + database->server->rlag = (rlag > (mon->interval / 1000)) ? rlag : 0; } else { database->server->rlag = -1; } @@ -1218,7 +1353,8 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database * @return The server at root level with SERVER_MASTER bit */ -static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_servers) { +static MONITOR_SERVERS *get_replication_tree(MONITOR *mon, int num_servers) { + MYSQL_MONITOR* handle = (MYSQL_MONITOR*)mon->handle; MONITOR_SERVERS *ptr; MONITOR_SERVERS *backend; SERVER *current; @@ -1226,7 +1362,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv long node_id; int root_level; - ptr = handle->databases; + ptr = mon->databases; root_level = num_servers; while (ptr) @@ -1245,7 +1381,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv node_id = current->master_id; if (node_id < 1) { MONITOR_SERVERS *find_slave; - find_slave = getSlaveOfNodeId(handle->databases, current->node_id); + find_slave = getSlaveOfNodeId(mon->databases, current->node_id); if (find_slave == NULL) { current->depth = -1; @@ -1265,7 +1401,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv root_level = current->depth; handle->master = ptr; } - backend = getServerByNodeId(handle->databases, node_id); + backend = getServerByNodeId(mon->databases, node_id); if (backend) { node_id = backend->server->master_id; @@ -1281,7 +1417,7 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv MONITOR_SERVERS *master; current->depth = depth; - master = getServerByNodeId(handle->databases, current->master_id); + master = getServerByNodeId(mon->databases, current->master_id); if (master && master->server && master->server->node_id > 0) { add_slave_to_master(master->server->slaves, MONITOR_MAX_NUM_SLAVES, current->node_id); master->server->depth = current->depth -1; @@ -1340,88 +1476,31 @@ static int add_slave_to_master(long *slaves_list, int list_size, long node_id) { return 0; } +static monitor_event_t mysql_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + MAX_MONITOR_EVENT +}; /** - * Set a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) + * Check if the MySQL monitor is monitoring this event type. + * @param event Event to check + * @return True if the event is monitored, false if it is not + * */ +bool isMySQLEvent(monitor_event_t event) { - ptr->pending_status |= bit; + int i; + for(i = 0;mysql_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == mysql_events[i]) + return true; + } + return false; } - -/** - * Clear a pending status bit in the monior server - * - * @param server The server to update - * @param bit The bit to clear for the server - */ -static void -monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) -{ - ptr->pending_status &= ~bit; -} - -/** - * Set the default id to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set - */ -static void -setNetworkTimeout(void *arg, int type, int value) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 11de7e0f4..227b693c9 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -17,10 +17,22 @@ * * Copyright MariaDB Corporation Ab 2013-2014 */ -#include -#include -#include - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /** * @file mysqlmon.h - The MySQL monitor functionality within the gateway * @@ -37,24 +49,10 @@ * 07/11/14 Massimiliano Pinto Addition of NetworkTimeout: connect, read, write * 20/04/15 Guillaume Lefranc Addition of availableWhenDonor * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting - * + * 07/05/15 Markus Makela Addition of command execution on Master server failure * @endverbatim */ -/** - * The linked list of servers that are being monitored by the MySQL - * Monitor module. - */ -typedef struct monitor_servers { - SERVER *server; /**< The server being monitored */ - MYSQL *con; /**< The MySQL connection */ - int mon_err_count; - unsigned int mon_prev_status; - unsigned int pending_status; /**< Pending Status flag bitmap */ - struct monitor_servers - *next; /**< The next server in the list */ -} MONITOR_SERVERS; - /** * The handle for an instance of a MySQL Monitor module */ @@ -63,32 +61,16 @@ typedef struct { pthread_t tid; /**< id of monitor thread */ int shutdown; /**< Flag to shutdown the monitor thread */ int status; /**< Monitor status */ - char *defaultUser; /**< Default username for monitoring */ - char *defaultPasswd; /**< Default password for monitoring */ - unsigned long interval; /**< Monitor sampling interval */ unsigned long id; /**< Monitor ID */ int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */ int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */ int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */ int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */ int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */ + bool mysql51_replication; /**< Use MySQL 5.1 replication */ MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ - MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ - int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */ - int read_timeout; /**< Timeout in seconds to read from the server. - * There are retries and the total effective timeout value is three times the option value. - */ - int write_timeout; /**< Timeout in seconds for each attempt to write to the server. - * There are retries and the total effective timeout value is two times the option value. - */ + char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ } MYSQL_MONITOR; -#define MONITOR_RUNNING 1 -#define MONITOR_STOPPING 2 -#define MONITOR_STOPPED 3 - -#define MONITOR_INTERVAL 10000 // in milliseconds -#define MONITOR_DEFAULT_ID 1UL // unsigned long value -#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server - #endif diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbclustermon.c similarity index 52% rename from server/modules/monitor/ndbcluster_mon.c rename to server/modules/monitor/ndbclustermon.c index 76a05fd03..a5d0b2455 100644 --- a/server/modules/monitor/ndbcluster_mon.c +++ b/server/modules/monitor/ndbclustermon.c @@ -25,24 +25,14 @@ * Date Who Description * 25/07/14 Massimiliano Pinto Initial implementation * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write + * 08/05/15 Markus Makela Addition of launchable scripts * * @endverbatim */ -#include -#include -#include -#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -50,7 +40,7 @@ extern __thread log_info_t tls_log_info; static void monitorMain(void *); -static char *version_str = "V1.1.0"; +static char *version_str = "V2.1.0"; MODULE_INFO info = { MODULE_API_MONITOR, @@ -61,22 +51,13 @@ MODULE_INFO info = { static void *startMonitor(void *,void*); static void stopMonitor(void *); -static void registerServer(void *, SERVER *); -static void unregisterServer(void *, SERVER *); -static void defaultUsers(void *, char *, char *); static void diagnostics(DCB *, void *); -static void setInterval(void *, size_t); -static void setNetworkTimeout(void *arg, int type, int value); +bool isNdbEvent(monitor_event_t event); static MONITOR_OBJECT MyObject = { startMonitor, - stopMonitor, - registerServer, - unregisterServer, - defaultUsers, - diagnostics, - setInterval, - setNetworkTimeout + stopMonitor, + diagnostics }; /** @@ -127,31 +108,77 @@ GetModuleObject() static void * startMonitor(void *arg,void* opt) { -MYSQL_MONITOR *handle; -CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; - if (arg != NULL) - { - handle = (MYSQL_MONITOR *)arg; - handle->shutdown = 0; - } - else - { - if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) - return NULL; - handle->databases = NULL; - handle->shutdown = 0; - handle->defaultUser = NULL; - handle->defaultPasswd = NULL; - handle->id = MONITOR_DEFAULT_ID; - handle->interval = MONITOR_INTERVAL; - handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT; - handle->read_timeout=DEFAULT_READ_TIMEOUT; - handle->write_timeout=DEFAULT_WRITE_TIMEOUT; - spinlock_init(&handle->lock); - } + MONITOR* mon = (MONITOR*)arg; + MYSQL_MONITOR *handle = mon->handle; + CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; + bool have_events = false,script_error = false; - handle->tid = (THREAD)thread_start(monitorMain, handle); - return handle; + if (handle != NULL) + { + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->shutdown = 0; + handle->id = MONITOR_DEFAULT_ID; + handle->script = NULL; + handle->master = NULL; + memset(handle->events,false,sizeof(handle->events)); + spinlock_init(&handle->lock); + } + while(params) + { + if(!strcmp(params->name,"script")) + { + if(handle->script) + free(handle->script); + if(access(params->value,X_OK) == 0) + { + handle->script = strdup(params->value); + } + else + { + script_error = true; + if(access(params->value,F_OK) == 0) + { + skygw_log_write(LE, + "Error: The file cannot be executed: %s", + params->value); + } + else + { + skygw_log_write(LE, + "Error: The file cannot be found: %s", + params->value); + } + handle->script = NULL; + } + } + else if(!strcmp(params->name,"events")) + { + if(mon_parse_event_string((bool*)&handle->events,sizeof(handle->events),params->value) != 0) + script_error = true; + else + have_events = true; + } + params = params->next; + } + if(script_error) + { + skygw_log_write(LE,"Error: Errors were found in the script configuration parameters " + "for the monitor '%s'. The script will not be used.",mon->name); + free(handle->script); + handle->script = NULL; + } + /** If no specific events are given, enable them all */ + if(!have_events) + { + memset(handle->events,true,sizeof(handle->events)); + } + handle->tid = (THREAD)thread_start(monitorMain, mon); + return handle; } /** @@ -162,82 +189,13 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt; static void stopMonitor(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = (MONITOR*)arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; handle->shutdown = 1; thread_wait((void *)handle->tid); } -/** - * Register a server that must be added to the monitored servers for - * a monitoring module. - * - * @param arg A handle on the running monitor module - * @param server The server to add - */ -static void -registerServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *db; - - if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) - return; - db->server = server; - db->con = NULL; - db->next = NULL; - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - handle->databases = db; - else - { - ptr = handle->databases; - while (ptr->next != NULL) - ptr = ptr->next; - ptr->next = db; - } - spinlock_release(&handle->lock); -} - -/** - * Remove a server from those being monitored by a monitoring module - * - * @param arg A handle on the running monitor module - * @param server The server to remove - */ -static void -unregisterServer(void *arg, SERVER *server) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -MONITOR_SERVERS *ptr, *lptr; - - spinlock_acquire(&handle->lock); - if (handle->databases == NULL) - { - spinlock_release(&handle->lock); - return; - } - if (handle->databases->server == server) - { - ptr = handle->databases; - handle->databases = handle->databases->next; - free(ptr); - } - else - { - ptr = handle->databases; - while (ptr->next != NULL && ptr->next->server != server) - ptr = ptr->next; - if (ptr->next) - { - lptr = ptr->next; - ptr->next = ptr->next->next; - free(lptr); - } - } - spinlock_release(&handle->lock); -} - /** * Diagnostic interface * @@ -247,7 +205,8 @@ MONITOR_SERVERS *ptr, *lptr; static void diagnostics(DCB *dcb, void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = arg; +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)mon->handle; MONITOR_SERVERS *db; char *sep; @@ -264,13 +223,13 @@ char *sep; break; } - dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); - dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout); - dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout); - dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout); + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", mon->interval); + dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", mon->connect_timeout); + dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", mon->read_timeout); + dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout); dcb_printf(dcb, "\tMonitored servers: "); - db = handle->databases; + db = mon->databases; sep = ""; while (db) { @@ -281,41 +240,19 @@ char *sep; dcb_printf(dcb, "\n"); } -/** - * Set the default username and password to use to monitor if the server does not - * override this. - * - * @param arg The handle allocated by startMonitor - * @param uname The default user name - * @param passwd The default password - */ -static void -defaultUsers(void *arg, char *uname, char *passwd) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - - if (handle->defaultUser) - free(handle->defaultUser); - if (handle->defaultPasswd) - free(handle->defaultPasswd); - handle->defaultUser = strdup(uname); - handle->defaultPasswd = strdup(passwd); -} - /** * Monitor an individual server * * @param database The database to probe */ static void -monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd, MYSQL_MONITOR *handle) +monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd, MONITOR *mon) { + MYSQL_MONITOR* handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; -int num_fields; int isjoined = 0; char *uname = defaultUser, *passwd = defaultPasswd; -unsigned long int server_version = 0; char *server_string; if (database->server->monuser != NULL) @@ -333,18 +270,17 @@ char *server_string; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); - int rc; - int connect_timeout = handle->connect_timeout; - int read_timeout = handle->read_timeout; - int write_timeout = handle->write_timeout; + int connect_timeout = mon->connect_timeout; + int read_timeout = mon->read_timeout; + int write_timeout = mon->write_timeout; if(database->con) mysql_close(database->con); database->con = mysql_init(NULL); - rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); - rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); - rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); + mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); + mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout); if (mysql_real_connect(database->con, database->server->name, uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) @@ -375,9 +311,6 @@ char *server_string; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); - /* get server version from current server */ - server_version = mysql_get_server_version(database->con); - /* get server version string */ server_string = (char *)mysql_get_server_info(database->con); if (server_string) { @@ -390,7 +323,14 @@ char *server_string; if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0 && (result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } + while ((row = mysql_fetch_row(result))) { if (atoi(row[1]) > 0) @@ -403,8 +343,15 @@ char *server_string; if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_cluster_node_id'") == 0 && (result = mysql_store_result(database->con)) != NULL) { + if(mysql_field_count(database->con) < 2) + { + mysql_free_result(result); + skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'Ndb_cluster_node_id'\". Expected 2 columns." + " MySQL Version: %s",version_str); + return; + } + long cluster_node_id = -1; - num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { cluster_node_id = strtol(row[1], NULL, 10); @@ -435,11 +382,15 @@ char *server_string; static void monitorMain(void *arg) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + MONITOR* mon = arg; +MYSQL_MONITOR *handle; MONITOR_SERVERS *ptr; -long master_id; size_t nrounds = 0; +spinlock_acquire(&mon->lock); +handle = (MYSQL_MONITOR *)mon->handle; +spinlock_release(&mon->lock); + if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( @@ -469,22 +420,21 @@ size_t nrounds = 0; * round. */ if (nrounds != 0 && - ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= + ((nrounds*MON_BASE_INTERVAL_MS)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; continue; } nrounds += 1; - master_id = -1; - ptr = handle->databases; + ptr = mon->databases; while (ptr) { - unsigned int prev_status = ptr->server->status; - monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd,handle); + ptr->mon_prev_status = ptr->server->status; + monitorDatabase(ptr, mon->user, mon->password,mon); - if (ptr->server->status != prev_status || + if (ptr->server->status != ptr->mon_prev_status || SERVER_IS_DOWN(ptr->server)) { LOGIF(LD, (skygw_log_write_flush( @@ -497,80 +447,64 @@ size_t nrounds = 0; ptr = ptr->next; } + + ptr = mon->databases; + monitor_event_t evtype; + + while(ptr) + { + /** Execute monitor script if a server state has changed */ + if(mon_status_changed(ptr)) + { + evtype = mon_get_event_type(ptr); + if(isNdbEvent(evtype)) + { + skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", + ptr->server->unique_name, + ptr->server->name,ptr->server->port, + mon_get_event_name(ptr)); + if(handle->script && handle->events[evtype]) + { + monitor_launch_script(mon,ptr,handle->script); + } + } + } + ptr = ptr->next; + } } } -/** - * Set the monitor sampling interval. - * - * @param arg The handle allocated by startMonitor - * @param interval The interval to set in monitor struct, in milliseconds - */ -static void -setInterval(void *arg, size_t interval) -{ -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; - memcpy(&handle->interval, &interval, sizeof(unsigned long)); -} + +static monitor_event_t ndb_events[] = { + MASTER_DOWN_EVENT, + MASTER_UP_EVENT, + SLAVE_DOWN_EVENT, + SLAVE_UP_EVENT, + SERVER_DOWN_EVENT, + SERVER_UP_EVENT, + NDB_UP_EVENT, + NDB_DOWN_EVENT, + LOST_MASTER_EVENT, + LOST_SLAVE_EVENT, + LOST_NDB_EVENT, + NEW_MASTER_EVENT, + NEW_SLAVE_EVENT, + NEW_NDB_EVENT, + MAX_MONITOR_EVENT +}; /** - * Set the timeouts to use in the monitor. - * - * @param arg The handle allocated by startMonitor - * @param type The connect timeout type - * @param value The timeout value to set + * Check if the event type is one the ndbcustermonitor is interested in. + * @param event Event to check + * @return True if the event is monitored, false if it is not */ -static void -setNetworkTimeout(void *arg, int type, int value) +bool isNdbEvent(monitor_event_t event) { -MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; -int max_timeout = (int)(handle->interval/1000); -int new_timeout = max_timeout -1; - - if (new_timeout <= 0) - new_timeout = DEFAULT_CONNECT_TIMEOUT; - - switch(type) { - case MONITOR_CONNECT_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->connect_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->connect_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_READ_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->read_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->read_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - - case MONITOR_WRITE_TIMEOUT: - if (value < max_timeout) { - memcpy(&handle->write_timeout, &value, sizeof(int)); - } else { - memcpy(&handle->write_timeout, &new_timeout, sizeof(int)); - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds" - ", lowering to %i seconds", value, max_timeout, new_timeout))); - } - break; - default: - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor setNetworkTimeout received an unsupported action type %i", type))); - break; - } -} - + int i; + for(i = 0;ndb_events[i] != MAX_MONITOR_EVENT;i++) + { + if(event == ndb_events[i]) + return true; + } + return false; +} \ No newline at end of file diff --git a/server/modules/monitor/ndbclustermon.h b/server/modules/monitor/ndbclustermon.h new file mode 100644 index 000000000..56b961c45 --- /dev/null +++ b/server/modules/monitor/ndbclustermon.h @@ -0,0 +1,56 @@ +#ifndef _MYSQLMON_H +#define _MYSQLMON_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file ndbclustermon.h - The NDB Cluster monitor + * + */ + +/** + * The handle for an instance of a NDB Cluster Monitor module + */ +typedef struct { + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + unsigned long id; /**< Monitor ID */ + MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ + char* script; /*< Script to call when state changes occur on servers */ + bool events[MAX_MONITOR_EVENT]; /*< enabled events */ +} MYSQL_MONITOR; + +#endif diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index fa1c2ab34..124071c44 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -1,27 +1,28 @@ add_library(MySQLClient SHARED mysql_client.c mysql_common.c) target_link_libraries(MySQLClient log_manager utils) -install(TARGETS MySQLClient DESTINATION modules) +install(TARGETS MySQLClient DESTINATION ${MAXSCALE_LIBDIR}) add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c) target_link_libraries(MySQLBackend log_manager utils) -install(TARGETS MySQLBackend DESTINATION modules) +install(TARGETS MySQLBackend DESTINATION ${MAXSCALE_LIBDIR}) add_library(telnetd SHARED telnetd.c) target_link_libraries(telnetd log_manager utils) -install(TARGETS telnetd DESTINATION modules) +install(TARGETS telnetd DESTINATION ${MAXSCALE_LIBDIR}) add_library(HTTPD SHARED httpd.c) target_link_libraries(HTTPD log_manager utils) -install(TARGETS HTTPD DESTINATION modules) +install(TARGETS HTTPD DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_TESTS) add_library(testprotocol SHARED testprotocol.c) - install(TARGETS testprotocol DESTINATION modules) + install(TARGETS testprotocol DESTINATION ${MAXSCALE_LIBDIR}) + add_subdirectory(test) endif() add_library(maxscaled SHARED maxscaled.c) target_link_libraries(maxscaled log_manager utils) -install(TARGETS maxscaled DESTINATION modules) +install(TARGETS maxscaled DESTINATION ${MAXSCALE_LIBDIR}) diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index 0de934dec..0ebb4a72e 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -140,7 +140,6 @@ char buf[HTTPD_REQUESTLINE_MAXLEN-1] = ""; char *query_string = NULL; char method[HTTPD_METHOD_MAXLEN-1] = ""; char url[HTTPD_SMALL_BUFFER] = ""; -int cgi = 0; size_t i, j; int headers_read = 0; HTTPD_session *client_data = NULL; @@ -170,9 +169,6 @@ GWBUF *uri; return 0; } - if (strcasecmp(method, "POST") == 0) - cgi = 1; - i = 0; while ( (j < sizeof(buf)) && ISspace(buf[j])) { @@ -195,7 +191,7 @@ GWBUF *uri; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if (*query_string == '?') { - cgi = 1; + *query_string = '\0'; query_string++; } diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index 67d51f9bd..6b1b5e570 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -58,6 +58,7 @@ extern __thread log_info_t tls_log_info; * Revision History * Date Who Description * 13/06/2014 Mark Riddoch Initial implementation + * 07/07/15 Martin Brampton Correct failure handling * * @endverbatim */ @@ -142,13 +143,11 @@ SESSION *session = dcb->session; MAXSCALED *maxscaled = (MAXSCALED *)dcb->protocol; char *password; - if ((n = dcb_read(dcb, &head)) != -1) + if ((n = dcb_read(dcb, &head, 0)) != -1) { if (head) { - unsigned char *ptr = GWBUF_DATA(head); - ptr = GWBUF_DATA(head); if (GWBUF_LENGTH(head)) { switch (maxscaled->state) @@ -270,9 +269,7 @@ int n_connect = 0; { atomic_add(&dcb->stats.n_accepts, 1); client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); - if (client_dcb == NULL) - { close(so); return n_connect; @@ -283,7 +280,8 @@ int n_connect = 0; if ((maxscaled_pr = (MAXSCALED *)malloc(sizeof(MAXSCALED))) == NULL) { client_dcb->protocol = NULL; - dcb_add_to_zombieslist(client_dcb); + close(so); + dcb_close(client_dcb); return n_connect; } maxscaled_pr->username = NULL; @@ -293,9 +291,9 @@ int n_connect = 0; client_dcb->session = session_alloc(dcb->session->service, client_dcb); - if (poll_add_dcb(client_dcb) == -1) + if (poll_add_dcb(client_dcb)) { - dcb_add_to_zombieslist(dcb); + dcb_close(dcb); return n_connect; } n_connect++; diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index ba5786851..c66fad274 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -43,6 +43,7 @@ * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0 * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support * 10/11/2014 Massimiliano Pinto Client charset is passed to backend + * 19/06/2015 Martin Brampton Persistent connection handling * */ #include @@ -72,7 +73,7 @@ static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1); -extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); +extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db, int errcode); static bool sescmd_response_complete(DCB* dcb); @@ -166,6 +167,17 @@ static int gw_read_backend_event(DCB *dcb) { int rc = 0; CHK_DCB(dcb); + if (!dcb->session && dcb->persistentstart) + { + dcb->dcb_errhandle_called = true; + goto return_rc; + } + + if(dcb->session == NULL) + { + goto return_rc; + } + CHK_SESSION(dcb->session); /*< return only with complete session */ @@ -272,14 +284,13 @@ static int gw_read_backend_event(DCB *dcb) { void *rsession = NULL; SESSION *session = dcb->session; int receive_rc = 0; - - CHK_SESSION(session); if (session == NULL) { rc = 0; goto return_with_lock; } + CHK_SESSION(session); router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; @@ -446,7 +457,7 @@ static int gw_read_backend_event(DCB *dcb) { router_instance = session->service->router_instance; /* read available backend data */ - rc = dcb_read(dcb, &read_buffer); + rc = dcb_read(dcb, &read_buffer, 0); if (rc < 0) { @@ -1043,8 +1054,19 @@ gw_backend_hangup(DCB *dcb) session_state_t ses_state; CHK_DCB(dcb); + if (!dcb->session && dcb->persistentstart) + { + dcb->dcb_errhandle_called = true; + goto retblock; + } session = dcb->session; - CHK_SESSION(session); + + if(session == NULL) + { + goto retblock; + } + + CHK_SESSION(session); rsession = session->router_session; router = session->service->router; @@ -1080,7 +1102,7 @@ gw_backend_hangup(DCB *dcb) len = sizeof(error); if (getsockopt(dcb->fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) == 0) { - if (error != 0) + if (error != 0 && ses_state != SESSION_STATE_STOPPING) { strerror_r(error, buf, 100); LOGIF(LE, (skygw_log_write_flush( @@ -1094,9 +1116,12 @@ gw_backend_hangup(DCB *dcb) goto retblock; } #if defined(SS_DEBUG) - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Backend hangup error handling."))); + if(ses_state != SESSION_STATE_STOPPING) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend hangup error handling."))); + } #endif router->handleError(router_instance, @@ -1140,7 +1165,6 @@ gw_backend_close(DCB *dcb) CHK_DCB(dcb); session = dcb->session; - CHK_SESSION(session); LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, "%lu [gw_backend_close]", @@ -1153,21 +1177,24 @@ 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 && + + if (session) + { + CHK_SESSION(session); + /** + * 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->state == SESSION_STATE_STOPPING && session->client != NULL) - { + { if (session->client->state == DCB_STATE_POLLING) { spinlock_release(&session->ses_lock); @@ -1179,11 +1206,12 @@ gw_backend_close(DCB *dcb) { spinlock_release(&session->ses_lock); } - } - else - { + } + else + { spinlock_release(&session->ses_lock); - } + } + } return 1; } @@ -1433,7 +1461,8 @@ static int gw_change_user( message = create_auth_fail_str(username, backend->session->client->remote, password_set, - ""); + "", + auth_ret); if (message == NULL) { LOGIF(LE, (skygw_log_write_flush( diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index abdb4422c..70ddcac84 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -37,7 +37,8 @@ * 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path * 13/10/2014 Massimiliano Pinto Added: dbname authentication check * 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct - * + * 29/05/2015 Markus Makela Added SSL support + * 11/06/2015 Martin Brampton COM_QUIT suppressed for persistent connections */ #include #include @@ -46,6 +47,7 @@ #include #include #include +#include MODULE_INFO info = { MODULE_API_PROTOCOL, @@ -69,14 +71,18 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue); static int gw_error_client_event(DCB *dcb); static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); - +int gw_read_client_event_SSL(DCB* dcb); +int gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue); +int gw_write_client_event_SSL(DCB *dcb); int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); int MySQLSendHandshake(DCB* dcb); -static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); +static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue); static int route_by_statement(SESSION *, GWBUF **); extern char* get_username_from_auth(char* ptr, uint8_t* data); extern int check_db_name_after_auth(DCB *, char *, int); -extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); +extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); + +int do_ssl_accept(MySQLProtocol* protocol); /* * The "module object" for the mysqld client protocol module. @@ -242,7 +248,7 @@ MySQLSendHandshake(DCB* dcb) char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]=""; char *version_string; int len_version_string=0; - + MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); GWBUF *buf; @@ -319,7 +325,16 @@ MySQLSendHandshake(DCB* dcb) mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS; - mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL; + + if(dcb->service->ssl_mode != SSL_DISABLED) + { + mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8; + } + else + { + mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL; + } + memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one)); mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one); @@ -375,21 +390,24 @@ MySQLSendHandshake(DCB* dcb) /** * gw_mysql_do_authentication * - * Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue + * Performs the MySQL protocol 4.1 authentication, using data in GWBUF **queue. * * (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 + * the dcb->data and later to dcb->session->data. client_capabilities are copied + * into the dcb->protocol. + * + * If SSL is enabled for the service, the SSL handshake will be done before the + * MySQL authentication. * * @param dcb Descriptor Control Block of the client - * @param queue The GWBUF with data from client + * @param queue Pointer to the location of 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) { +static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) { + GWBUF* queue = *buf; MySQLProtocol *protocol = NULL; /* int compress = -1; */ int connect_with_db = -1; @@ -402,20 +420,24 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { uint8_t *stage1_hash = NULL; int auth_ret = -1; MYSQL_session *client_data = NULL; - + int ssl = 0; CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); - client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session)); + if(dcb->data == NULL) + { + 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; + 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; + dcb->data = client_data; + } + else + { + client_data = (MYSQL_session *)dcb->data; + } stage1_hash = client_data->client_sha1; username = client_data->user; @@ -437,7 +459,7 @@ 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)) { - return 1; + return MYSQL_FAILED_AUTH; } memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4); @@ -451,11 +473,66 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { &protocol->client_capabilities); */ + /** Skip this if the SSL handshake is already done. + * If not, start the SSL handshake. */ + if(protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE) + { + + ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; + + /** Client didn't requested SSL when SSL mode was required*/ + if(!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED) + { + LOGIF(LT,(skygw_log_write(LT,"User %s@%s connected to service '%s' without SSL when SSL was required.", + protocol->owner_dcb->user, + protocol->owner_dcb->remote, + protocol->owner_dcb->service->name))); + return MYSQL_FAILED_AUTH_SSL; + } + + if(LOG_IS_ENABLED(LT) && ssl) + { + skygw_log_write(LT,"User %s@%s connected to service '%s' with SSL.", + protocol->owner_dcb->user, + protocol->owner_dcb->remote, + protocol->owner_dcb->service->name); + } + + /** Do the SSL Handshake */ + if(ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) + { + protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; + + if(do_ssl_accept(protocol) < 0) + { + return MYSQL_FAILED_AUTH; + } + else + { + return 0; + } + } + else if(dcb->service->ssl_mode == SSL_ENABLED) + { + /** This is a non-SSL connection to a SSL enabled service. + * We have only read enough of the packet to know that the client + * is not requesting SSL and the rest of the auth packet is still + * waiting in the socket. We need to read the data from the socket + * to find out the username of the connecting client. */ + int bytes = dcb_read(dcb,&queue, 0); + queue = gwbuf_make_contiguous(queue); + client_auth_packet = GWBUF_DATA(queue); + client_auth_packet_size = gwbuf_length(queue); + *buf = queue; + LOGIF(LD,(skygw_log_write(LD,"%lu Read %d bytes from fd %d",pthread_self(),bytes,dcb->fd))); + } + } + username = get_username_from_auth(username, client_auth_packet); if (username == NULL) { - return 1; + return MYSQL_FAILED_AUTH; } /* get charset */ @@ -557,6 +634,26 @@ gw_MySQLWrite_client(DCB *dcb, GWBUF *queue) return dcb_write(dcb, queue); } + +/** + * Write function for client DCB: writes data from MaxScale to Client using SSL + * encryption. The SSH handshake must have already been done. + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + */ +int +gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue) +{ + CHK_DCB(dcb); +#ifdef SS_DEBUG + MySQLProtocol *protocol = NULL; + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); +#endif + return dcb_write_SSL(dcb, queue); +} + /** * Client read event triggered by EPOLLIN * @@ -580,9 +677,66 @@ int gw_read_client_event( CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); - rc = dcb_read(dcb, &read_buffer); - - + +#ifdef SS_DEBUG + skygw_log_write(LD,"[gw_read_client_event] Protocol state: %s", + gw_mysql_protocol_state2string(protocol->protocol_auth_state)); + +#endif + + /** SSL authentication is still going on, we need to call do_ssl_accept + * until it return 1 for success or -1 for error */ + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || + protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) + { + + switch(do_ssl_accept(protocol)) + { + case 0: + return 0; + break; + case 1: + { + int b = 0; + ioctl(dcb->fd,FIONREAD,&b); + if(b == 0) + { + skygw_log_write(LD, + "[gw_read_client_event] No data in socket after SSL auth"); + return 0; + } + break; + } + + case -1: + return 1; + break; + default: + return 1; + break; + } + } + + if(protocol->use_ssl) + { + /** SSL handshake is done, communication is now encrypted with SSL */ + rc = dcb_read_SSL(dcb, &read_buffer); + } + else if(dcb->service->ssl_mode != SSL_DISABLED && + protocol->protocol_auth_state == MYSQL_AUTH_SENT) + { + /** The service allows both SSL and non-SSL connections. + * read only enough of the auth packet to know if the client is + * requesting SSL. If the client is not requesting SSL the rest of + the auth packet will be read later. */ + rc = dcb_read(dcb, &read_buffer,(4 + 4 + 4 + 1 + 23)); + } + else + { + /** Normal non-SSL connection */ + rc = dcb_read(dcb, &read_buffer, 0); + } + if (rc < 0) { dcb_close(dcb); @@ -690,8 +844,8 @@ int gw_read_client_event( dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer); nbytes_read = gwbuf_length(dcb->dcb_readqueue); data = (uint8_t *)GWBUF_DATA(dcb->dcb_readqueue); - - if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data)) + int plen = MYSQL_GET_PACKET_LEN(data); + if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data) + 4) { rc = 0; goto return_rc; @@ -719,7 +873,7 @@ int gw_read_client_event( } } - + /** * Now there should be at least one complete mysql packet in read_buffer. */ @@ -729,8 +883,20 @@ int gw_read_client_event( { int auth_val; - auth_val = gw_mysql_do_authentication(dcb, read_buffer); - + auth_val = gw_mysql_do_authentication(dcb, &read_buffer); + + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ || + protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || + protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE || + protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED) + { + /** SSL was requested and the handshake is either done or + * still ongoing. After the handshake is done, the client + * will send another auth packet. */ + while((read_buffer = gwbuf_consume(read_buffer,GWBUF_LENGTH(read_buffer)))); + break; + } + if (auth_val == 0) { SESSION *session; @@ -796,7 +962,7 @@ int gw_read_client_event( fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, dcb->remote, (char*)((MYSQL_session *)dcb->data)->client_sha1, - (char*)((MYSQL_session *)dcb->data)->db); + (char*)((MYSQL_session *)dcb->data)->db,auth_val); modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str); } if (fail_str) @@ -824,10 +990,111 @@ int gw_read_client_event( } break; + case MYSQL_AUTH_SSL_HANDSHAKE_DONE: + { + int auth_val; + + auth_val = gw_mysql_do_authentication(dcb, &read_buffer); + + + if (auth_val == 0) + { + SESSION *session; + + protocol->protocol_auth_state = MYSQL_AUTH_RECV; + /** + * Create session, and a router session for it. + * If successful, there will be backend connection(s) + * after this point. + */ + session = session_alloc(dcb->service, dcb); + + if (session != NULL) + { + CHK_SESSION(session); + ss_dassert(session->state != SESSION_STATE_ALLOC); + + protocol->protocol_auth_state = MYSQL_IDLE; + /** + * Send an AUTH_OK packet to the client, + * packet sequence is # 2 + */ + mysql_send_ok(dcb, 3, 0, NULL); + } + else + { + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] session " + "creation failed. fd %d, " + "state = MYSQL_AUTH_FAILED.", + pthread_self(), + protocol->owner_dcb->fd))); + + /** Send ERR 1045 to client */ + mysql_send_auth_error( + dcb, + 3, + 0, + "failed to create new session"); + + dcb_close(dcb); + } + } + else + { + char* fail_str = NULL; + + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + + if (auth_val == 2) { + /** Send error 1049 to client */ + int message_len = 25 + MYSQL_DATABASE_MAXLEN; + + fail_str = calloc(1, message_len+1); + snprintf(fail_str, message_len, "Unknown database '%s'", + (char*)((MYSQL_session *)dcb->data)->db); + + modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str); + }else { + /** Send error 1045 to client */ + fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, + dcb->remote, + (char*)((MYSQL_session *)dcb->data)->client_sha1, + (char*)((MYSQL_session *)dcb->data)->db,auth_val); + modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str); + } + if (fail_str) + free(fail_str); + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] after " + "gw_mysql_do_authentication, fd %d, " + "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); + } + break; + case MYSQL_IDLE: { uint8_t* payload = NULL; - + session_state_t ses_state; + session = dcb->session; ss_dassert(session!= NULL); @@ -835,93 +1102,108 @@ int gw_read_client_event( { CHK_SESSION(session); } + spinlock_acquire(&session->ses_lock); + ses_state = session->state; + spinlock_release(&session->ses_lock); /* Now, we are assuming in the first buffer there is * the information form mysql command */ payload = GWBUF_DATA(read_buffer); - /** Route COM_QUIT to backend */ - if (MYSQL_IS_COM_QUIT(payload)) - { + if(ses_state == SESSION_STATE_ROUTER_READY) + { + /** Route COM_QUIT to backend */ + if (MYSQL_IS_COM_QUIT(payload)) + { /** * Sends COM_QUIT packets since buffer is already * created. A BREF_CLOSED flag is set so dcb_close won't * send redundant COM_QUIT. */ - SESSION_ROUTE_QUERY(session, read_buffer); - /** - * Close router session which causes closing of backends. - */ + /* Temporarily suppressed: SESSION_ROUTE_QUERY(session, read_buffer); */ + /* Replaced with freeing the read buffer. */ + gwbuf_free(read_buffer); + /** + * Close router session which causes closing of backends. + */ dcb_close(dcb); - } - else - { + } + else + { /** Reset error handler when routing of the new query begins */ router->handleError(NULL, NULL, NULL, dcb, ERRACT_RESET, NULL); if (stmt_input) { - /** - * Feed each statement completely and separately - * to router. - */ - rc = route_by_statement(session, &read_buffer); - - if (read_buffer != NULL) - { - /** add incomplete mysql packet to read queue */ - dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer); - } + /** + * Feed each statement completely and separately + * to router. + */ + rc = route_by_statement(session, &read_buffer); + + if (read_buffer != NULL) + { + /** add incomplete mysql packet to read queue */ + dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer); + } } else { - /** Feed whole packet to router */ - rc = SESSION_ROUTE_QUERY(session, read_buffer); + /** Feed whole packet to router */ + rc = SESSION_ROUTE_QUERY(session, read_buffer); } - + /** Routing succeed */ if (rc) { - rc = 0; /**< here '0' means success */ + rc = 0; /**< here '0' means success */ } else { - bool succp; - GWBUF* errbuf; - /** - * Create error to be sent to client if session - * can't be continued. - */ - errbuf = mysql_create_custom_error( - 1, - 0, - "Routing failed. Session is closed."); - /** - * Ensure that there are enough backends - * available. - */ - router->handleError( - router_instance, - session->router_session, - errbuf, - dcb, - ERRACT_NEW_CONNECTION, - &succp); - gwbuf_free(errbuf); - /** - * If there are not enough backends close - * session - */ - if (!succp) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Routing the query failed. " - "Session will be closed."))); + bool succp; + GWBUF* errbuf; + /** + * Create error to be sent to client if session + * can't be continued. + */ + errbuf = mysql_create_custom_error( + 1, + 0, + "Routing failed. Session is closed."); + /** + * Ensure that there are enough backends + * available. + */ + router->handleError( + router_instance, + session->router_session, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + gwbuf_free(errbuf); + /** + * If there are not enough backends close + * session + */ + if (!succp) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing the query failed. " + "Session will be closed."))); - dcb_close(dcb); - } + dcb_close(dcb); + } } - } + } + } + else + { + skygw_log_write_flush(LT,"Session received a query in state %s", + STRSESSIONSTATE(ses_state)); + while((read_buffer = GWBUF_CONSUME_ALL(read_buffer)) != NULL); + goto return_rc; + } goto return_rc; } /* MYSQL_IDLE */ break; @@ -943,12 +1225,13 @@ return_rc: return rc; } + /////////////////////////////////////////////// // client write event to Client triggered by EPOLLOUT ////////////////////////////////////////////// -/** +/** * @node Client's fd became writable, and EPOLLOUT event - * arrived. As a consequence, client input buffer (writeq) is flushed. + * arrived. As a consequence, client input buffer (writeq) is flushed. * * Parameters: * @param dcb - in, use @@ -956,7 +1239,7 @@ return_rc: * * @return constantly 1 * - * + * * @details (write detailed description here) * */ @@ -966,6 +1249,53 @@ int gw_write_client_event(DCB *dcb) CHK_DCB(dcb); + ss_dassert(dcb->state != DCB_STATE_DISCONNECTED); + + if (dcb == NULL) { + goto return_1; + } + + if (dcb->state == DCB_STATE_DISCONNECTED) { + goto return_1; + } + + if (dcb->protocol == NULL) { + goto return_1; + } + protocol = (MySQLProtocol *)dcb->protocol; + CHK_PROTOCOL(protocol); + + if (protocol->protocol_auth_state == MYSQL_IDLE) + { + dcb_drain_writeq(dcb); + goto return_1; + } + +return_1: +#if defined(SS_DEBUG) + if (dcb->state == DCB_STATE_POLLING || + dcb->state == DCB_STATE_NOPOLLING || + dcb->state == DCB_STATE_ZOMBIE) + { + CHK_PROTOCOL(protocol); + } +#endif + return 1; +} + +/** + * EPOLLOUT event arrived and as a consequence, client input buffer (writeq) is + * flushed. The data is encrypted and SSL is used. The SSL handshake must have + * been successfully completed prior to this function being called. + * @param client dcb + * @return constantly 1 + */ +int gw_write_client_event_SSL(DCB *dcb) +{ + MySQLProtocol *protocol = NULL; + + CHK_DCB(dcb); + ss_dassert(dcb->state != DCB_STATE_DISCONNECTED); if (dcb == NULL) { @@ -984,7 +1314,7 @@ int gw_write_client_event(DCB *dcb) if (protocol->protocol_auth_state == MYSQL_IDLE) { - dcb_drain_writeq(dcb); + dcb_drain_writeq_SSL(dcb); goto return_1; } @@ -1014,7 +1344,7 @@ int gw_MySQLListener( struct sockaddr *current_addr; int one = 1; int rc; - + bool is_tcp = false; memset(&serv_addr,0,sizeof(serv_addr)); memset(&local_addr,0,sizeof(local_addr)); @@ -1055,6 +1385,7 @@ int gw_MySQLListener( } current_addr = (struct sockaddr *) &serv_addr; + is_tcp = true; } listen_dcb->fd = -1; @@ -1064,7 +1395,12 @@ int gw_MySQLListener( LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); } - + if(is_tcp) + { + if((syseno = setsockopt(l_so, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one))) != 0){ + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno)))); + } + } // set NONBLOCKING mode setnonblocking(l_so); @@ -1135,10 +1471,8 @@ int gw_MySQLListener( // add listening socket to poll structure if (poll_add_dcb(listen_dcb) == -1) { fprintf(stderr, - "\n* Failed to start polling the socket due error " - "%i, %s.\n\n", - errno, - strerror(errno)); + "\n* MaxScale encountered system limit while " + "attempting to register on an epoll instance.\n\n"); return 0; } #if defined(FAKE_CODE) @@ -1371,7 +1705,8 @@ int gw_MySQLAccept(DCB *listener) client_dcb, 1, 0, - "MaxScale internal error."); + "MaxScale encountered system limit while " + "attempting to register on an epoll instance."); /** close client_dcb */ dcb_close(client_dcb); @@ -1605,61 +1940,79 @@ return_rc: return rc; } - /** - * Create a character array including the query string. - * GWBUF given as input includes either one complete or partial query. - * Length of buffer is at most the query length+4 (length of packet header). + * Do the SSL authentication handshake. + * This creates the DCB SSL structure if one has not been created and starts the + * SSL handshake handling. + * @param protocol Protocol to connect with SSL + * @return 1 on success, 0 when the handshake is ongoing or -1 on error */ -#if defined(NOT_USED) -static char* gw_get_or_create_querystr ( - void* data, - bool* new_allocation) +int do_ssl_accept(MySQLProtocol* protocol) { - GWBUF* buf = (GWBUF *)data; - size_t buflen; /*< first gw buffer data length */ - size_t packetlen; /*< length of mysql packet */ - size_t querylen; /*< total buffer length- */ - size_t nbytes_copied; - char* startpos; /*< first byte of query in gw buffer */ - char* str; /*< resulting query string */ - - CHK_GWBUF(buf); - packetlen = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf)); - str = (char *)malloc(packetlen); /*< leave space for terminating null */ - - if (str == NULL) - { - goto return_str; - } - *new_allocation = true; - /** - * First buffer includes 4 bytes header and a type indicator byte. - */ - buflen = GWBUF_LENGTH(buf); - querylen = packetlen-1; - ss_dassert(buflen<=querylen+5); /*< 5 == header+type indicator */ - startpos = (char *)GWBUF_DATA(buf)+5; - nbytes_copied = MIN(querylen, buflen-5); - memcpy(str, startpos, nbytes_copied); - memset(&str[querylen-1], 0, 1); - buf = gwbuf_consume(buf, querylen-1); - - /** - * In case of multi-packet statement whole buffer consists of query - * string. - */ - while (buf != NULL) - { - buflen = GWBUF_LENGTH(buf); - memcpy(str+nbytes_copied, GWBUF_DATA(buf), buflen); - nbytes_copied += buflen; - buf = gwbuf_consume(buf, buflen); - } - ss_dassert(str[querylen-1] == 0); - -return_str: - return str; -} + int rval,errnum; + char errbuf[2014]; + DCB* dcb = protocol->owner_dcb; + if(dcb->ssl == NULL) + { + if(dcb_create_SSL(dcb) != 0) + { + return -1; + } + } + + rval = dcb_accept_SSL(dcb); + + switch(rval) + { + case 0: + /** Not all of the data has been read. Go back to the poll + queue and wait for more.*/ + + rval = 0; + skygw_log_write_flush(LT,"SSL_accept ongoing for %s@%s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote); + return 0; + break; + case 1: + spinlock_acquire(&protocol->protocol_lock); + protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE; + protocol->use_ssl = true; + spinlock_release(&protocol->protocol_lock); + + spinlock_acquire(&dcb->authlock); + dcb->func.write = gw_MySQLWrite_client_SSL; + dcb->func.write_ready = gw_write_client_event_SSL; + spinlock_release(&dcb->authlock); + + rval = 1; + + skygw_log_write_flush(LT,"SSL_accept done for %s@%s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote); + break; + + case -1: + + spinlock_acquire(&protocol->protocol_lock); + protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED; + spinlock_release(&protocol->protocol_lock); + rval = -1; + skygw_log_write_flush(LE, + "Error: Fatal error in SSL_accept for %s", + protocol->owner_dcb->remote); + break; + + default: + skygw_log_write_flush(LE, + "Error: Fatal error in SSL_accept, returned value was %d.", + rval); + break; + } +#ifdef SS_DEBUG + skygw_log_write(LD,"[do_ssl_accept] Protocol state: %s", + gw_mysql_protocol_state2string(protocol->protocol_auth_state)); #endif + return rval; +} diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 0e85f51ed..da38510b5 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -36,6 +36,7 @@ * 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts. * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support * 10/11/2014 Massimiliano Pinto Charset at connect is passed to backend during authentication + * 07/07/15 Martin Brampton Fix problem recognising null password * */ @@ -44,6 +45,10 @@ #include #include #include +#include + +/* The following can be compared using memcmp to detect a null password */ +uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]=""; /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -137,7 +142,7 @@ void mysql_protocol_done ( goto retblock; } scmd = p->protocol_cmd_history; - + while (scmd != NULL) { scmd2 = scmd->scom_next; @@ -168,7 +173,7 @@ int gw_read_backend_handshake( int success = 0; int packet_len = 0; - if ((n = dcb_read(dcb, &head)) != -1) + if ((n = dcb_read(dcb, &head, 0)) != -1) { dcb->last_read = hkheartbeat; @@ -287,7 +292,7 @@ int gw_read_backend_handshake( pthread_self(), conn->owner_dcb->fd, pthread_self()))); - while(head = gwbuf_consume(head, GWBUF_LENGTH(head))); + while((head = gwbuf_consume(head, GWBUF_LENGTH(head)))); return 1; } @@ -421,7 +426,7 @@ int gw_receive_backend_auth( uint8_t *ptr = NULL; int rc = 0; - n = dcb_read(dcb, &head); + n = dcb_read(dcb, &head, 0); dcb->last_read = hkheartbeat; @@ -576,7 +581,7 @@ int gw_send_authentication_to_backend( if (strlen(dbname)) curr_db = dbname; - if (strlen((char *)passwd)) + if (memcmp(passwd, null_client_sha1, MYSQL_SCRAMBLE_LEN)) curr_passwd = passwd; dcb = conn->owner_dcb; @@ -812,6 +817,23 @@ int gw_do_connect_to_backend( goto close_so; } + int one = 1; + if(setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to set socket options " + "%s:%d failed.\n\t\t Socket configuration failed " + "due %d, %s.", + host, + port, + errno, + strerror(errno)))); + rv = -1; + /** Close socket */ + goto close_so; + } + /* set socket to as non-blocking here */ setnonblocking(so); rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); @@ -890,7 +912,11 @@ gw_mysql_protocol_state2string (int state) { case MYSQL_AUTH_FAILED: return "MySQL Authentication failed"; case MYSQL_IDLE: - return "MySQL authentication is succesfully done."; + return "MySQL authentication is succesfully done."; + case MYSQL_AUTH_SSL_REQ: return "MYSQL_AUTH_SSL_REQ"; + case MYSQL_AUTH_SSL_HANDSHAKE_DONE: return "MYSQL_AUTH_SSL_HANDSHAKE_DONE"; + case MYSQL_AUTH_SSL_HANDSHAKE_FAILED: return "MYSQL_AUTH_SSL_HANDSHAKE_FAILED"; + case MYSQL_AUTH_SSL_HANDSHAKE_ONGOING: return "MYSQL_AUTH_SSL_HANDSHAKE_ONGOING"; default: return "MySQL (unknown protocol state)"; } @@ -1100,7 +1126,7 @@ GWBUF* gw_create_change_user_packet( curr_db = db; } - if (strlen((char *)pwd) > 0) + if (memcmp(pwd, null_client_sha1, MYSQL_SCRAMBLE_LEN)) { curr_passwd = pwd; } @@ -1319,7 +1345,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le if (ret_val) { /* if password was sent, fill stage1_hash with at least 1 byte in order - * to create rigth error message: (using password: YES|NO) + * to create right error message: (using password: YES|NO) */ if (token_len) memcpy(stage1_hash, (char *)"_", 1); @@ -1336,12 +1362,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH); } else { /* check if the password is not set in the user table */ - if (!strlen((char *)password)) { - /* Username without password */ - return 0; - } else { - return 1; - } + return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? 1 : 0; } /*< @@ -1362,7 +1383,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le /*< * step2: STEP2 = XOR(token, STEP1) * - * token is trasmitted form client and it's based on the handshake scramble and SHA1(real_passowrd) + * token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd) * step1 has been computed in the previous step * the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long */ @@ -1530,13 +1551,6 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, break; } - /** See if ANYDB == Y */ - if(key.resource) - { - key.resource = NULL; - continue; - } - if (!user_password) { /* * user@% not found. @@ -2199,7 +2213,8 @@ char *create_auth_fail_str( char *username, char *hostaddr, char *sha1, - char *db) + char *db, + int errcode) { char* errstr; const char* ferrstr; @@ -2214,6 +2229,10 @@ char *create_auth_fail_str( { ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'"; } + else if(errcode == MYSQL_FAILED_AUTH_SSL) + { + ferrstr = "Access without SSL denied"; + } else { ferrstr = "Access denied for user '%s'@'%s' (using password: %s)"; @@ -2233,6 +2252,10 @@ char *create_auth_fail_str( { sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db); } + else if(errcode == MYSQL_FAILED_AUTH_SSL) + { + sprintf(errstr, ferrstr); + } else { sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index ab5c95b47..8274c91cf 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -67,6 +67,7 @@ extern __thread log_info_t tls_log_info; * Date Who Description * 17/06/2013 Mark Riddoch Initial version * 17/07/2013 Mark Riddoch Addition of login phase + * 07/07/2015 Martin Brampton Call unified dcb_close on error * * @endverbatim */ @@ -154,7 +155,7 @@ SESSION *session = dcb->session; TELNETD *telnetd = (TELNETD *)dcb->protocol; char *password, *t; - if ((n = dcb_read(dcb, &head)) != -1) + if ((n = dcb_read(dcb, &head, 0)) != -1) { if (head) @@ -315,13 +316,13 @@ int n_connect = 0; if (telnetd_pr == NULL) { - dcb_add_to_zombieslist(client_dcb); + dcb_close(client_dcb); return n_connect; } - if (poll_add_dcb(client_dcb) == -1) + if (poll_add_dcb(client_dcb)) { - dcb_add_to_zombieslist(dcb); + dcb_close(dcb); return n_connect; } n_connect++; diff --git a/server/modules/protocol/test/CMakeLists.txt b/server/modules/protocol/test/CMakeLists.txt new file mode 100644 index 000000000..26653901c --- /dev/null +++ b/server/modules/protocol/test/CMakeLists.txt @@ -0,0 +1,11 @@ +configure_file(test_ssl.sh ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh @ONLY) +configure_file(no_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_ca.cnf @ONLY) +configure_file(no_server_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_cert.cnf @ONLY) +configure_file(no_server_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_key.cnf @ONLY) +configure_file(bad_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ca.cnf @ONLY) +configure_file(bad_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_cert.cnf @ONLY) +configure_file(bad_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_key.cnf @ONLY) +configure_file(bad_ssl.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl.cnf @ONLY) +configure_file(bad_ssl_version.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl_version.cnf @ONLY) +configure_file(ok.cnf ${CMAKE_CURRENT_BINARY_DIR}/ok.cnf @ONLY) +add_test(NAME SSLTest COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh) diff --git a/server/modules/protocol/test/bad_ca.cnf b/server/modules/protocol/test/bad_ca.cnf new file mode 100644 index 000000000..9206679a9 --- /dev/null +++ b/server/modules/protocol/test/bad_ca.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=This is not a value +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/bad_cert.cnf b/server/modules/protocol/test/bad_cert.cnf new file mode 100644 index 000000000..1b4c776cc --- /dev/null +++ b/server/modules/protocol/test/bad_cert.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=This is not a value +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/bad_key.cnf b/server/modules/protocol/test/bad_key.cnf new file mode 100644 index 000000000..4e0be5f05 --- /dev/null +++ b/server/modules/protocol/test/bad_key.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=This is not a value + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/bad_ssl.cnf b/server/modules/protocol/test/bad_ssl.cnf new file mode 100644 index 000000000..f6dcff1a1 --- /dev/null +++ b/server/modules/protocol/test/bad_ssl.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=testing +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/bad_ssl_version.cnf b/server/modules/protocol/test/bad_ssl_version.cnf new file mode 100644 index 000000000..6849b1904 --- /dev/null +++ b/server/modules/protocol/test/bad_ssl_version.cnf @@ -0,0 +1,29 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key +ssl_version=Don't use SSL, it's not needed! + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/no_ca.cnf b/server/modules/protocol/test/no_ca.cnf new file mode 100644 index 000000000..56f603f6a --- /dev/null +++ b/server/modules/protocol/test/no_ca.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +#ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/no_server_cert.cnf b/server/modules/protocol/test/no_server_cert.cnf new file mode 100644 index 000000000..f714a0b3f --- /dev/null +++ b/server/modules/protocol/test/no_server_cert.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +#ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/no_server_key.cnf b/server/modules/protocol/test/no_server_key.cnf new file mode 100644 index 000000000..a820ee414 --- /dev/null +++ b/server/modules/protocol/test/no_server_key.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +#ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/ok.cnf b/server/modules/protocol/test/ok.cnf new file mode 100644 index 000000000..089025c0d --- /dev/null +++ b/server/modules/protocol/test/ok.cnf @@ -0,0 +1,28 @@ +[maxscale] +threads=1 +logdir=@CMAKE_CURRENT_BINARY_DIR@ +datadir=@CMAKE_CURRENT_BINARY_DIR@ +piddir=@CMAKE_CURRENT_BINARY_DIR@ +cachedir=@CMAKE_CURRENT_BINARY_DIR@ + +[Testservice] +type=service +router=readconnroute +servers=server1 +user=user +passwd=pwd +ssl=enabled +ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca +ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert +ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key + +[Testlistener] +type=listener +service=Testservice +protocol=MySQLBackend +port=12345 + +[server1] +type=server +address=127.0.0.1 +port=4321 diff --git a/server/modules/protocol/test/test_ssl.sh b/server/modules/protocol/test/test_ssl.sh new file mode 100755 index 000000000..f14670689 --- /dev/null +++ b/server/modules/protocol/test/test_ssl.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +function create_certs() +{ + echo "CA cert" > @CMAKE_CURRENT_BINARY_DIR@/ca.pem + echo "Server Certificate" > @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem + echo "Server Key" > @CMAKE_CURRENT_BINARY_DIR@/server-key.pem +} + +function start_maxscale () +{ + local result=$(@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale -d -f $1 &> $1.log;echo $?) + if [[ $result == "0" ]] + then + echo "Error: $1 exited with status $result!" + exit 1 + fi +} + +# All test cases expect that MaxScale will not start with a bad configuration or missing certificates + +# No CA defined +printf "Testing No CA defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_ca.cnf +echo " OK" + +# No cert defined +printf "Testing No cert defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_cert.cnf +echo " OK" + +# No key defined +printf "Testing No key defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_key.cnf +echo " OK" + +# Bad SSL value defined +printf "Testing Bad SSL defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl.cnf +echo " OK" + +# Bad SSL version defined +printf "Testing Bad SSL version defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl_version.cnf +echo " OK" + +# Bad CA value defined +printf "Testing Bad CA defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ca.cnf +echo " OK" + +# Bad server certificate defined +printf "Testing Bad cert defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_cert.cnf +echo " OK" + +# Bad server key defined +printf "Testing Bad key defined" +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_key.cnf +echo " OK" + +# No CA file +printf "Testing No CA file" +create_certs +rm @CMAKE_CURRENT_BINARY_DIR@/ca.pem +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf +echo " OK" + +# No server certificate file +printf "Testing No cert file" +create_certs +rm @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf +echo " OK" + +# No server key file +printf "Testing No key file" +create_certs +rm @CMAKE_CURRENT_BINARY_DIR@/server-key.pem +start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf +echo " OK" + +exit 0 diff --git a/server/modules/routing/CMakeLists.txt b/server/modules/routing/CMakeLists.txt index 02535b7b9..d51c8260b 100644 --- a/server/modules/routing/CMakeLists.txt +++ b/server/modules/routing/CMakeLists.txt @@ -2,20 +2,20 @@ if(BUILD_TESTS) add_subdirectory(test) add_library(testroute SHARED testroute.c) target_link_libraries(testroute log_manager utils) - install(TARGETS testroute DESTINATION modules) + install(TARGETS testroute DESTINATION ${MAXSCALE_LIBDIR}) endif() add_library(readconnroute SHARED readconnroute.c) target_link_libraries(readconnroute log_manager utils) -install(TARGETS readconnroute DESTINATION modules) +install(TARGETS readconnroute DESTINATION ${MAXSCALE_LIBDIR}) add_library(debugcli SHARED debugcli.c debugcmd.c) target_link_libraries(debugcli log_manager utils) -install(TARGETS debugcli DESTINATION modules) +install(TARGETS debugcli DESTINATION ${MAXSCALE_LIBDIR}) add_library(cli SHARED cli.c debugcmd.c) target_link_libraries(cli log_manager utils) -install(TARGETS cli DESTINATION modules) +install(TARGETS cli DESTINATION ${MAXSCALE_LIBDIR}) add_subdirectory(readwritesplit) add_subdirectory(schemarouter) diff --git a/server/modules/routing/binlog/CMakeLists.txt b/server/modules/routing/binlog/CMakeLists.txt index 4de2a35b4..9a0c245de 100644 --- a/server/modules/routing/binlog/CMakeLists.txt +++ b/server/modules/routing/binlog/CMakeLists.txt @@ -1,4 +1,4 @@ add_library(binlogrouter SHARED blr.c blr_master.c blr_cache.c blr_slave.c blr_file.c) -set_target_properties(binlogrouter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib) +set_target_properties(binlogrouter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${MAXSCALE_LIBDIR}) target_link_libraries(binlogrouter ssl pthread log_manager) -install(TARGETS binlogrouter DESTINATION modules) +install(TARGETS binlogrouter DESTINATION ${MAXSCALE_LIBDIR}) diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 51106e638..0a90b7a71 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -35,6 +35,8 @@ * 02/04/2014 Mark Riddoch Initial implementation * 17/02/2015 Massimiliano Pinto Addition of slave port and username in diagnostics * 18/02/2015 Massimiliano Pinto Addition of dcb_close in closeSession + * 07/05/2015 Massimiliano Pinto Addition of MariaDB 10 compatibility support + * 12/06/2015 Massimiliano Pinto Addition of MariaDB 10 events in diagnostics() * 29/06/2015 Massimiliano Pinto Addition of master.ini for easy startup configuration * If not found router goes into BLRM_UNCONFIGURED state. * Cache dir is 'cache' under router->binlogdir. @@ -117,6 +119,9 @@ static void stats_func(void *); static bool rses_begin_locked_router_action(ROUTER_SLAVE *); static void rses_end_locked_router_action(ROUTER_SLAVE *); +void my_uuid_init(ulong seed1, ulong seed2); +void my_uuid(char *guid); +GWBUF *blr_cache_read_response(ROUTER_INSTANCE *router, char *response); static SPINLOCK instlock; static ROUTER_INSTANCE *instances; @@ -184,8 +189,45 @@ char path[PATH_MAX+1] = ""; char filename[PATH_MAX+1] = ""; int master_info = 0; int rc = 0; +char *defuuid; - if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { + if(service->credentials.name == NULL || + service->credentials.authdata == NULL) + { + skygw_log_write(LE,"%s: Error: Service is missing user credentials." + " Add the missing username or passwd parameter to the service.", + service->name); + return NULL; + } + + if(options == NULL || options[0] == NULL) + { + skygw_log_write(LE, + "%s: Error: No router options supplied for binlogrouter", + service->name); + return NULL; + } + + /* + * We only support one server behind this router, since the server is + * the master from which we replicate binlog records. Therefore check + * that only one server has been defined. + * + * A later improvement will be to define multiple servers and have the + * router use the information that is supplied by the monitor to find + * which of these servers is currently the master and replicate from + * that server. + */ + if (service->dbref == NULL || service->dbref->next != NULL) + { + skygw_log_write(LE, + "%s: Error : Exactly one database server may be " + "for use with the binlog router.", + service->name); + return NULL; + } + + if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; } @@ -215,6 +257,7 @@ int rc = 0; inst->retry_backoff = 1; inst->binlogdir = NULL; inst->heartbeat = 300; // Default is every 5 minutes + inst->mariadb10_compat = false; inst->user = strdup(service->credentials.name); inst->password = strdup(service->credentials.authdata); @@ -234,22 +277,6 @@ int rc = 0; defuuid[12], defuuid[13], defuuid[14], defuuid[15]); } - /* - * We only support one server behind this router, since the server is - * the master from which we replicate binlog records. Therefore check - * that only one server has been defined. - * - */ - - if (service->dbref && service->dbref->next != NULL) - { - LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, - "Error : Exactly one database server may be " - "for use with the binlog router."))); - /* report as error whether a server is defined in the service */ - } - /* * Process the options. * We have an array of attribute values passed to us that we must @@ -302,6 +329,10 @@ int rc = 0; { inst->masterid = atoi(value); } + else if (strcmp(options[i], "mariadb10-compatibility") == 0) + { + inst->mariadb10_compat = config_truth_value(value); + } else if (strcmp(options[i], "filestem") == 0) { inst->fileroot = strdup(value); @@ -376,7 +407,7 @@ int rc = 0; else { LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, "%s: No router options supplied for binlogrouter", + LOGFILE_ERROR, "%s: Error: No router options supplied for binlogrouter", service->name))); } @@ -508,6 +539,7 @@ int rc = 0; /* * Read any cached response messages */ + blr_cache_read_master_data(inst); /* @@ -627,6 +659,7 @@ ROUTER_SLAVE *slave; strcpy(slave->binlogfile, "unassigned"); slave->connect_time = time(0); slave->lastEventTimestamp = 0; + slave->mariadb10_compat = false; /** * Add this session to the list of active sessions. @@ -810,6 +843,15 @@ static char *event_names[] = { "Anonymous GTID Event", "Previous GTIDS Event" }; +/* New MariaDB event numbers starts from 0xa0 */ +static char *event_names_mariadb10[] = { + "Annotate Rows Event", + /* New MariaDB 10.x event numbers */ + "Binlog Checkpoint Event", + "GTID Event", + "GTID List Event" +}; + /** * Display an entry from the spinlock statistics data * @@ -929,14 +971,31 @@ struct tm tm; buf); dcb_printf(dcb, "\t (%d seconds ago)\n", time(0) - router_inst->stats.lastReply); - dcb_printf(dcb, "\tLast event from master: 0x%x, %s", + + if (!router_inst->mariadb10_compat) { + dcb_printf(dcb, "\tLast event from master: 0x%x, %s", router_inst->lastEventReceived, (router_inst->lastEventReceived >= 0 && - router_inst->lastEventReceived < 0x24) ? + router_inst->lastEventReceived <= MAX_EVENT_TYPE) ? event_names[router_inst->lastEventReceived] : "unknown"); + } else { + char *ptr = NULL; + if (router_inst->lastEventReceived >= 0 && router_inst->lastEventReceived <= MAX_EVENT_TYPE) { + ptr = event_names[router_inst->lastEventReceived]; + } else { + /* Check MariaDB 10 new events */ + if (router_inst->lastEventReceived >= MARIADB_NEW_EVENTS_BEGIN && router_inst->lastEventReceived <= MAX_EVENT_TYPE_MARIADB10) { + ptr = event_names_mariadb10[(router_inst->lastEventReceived - MARIADB_NEW_EVENTS_BEGIN)]; + } + } + + dcb_printf(dcb, "\tLast event from master: 0x%x, %s", + router_inst->lastEventReceived, (ptr != NULL) ? ptr : "unknown"); + } + if (router_inst->lastEventTimestamp) { - localtime_r(&router_inst->lastEventTimestamp, &tm); + localtime_r((const time_t*)&router_inst->lastEventTimestamp, &tm); asctime_r(&tm, buf); dcb_printf(dcb, "\tLast binlog event timestamp: %ld (%s)\n", router_inst->lastEventTimestamp, buf); @@ -946,11 +1005,17 @@ struct tm tm; if (router_inst->reconnect_pending) dcb_printf(dcb, "\tRouter pending reconnect to master\n"); dcb_printf(dcb, "\tEvents received:\n"); - for (i = 0; i < 0x24; i++) + for (i = 0; i <= MAX_EVENT_TYPE; i++) { dcb_printf(dcb, "\t\t%-38s %u\n", event_names[i], router_inst->stats.events[i]); } + if (router_inst->mariadb10_compat) { + /* Display MariaDB 10 new events */ + for (i = MARIADB_NEW_EVENTS_BEGIN; i <= MAX_EVENT_TYPE_MARIADB10; i++) + dcb_printf(dcb, "\t\tMariaDB 10 %-38s %u\n", event_names_mariadb10[(i - MARIADB_NEW_EVENTS_BEGIN)], router_inst->stats.events[i]); + } + #if SPINLOCK_PROFILE dcb_printf(dcb, "\tSpinlock statistics (instlock):\n"); spinlock_stats(&instlock, spin_reporter, dcb); @@ -1058,7 +1123,7 @@ struct tm tm; if (session->lastEventTimestamp && router_inst->lastEventTimestamp) { - localtime_r(&session->lastEventTimestamp, &tm); + localtime_r((const time_t*)&session->lastEventTimestamp, &tm); asctime_r(&tm, buf); dcb_printf(dcb, "\t\tLast binlog event timestamp %u, %s", session->lastEventTimestamp, buf); dcb_printf(dcb, "\t\tSeconds behind master %u\n", router_inst->lastEventTimestamp - session->lastEventTimestamp); @@ -1156,7 +1221,8 @@ static void errorReply(ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb, error_action_t action, bool *succp) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; -int error, len; +int error; +socklen_t len; char msg[85], *errmsg; unsigned long mysql_errno; @@ -1329,10 +1395,10 @@ unsigned long 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, + (unsigned int)(time(0) - router->connect_time), + (unsigned int)config_threadcount(), + (unsigned int)router->stats.n_binlogs_ses, + (unsigned int)router->stats.n_slaves, blrm_states[router->master_state]); if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL) return 0; @@ -1684,5 +1750,24 @@ int mkdir_rval; strncat(path, "/dbusers", PATH_MAX); return dbusers_save(service->users, path); - +} + +/** + * Extract a numeric field from a packet of the specified number of bits + * + * @param src The raw packet source + * @param birs The number of bits to extract (multiple of 8) + */ +uint32_t +extract_field(uint8_t *src, int bits) +{ +uint32_t rval = 0, shift = 0; + + while (bits > 0) + { + rval |= (*src++) << shift; + shift += 8; + bits -= 8; + } + return rval; } diff --git a/server/modules/routing/binlog/blr_file.c b/server/modules/routing/binlog/blr_file.c index 940c9905b..a627d788e 100644 --- a/server/modules/routing/binlog/blr_file.c +++ b/server/modules/routing/binlog/blr_file.c @@ -25,6 +25,7 @@ * * Date Who Description * 14/04/2014 Mark Riddoch Initial implementation + * 07/05/2015 Massimiliano Pinto Added MAX_EVENT_TYPE_MARIADB10 * 08/06/2015 Massimiliano Pinto Addition of blr_cache_read_master_data() * 15/06/2015 Massimiliano Pinto Addition of blr_file_get_next_binlogname() * 23/06/2015 Massimiliano Pinto Addition of blr_file_use_binlog, blr_file_create_binlog @@ -50,7 +51,7 @@ #include #include #include - +#include #include #include #include @@ -62,7 +63,6 @@ extern __thread log_info_t tls_log_info; 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); void blr_cache_read_master_data(ROUTER_INSTANCE *router); int blr_file_get_next_binlogname(ROUTER_INSTANCE *router); @@ -81,7 +81,7 @@ int blr_file_write_master_config(ROUTER_INSTANCE *router, char *error); int blr_file_init(ROUTER_INSTANCE *router) { -char *ptr, path[PATH_MAX], filename[PATH_MAX]; +char *ptr, path[PATH_MAX+1], filename[PATH_MAX+1]; int file_found, n = 1; int root_len, i; DIR *dirp; @@ -89,12 +89,8 @@ struct dirent *dp; if (router->binlogdir == NULL) { - strcpy(path, "/usr/local/mariadb-maxscale"); - if ((ptr = getenv("MAXSCALE_HOME")) != NULL) - { - strncpy(path, ptr,PATH_MAX); - } - strncat(path, "/",PATH_MAX); + strcpy(path, get_datadir()); + strncat(path,"/",PATH_MAX); strncat(path, router->service->name,PATH_MAX); if (access(path, R_OK) == -1) @@ -452,15 +448,26 @@ struct stat statb; hdr->next_pos = EXTRACT32(&hdbuf[13]); hdr->flags = EXTRACT16(&hdbuf[17]); - if (hdr->event_type > MAX_EVENT_TYPE) - { - LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, - "Invalid event type 0x%x. " + if (router->mariadb10_compat) { + if (hdr->event_type > MAX_EVENT_TYPE_MARIADB10) { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "Invalid MariaDB 10 event type 0x%x. " "Binlog file is %s, position %d", hdr->event_type, file->binlogname, pos))); - return NULL; - } + return NULL; + } + } else { + if (hdr->event_type > MAX_EVENT_TYPE) { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "Invalid event type 0x%x. " + "Binlog file is %s, position %d", + hdr->event_type, + file->binlogname, pos))); + + return NULL; + } + } if (hdr->next_pos < pos && hdr->event_type != ROTATE_EVENT) { @@ -600,26 +607,6 @@ blr_close_binlog(ROUTER_INSTANCE *router, BLFILE *file) free(file); } -/** - * Extract a numeric field from a packet of the specified number of bits - * - * @param src The raw packet source - * @param birs The number of bits to extract (multiple of 8) - */ -static uint32_t -extract_field(uint8_t *src, int bits) -{ -uint32_t rval = 0, shift = 0; - - while (bits > 0) - { - rval |= (*src++) << shift; - shift += 8; - bits -= 8; - } - return rval; -} - /** * Log the event header of binlog event * @@ -671,10 +658,10 @@ struct stat statb; void blr_cache_response(ROUTER_INSTANCE *router, char *response, GWBUF *buf) { -char path[4097], *ptr; +char path[PATH_MAX+1], *ptr; int fd; - strncpy(path, router->binlogdir, 4096); + strncpy(path, router->binlogdir, PATH_MAX); strncat(path, "/cache", 4096); if (access(path, R_OK) == -1) { @@ -682,8 +669,8 @@ int fd; mkdir_ret = mkdir(path, 0700); } - strncat(path, "/", 4096); - strncat(path, response, 4096); + strncat(path, "/", PATH_MAX); + strncat(path, response, PATH_MAX); if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1) return; @@ -707,14 +694,14 @@ GWBUF * blr_cache_read_response(ROUTER_INSTANCE *router, char *response) { struct stat statb; -char path[4097], *ptr; +char path[PATH_MAX+1], *ptr; int fd; GWBUF *buf; - strncpy(path, router->binlogdir, 4096); - strncat(path, "/cache", 4096); - strncat(path, "/", 4096); - strncat(path, response, 4096); + strncpy(path, router->binlogdir, PATH_MAX); + strncat(path, "/cache", PATH_MAX); + strncat(path, "/", PATH_MAX); + strncat(path, response, PATH_MAX); if ((fd = open(path, O_RDONLY)) == -1) return NULL; diff --git a/server/modules/routing/binlog/blr_master.c b/server/modules/routing/binlog/blr_master.c index 47f7a4403..cbd8d78c8 100644 --- a/server/modules/routing/binlog/blr_master.c +++ b/server/modules/routing/binlog/blr_master.c @@ -32,12 +32,13 @@ * Revision History * * Date Who Description - * 02/04/2014 Mark Riddoch Initial implementation - * 25/05/2015 Massimiliano Pinto Added BLRM_SLAVE_STOPPED state - * 08/06/2015 Massimiliano Pinto Added m_errno and m_errmsg - * 23/06/2015 Massimiliano Pinto Master communication goes into BLRM_SLAVE_STOPPED state - * when an error is encountered in BLRM_BINLOGDUMP state. - * Server error code and msg are reported via SHOW SLAVE STATUS + * 02/04/2014 Mark Riddoch Initial implementation + * 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility + * 25/05/2015 Massimiliano Pinto Added BLRM_SLAVE_STOPPED state + * 08/06/2015 Massimiliano Pinto Added m_errno and m_errmsg + * 23/06/2015 Massimiliano Pinto Master communication goes into BLRM_SLAVE_STOPPED state + * when an error is encountered in BLRM_BINLOGDUMP state. + * Server error code and msg are reported via SHOW SLAVE STATUS * * @endverbatim */ @@ -81,11 +82,11 @@ static int blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *pkt, REP_HEADER * 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); void blr_master_close(ROUTER_INSTANCE *); static char *blr_extract_column(GWBUF *buf, int col); - +void blr_cache_response(ROUTER_INSTANCE *router, char *response, GWBUF *buf); +void poll_fake_write_event(DCB *dcb); static int keepalive = 1; /** @@ -96,8 +97,9 @@ static int keepalive = 1; * @param router The router instance */ void -blr_start_master(ROUTER_INSTANCE *router) +blr_start_master(void* data) { + ROUTER_INSTANCE *router = (ROUTER_INSTANCE*)data; DCB *client; GWBUF *buf; @@ -488,11 +490,27 @@ char query[128]; GWBUF_CONSUME_ALL(router->saved_master.chksum2); router->saved_master.chksum2 = buf; blr_cache_response(router, "chksum2", buf); - buf = blr_make_query("SELECT @@GLOBAL.GTID_MODE"); - router->master_state = BLRM_GTIDMODE; + + if (router->mariadb10_compat) { + buf = blr_make_query("SET @mariadb_slave_capability=4"); + router->master_state = BLRM_MARIADB10; + } else { + buf = blr_make_query("SELECT @@GLOBAL.GTID_MODE"); + router->master_state = BLRM_GTIDMODE; + } router->master->func.write(router->master, buf); break; } + case BLRM_MARIADB10: + // Response to the SET @mariadb_slave_capability=4, should be stored + if (router->saved_master.mariadb10) + GWBUF_CONSUME_ALL(router->saved_master.mariadb10); + router->saved_master.mariadb10 = buf; + blr_cache_response(router, "mariadb10", buf); + buf = blr_make_query("SHOW VARIABLES LIKE 'SERVER_UUID'"); + router->master_state = BLRM_MUUID; + router->master->func.write(router->master, buf); + break; case BLRM_GTIDMODE: // Response to the GTID_MODE, should be stored if (router->saved_master.gtid_mode) @@ -763,7 +781,6 @@ int no_residual = 1; int preslen = -1; int prev_length = -1; int n_bufs = -1, pn_bufs = -1; -static REP_HEADER phdr; /* * Prepend any residual buffer to the buffer chain we have @@ -950,7 +967,7 @@ static REP_HEADER phdr; } break; } - phdr = hdr; + if (hdr.ok == 0) { /* set mysql errno to 0 */ @@ -961,6 +978,8 @@ static REP_HEADER phdr; free(router->m_errmsg); router->m_errmsg = NULL; + int event_limit; + /* * First check that the checksum we calculate matches the * checksum in the packet we received. @@ -1001,8 +1020,11 @@ static REP_HEADER phdr; #ifdef SHOW_EVENTS printf("blr: event type 0x%02x, flags 0x%04x, event size %d", hdr.event_type, hdr.flags, hdr.event_size); #endif - if (hdr.event_type >= 0 && hdr.event_type < 0x24) + event_limit = router->mariadb10_compat ? MAX_EVENT_TYPE_MARIADB10 : MAX_EVENT_TYPE; + + if (hdr.event_type >= 0 && hdr.event_type <= event_limit) router->stats.events[hdr.event_type]++; + if (hdr.event_type == FORMAT_DESCRIPTION_EVENT && hdr.next_pos == 0) { // Fake format description message @@ -1224,26 +1246,6 @@ blr_extract_header(register uint8_t *ptr, register REP_HEADER *hdr) hdr->flags = EXTRACT16(&ptr[22]); } -/** - * Extract a numeric field from a packet of the specified number of bits - * - * @param src The raw packet source - * @param bits The number of bits to extract (multiple of 8) - */ -inline uint32_t -extract_field(register uint8_t *src, int bits) -{ -register uint32_t rval = 0, shift = 0; - - while (bits > 0) - { - rval |= (*src++) << shift; - shift += 8; - bits -= 8; - } - return rval; -} - /** * Process a binlog rotate event. * @@ -1313,8 +1315,8 @@ MYSQL_session *auth_info; if ((auth_info = calloc(1, sizeof(MYSQL_session))) == NULL) return NULL; - strncpy(auth_info->user, username,MYSQL_USER_MAXLEN+1); - strncpy(auth_info->db, database,MYSQL_DATABASE_MAXLEN+1); + strncpy(auth_info->user, username,MYSQL_USER_MAXLEN); + strncpy(auth_info->db, database,MYSQL_DATABASE_MAXLEN); gw_sha1_str((const uint8_t *)password, strlen(password), auth_info->client_sha1); return auth_info; diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index b423b494f..aef9d8c2c 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -34,6 +34,8 @@ * 18/02/2015 Massimiliano Pinto Addition of DISCONNECT ALL and DISCONNECT SERVER server_id * 18/03/2015 Markus Makela Better detection of CRC32 | NONE checksum * 19/03/2015 Massimiliano Pinto Addition of basic MariaDB 10 compatibility support + * 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility + * 11/05/2015 Massimiliano Pinto Only MariaDB 10 Slaves can register to binlog router with a MariaDB 10 Master * 25/05/2015 Massimiliano Pinto Addition of BLRM_SLAVE_STOPPED state and blr_start/stop_slave. * New commands STOP SLAVE, START SLAVE added. * 29/05/2015 Massimiliano Pinto Addition of CHANGE MASTER TO ... @@ -48,6 +50,7 @@ * * @endverbatim */ + #include #include #include @@ -115,6 +118,8 @@ static void blr_master_restore_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG static void blr_master_set_empty_config(ROUTER_INSTANCE *router); static void blr_master_apply_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *prev_master); +void poll_fake_write_event(DCB *dcb); + extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; extern __thread log_info_t tls_log_info; @@ -166,7 +171,28 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) slave->dcb->remote))); dcb_close(slave->dcb); return 1; + } + + /* + * If Master is MariaDB10 don't allow registration from + * MariaDB/Mysql 5 Slaves + */ + + if (router->mariadb10_compat && !slave->mariadb10_compat) { + slave->state = BLRS_ERRORED; + blr_send_custom_error(slave->dcb, 1, 0, + "MariaDB 10 Slave is required for Slave registration"); + + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "%s: Slave %s: a MariaDB 10 Slave is required for Slave registration", + router->service->name, + slave->dcb->remote))); + + dcb_close(slave->dcb); + return 1; } else { + /* Master and Slave version OK: continue with slave registration */ return blr_slave_register(router, slave, queue); } break; @@ -455,10 +481,17 @@ extern char *strcasestr(); free(query_text); return blr_slave_replay(router, slave, router->saved_master.heartbeat); } - else if (strcasecmp(word, "@mariadb_slave_capability") == 0) + else if (strcasecmp(word, "@mariadb_slave_capability") == 0) { - free(query_text); - return blr_slave_send_ok(router, slave); + /* mariadb10 compatibility is set for the slave */ + slave->mariadb10_compat=true; + + free(query_text); + if (router->mariadb10_compat) { + return blr_slave_replay(router, slave, router->saved_master.mariadb10); + } else { + return blr_slave_send_ok(router, slave); + } } else if (strcasecmp(word, "@master_binlog_checksum") == 0) { @@ -1404,10 +1437,9 @@ blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { GWBUF *resp; uint8_t *ptr; -int len, slen; +int slen; ptr = GWBUF_DATA(queue); - len = extract_field(ptr, 24); ptr += 4; // Skip length and sequence number if (*ptr++ != COM_REGISTER_SLAVE) return 0; @@ -1476,7 +1508,7 @@ blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue { GWBUF *resp; uint8_t *ptr; -int len, flags, serverid, rval, binlognamelen; +int len, rval, binlognamelen; REP_HEADER hdr; uint32_t chksum; @@ -1504,9 +1536,7 @@ uint32_t chksum; slave->binlog_pos = extract_field(ptr, 32); ptr += 4; - flags = extract_field(ptr, 16); ptr += 2; - serverid = extract_field(ptr, 32); ptr += 4; strncpy(slave->binlogfile, (char *)ptr, binlognamelen); slave->binlogfile[binlognamelen] = 0; @@ -1588,28 +1618,6 @@ uint32_t chksum; return rval; } -/** - * Extract a numeric field from a packet of the specified number of bits, - * the number of bits must be a multiple of 8. - * - * @param src The raw packet source - * @param bits The number of bits to extract (multiple of 8) - * @return The extracted value - */ -static uint32_t -extract_field(uint8_t *src, int bits) -{ -uint32_t rval = 0, shift = 0; - - while (bits > 0) - { - rval |= (*src++) << shift; - shift += 8; - bits -= 8; - } - return rval; -} - /** * Encode a value into a number of bits in a MySQL packet * @@ -1971,7 +1979,7 @@ int len = EXTRACT24(ptr + 9); // Extract the event length len = BINLOG_FNAMELEN; ptr += 19; // Skip header slave->binlog_pos = extract_field(ptr, 32); - slave->binlog_pos += (extract_field(ptr+4, 32) << 32); + slave->binlog_pos += (((uint64_t)extract_field(ptr+4, 32)) << 32); memcpy(slave->binlogfile, ptr + 8, len); slave->binlogfile[len] = 0; } @@ -2006,6 +2014,9 @@ uint32_t chksum; binlognamelen = strlen(slave->binlogfile); len = 19 + 8 + 4 + binlognamelen; + /* no slave crc, remove 4 bytes */ + if (slave->nocrc) + len -= 4; /* no slave crc, remove 4 bytes */ if (slave->nocrc) @@ -2339,7 +2350,6 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave) uint8_t *ptr; int len, seqno; GWBUF *pkt; - int n = 1; /* preparing output result */ blr_slave_send_fieldcount(router, slave, 2); @@ -2389,7 +2399,7 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave) strncpy((char *)ptr, state, strlen(state)); // Result string ptr += strlen(state); - n = slave->dcb->func.write(slave->dcb, pkt); + slave->dcb->func.write(slave->dcb, pkt); /* force session close*/ router_obj->closeSession(router->service->router_instance, sptr); diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index d85f96422..058d16056 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -43,6 +43,7 @@ * 29/05/14 Mark Riddoch Add Filter support * 16/10/14 Mark Riddoch Add show eventq * 05/03/15 Massimiliano Pinto Added enable/disable feedback + * 27/05/15 Martin Brampton Add show persistent [server] * * @endverbatim */ @@ -154,6 +155,10 @@ struct subcommand showoptions[] = { "Show the monitors that are configured", "Show the monitors that are configured", {0, 0, 0} }, + { "persistent", 1, dprintPersistentDCBs, + "Show persistent pool for a named server, e.g. show persistent dbnode1", + "Show persistent pool for a server, e.g. show persistent 0x485390. The address may also be replaced with the server name from the configuration file", + {ARG_TYPE_SERVER, 0, 0} }, { "server", 1, dprintServer, "Show details for a named server, e.g. show server dbnode1", "Show details for a server, e.g. show server 0x485390. The address may also be repalced with the server name from the configuration file", diff --git a/server/modules/routing/maxinfo/CMakeLists.txt b/server/modules/routing/maxinfo/CMakeLists.txt index 898df9b73..8e07a34a7 100644 --- a/server/modules/routing/maxinfo/CMakeLists.txt +++ b/server/modules/routing/maxinfo/CMakeLists.txt @@ -1,4 +1,4 @@ add_library(maxinfo SHARED maxinfo.c maxinfo_parse.c maxinfo_error.c maxinfo_exec.c) -set_target_properties(maxinfo PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib) +set_target_properties(maxinfo PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${MAXSCALE_LIBDIR}) target_link_libraries(maxinfo pthread log_manager) -install(TARGETS maxinfo DESTINATION modules) +install(TARGETS maxinfo DESTINATION ${MAXSCALE_LIBDIR}) diff --git a/server/modules/routing/maxinfo/maxinfo_parse.c b/server/modules/routing/maxinfo/maxinfo_parse.c index 9eb445a22..bdef7ef26 100644 --- a/server/modules/routing/maxinfo/maxinfo_parse.c +++ b/server/modules/routing/maxinfo/maxinfo_parse.c @@ -113,14 +113,10 @@ MAXINFO_TREE *col, *table; #endif default: *parse_error = PARSE_SYNTAX_ERROR; - if (tree) - free_tree(tree); return NULL; } } *parse_error = PARSE_SYNTAX_ERROR; - if (tree) - free_tree(tree); return NULL; } diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index b4b080c16..ccb071e1a 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -67,6 +67,7 @@ * 06/03/2014 Massimiliano Pinto Server connection counter is now updated in closeSession * 24/06/2014 Massimiliano Pinto New rules for selecting the Master server * 27/06/2014 Mark Riddoch Addition of server weighting + * 11/06/2015 Martin Brampton Remove decrement n_current (moved to dcb.c) * * @endverbatim */ @@ -657,7 +658,6 @@ DCB* backend_dcb; if (rses_begin_locked_router_action(router_cli_ses)) { /* decrease server current connection counter */ - atomic_add(&router_cli_ses->backend->server->stats.n_current, -1); backend_dcb = router_cli_ses->backend_dcb; router_cli_ses->backend_dcb = NULL; @@ -723,16 +723,10 @@ routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) SERVER_IS_DOWN(router_cli_ses->backend->server)) { LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGFILE_TRACE|LOGFILE_ERROR, "Error : Failed to route MySQL command %d to backend " - "server.", - mysql_command))); - skygw_log_write( - LOGFILE_ERROR, - "Error : Failed to route MySQL command %d to backend " - "server %s.", - mysql_command, - router_cli_ses->backend->server->unique_name); + "server.%s", + mysql_command,rses_is_closed ? " Session is closed." : ""))); rc = 0; goto return_rc; @@ -833,12 +827,7 @@ clientReply( GWBUF *queue, DCB *backend_dcb) { - DCB *client ; - - client = backend_dcb->session->client; - - ss_dassert(client != NULL); - + ss_dassert(backend_dcb->session->client != NULL); SESSION_ROUTE_REPLY(backend_dcb->session, queue); } diff --git a/server/modules/routing/readwritesplit/CMakeLists.txt b/server/modules/routing/readwritesplit/CMakeLists.txt index 6960d4c8d..c7e387290 100644 --- a/server/modules/routing/readwritesplit/CMakeLists.txt +++ b/server/modules/routing/readwritesplit/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(readwritesplit SHARED readwritesplit.c) target_link_libraries(readwritesplit ssl pthread log_manager utils query_classifier) -install(TARGETS readwritesplit DESTINATION modules) +install(TARGETS readwritesplit DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_TESTS) add_subdirectory(test) endif() diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index b9beb80d0..3877b49aa 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -226,7 +226,7 @@ static rses_property_t* mysql_sescmd_get_property( static rses_property_t* rses_property_init( rses_property_type_t prop_type); -static void rses_property_add( +static int rses_property_add( ROUTER_CLIENT_SES* rses, rses_property_t* prop); @@ -287,7 +287,7 @@ static sescmd_cursor_t* backend_ref_get_sescmd_cursor (backend_ref_t* bref); static int router_handle_state_switch(DCB* dcb, DCB_REASON reason, void* data); static bool handle_error_new_connection( ROUTER_INSTANCE* inst, - ROUTER_CLIENT_SES* rses, + ROUTER_CLIENT_SES** rses, DCB* backend_dcb, GWBUF* errmsg); static void handle_error_reply_client( @@ -1022,7 +1022,6 @@ static void closeSession( */ dcb_close(dcb); /** decrease server current connection counters */ - atomic_add(&bref->bref_backend->backend_server->stats.n_current, -1); atomic_add(&bref->bref_backend->backend_conn_count, -1); } } @@ -1244,7 +1243,8 @@ static bool get_dcb( SERVER_IS_SLAVE(b->backend_server) && (max_rlag == MAX_RLAG_UNDEFINED || (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->backend_server->rlag <= max_rlag))) + b->backend_server->rlag <= max_rlag)) && + !rses->rses_config.master_reads) { /** found slave */ candidate_bref = &backend_ref[i]; @@ -1636,7 +1636,8 @@ static skygw_query_type_t is_read_tmp_table( bool target_tmp_table = false; int tsize = 0, klen = 0,i; char** tbl = NULL; - char *hkey,*dbname; + char *dbname; + char hkey[MYSQL_DATABASE_MAXLEN+MYSQL_TABLE_MAXLEN+2]; MYSQL_session* data; DCB* master_dcb = NULL; @@ -1664,12 +1665,7 @@ static skygw_query_type_t is_read_tmp_table( /** Query targets at least one table */ for(i = 0; irses_prop_data.temp_tables) { @@ -1684,8 +1680,6 @@ static skygw_query_type_t is_read_tmp_table( "Query targets a temporary table: %s",hkey))); } } - - free(hkey); } } @@ -2017,8 +2011,9 @@ static bool route_single_stmt( GWBUF* querybuf) { skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - mysql_server_cmd_t packet_type; + mysql_server_cmd_t packet_type = MYSQL_COM_UNDEFINED; uint8_t* packet; + size_t packet_len; int ret = 0; DCB* master_dcb = NULL; DCB* target_dcb = NULL; @@ -2026,11 +2021,8 @@ static bool route_single_stmt( 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 @@ -2058,7 +2050,19 @@ static bool route_single_stmt( { querybuf = gwbuf_make_contiguous(querybuf); } + + packet = GWBUF_DATA(querybuf); + packet_len = gw_mysql_get_byte3(packet); + if(packet_len == 0) + { + route_target = TARGET_MASTER; + packet_type = MYSQL_COM_UNDEFINED; + } + else + { + packet_type = packet[4]; + 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 */ @@ -2273,7 +2277,7 @@ static bool route_single_stmt( } goto retblock; } - + } /** Lock router session */ if (!rses_begin_locked_router_action(rses)) { @@ -2493,8 +2497,8 @@ static bool route_single_stmt( rses_end_locked_router_action(rses); goto retblock; } - - if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(querybuf))) == 1) + GWBUF* wbuf = gwbuf_clone(querybuf); + if ((ret = target_dcb->func.write(target_dcb, wbuf)) == 1) { backend_ref_t* bref; @@ -2508,7 +2512,8 @@ static bool route_single_stmt( } else { - LOGIF(LE, (skygw_log_write_flush( + gwbuf_free(wbuf); + LOGIF((LE|LT), (skygw_log_write_flush( LOGFILE_ERROR, "Error : Routing query failed."))); succp = false; @@ -2839,8 +2844,8 @@ static void clientReply ( /** There is one pending session command to be executed. */ if (sescmd_cursor_is_active(scur)) { - bool succp; - + bool succp; + LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "Backend %s:%d processed reply and starts to execute " @@ -2849,8 +2854,15 @@ static void clientReply ( bref->bref_backend->backend_server->port))); succp = execute_sescmd_in_backend(bref); - - ss_dassert(succp); + ss_dassert(succp); + if(!succp) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Backend %s:%d failed to execute session command.", + bref->bref_backend->backend_server->name, + bref->bref_backend->backend_server->port))); + } } else if (bref->bref_pending_cmd != NULL) /*< non-sescmd is waiting to be routed */ { @@ -2942,6 +2954,11 @@ static void bref_clear_state( backend_ref_t* bref, bref_state_t state) { + if(bref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } if (state != BREF_WAITING_RESULT) { bref->bref_state &= ~state; @@ -2963,6 +2980,13 @@ static void bref_clear_state( prev2 = atomic_add( &bref->bref_backend->backend_server->stats.n_current_ops, -1); ss_dassert(prev2 > 0); + if(prev2 <= 0) + { + skygw_log_write(LE,"[%s] Error: negative current operation count in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } } } } @@ -2971,6 +2995,11 @@ static void bref_set_state( backend_ref_t* bref, bref_state_t state) { + if(bref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } if (state != BREF_WAITING_RESULT) { bref->bref_state |= state; @@ -2983,11 +3012,24 @@ static void bref_set_state( /** Increase waiter count */ prev1 = atomic_add(&bref->bref_num_result_wait, 1); ss_dassert(prev1 >= 0); - + if(prev1 < 0) + { + skygw_log_write(LE,"[%s] Error: negative number of connections waiting for results in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } /** Increase global operation count */ prev2 = atomic_add( &bref->bref_backend->backend_server->stats.n_current_ops, 1); - ss_dassert(prev2 >= 0); + ss_dassert(prev2 >= 0); + if(prev2 < 0) + { + skygw_log_write(LE,"[%s] Error: negative current operation count in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } } } @@ -3534,7 +3576,8 @@ static rses_property_t* rses_property_init( prop = (rses_property_t*)calloc(1, sizeof(rses_property_t)); if (prop == NULL) { - goto return_prop; + skygw_log_write(LE,"Error: Malloc returned NULL. (%s:%d)",__FILE__,__LINE__); + return NULL; } prop->rses_prop_type = prop_type; #if defined(SS_DEBUG) @@ -3542,7 +3585,6 @@ static rses_property_t* rses_property_init( prop->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; #endif -return_prop: CHK_RSES_PROP(prop); return prop; } @@ -3553,6 +3595,11 @@ return_prop: static void rses_property_done( rses_property_t* prop) { + if(prop == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } CHK_RSES_PROP(prop); switch (prop->rses_prop_type) { @@ -3586,10 +3633,20 @@ static void rses_property_done( * * Router client session must be locked. */ -static void rses_property_add( +static int rses_property_add( ROUTER_CLIENT_SES* rses, rses_property_t* prop) { + if(rses == NULL) + { + skygw_log_write(LE,"Error: Router client session is NULL. (%s:%d)",__FILE__,__LINE__); + return -1; + } + if(prop == NULL) + { + skygw_log_write(LE,"Error: Router client session property is NULL. (%s:%d)",__FILE__,__LINE__); + return -1; + } rses_property_t* p; CHK_CLIENT_RSES(rses); @@ -3611,6 +3668,7 @@ static void rses_property_add( } p->rses_prop_next = prop; } + return 0; } /** @@ -3621,7 +3679,13 @@ static mysql_sescmd_t* rses_property_get_sescmd( rses_property_t* prop) { mysql_sescmd_t* sescmd; - + + if(prop == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return NULL; + } + CHK_RSES_PROP(prop); ss_dassert(prop->rses_prop_rsession == NULL || SPINLOCK_IS_LOCKED(&prop->rses_prop_rsession->rses_lock)); @@ -3634,22 +3698,6 @@ static mysql_sescmd_t* rses_property_get_sescmd( } return sescmd; } - -/** -static void rses_begin_locked_property_action( - rses_property_t* prop) -{ - CHK_RSES_PROP(prop); - spinlock_acquire(&prop->rses_prop_lock); -} - -static void rses_end_locked_property_action( - rses_property_t* prop) -{ - CHK_RSES_PROP(prop); - spinlock_release(&prop->rses_prop_lock); -} -*/ /** * Create session command property. @@ -3682,6 +3730,11 @@ static mysql_sescmd_t* mysql_sescmd_init ( static void mysql_sescmd_done( mysql_sescmd_t* sescmd) { + if(sescmd == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } CHK_RSES_PROP(sescmd->my_sescmd_prop); gwbuf_free(sescmd->my_sescmd_buf); memset(sescmd, 0, sizeof(mysql_sescmd_t)); @@ -3764,7 +3817,7 @@ static GWBUF* sescmd_cursor_process_replies( dcb_close(bref->bref_dcb); *reconnect = true; if(replybuf) - gwbuf_consume(replybuf,gwbuf_length(replybuf)); + while((replybuf = gwbuf_consume(replybuf,gwbuf_length(replybuf)))); } } /** This is a response from the master and it is the "right" one. @@ -3777,7 +3830,7 @@ static GWBUF* sescmd_cursor_process_replies( /** Mark the rest session commands as replied */ scmd->my_sescmd_is_replied = true; scmd->reply_cmd = *((unsigned char*)replybuf->start + 4); - skygw_log_write(LOGFILE_DEBUG,"Master '%s' responded to a session command.", + skygw_log_write(LT,"Master '%s' responded to a session command.", bref->bref_backend->backend_server->unique_name); int i; @@ -3797,6 +3850,11 @@ static GWBUF* sescmd_cursor_process_replies( if(ses->rses_backend_ref[i].bref_dcb) dcb_close(ses->rses_backend_ref[i].bref_dcb); *reconnect = true; + skygw_log_write(LT,"Disabling slave %s:%d, result differs from master's result. Master: %d Slave: %d", + ses->rses_backend_ref[i].bref_backend->backend_server->name, + ses->rses_backend_ref[i].bref_backend->backend_server->port, + bref->reply_cmd, + ses->rses_backend_ref[i].reply_cmd); } } } @@ -3804,11 +3862,17 @@ static GWBUF* sescmd_cursor_process_replies( } else { - skygw_log_write(LOGFILE_DEBUG,"Slave '%s' responded faster to a session command.", - bref->bref_backend->backend_server->unique_name); + skygw_log_write(LT,"Slave '%s' responded before master to a session command. Result: %d", + bref->bref_backend->backend_server->unique_name, + (int)bref->reply_cmd); + if(bref->reply_cmd == 0xff) + { + SERVER* serv = bref->bref_backend->backend_server; + skygw_log_write(LE,"Error: Slave '%s' (%s:%u) failed to execute session command.", + serv->unique_name,serv->name,serv->port); + } if(replybuf) - gwbuf_free(replybuf); - return NULL; + while((replybuf = gwbuf_consume(replybuf,gwbuf_length(replybuf)))); } @@ -3854,6 +3918,12 @@ static bool sescmd_cursor_is_active( sescmd_cursor_t* sescmd_cursor) { bool succp; + + if(sescmd_cursor == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return false; + } ss_dassert(SPINLOCK_IS_LOCKED(&sescmd_cursor->scmd_cur_rses->rses_lock)); succp = sescmd_cursor->scmd_cur_active; @@ -3879,9 +3949,14 @@ static GWBUF* sescmd_cursor_clone_querybuf( sescmd_cursor_t* scur) { GWBUF* buf; + if(scur == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return NULL; + } ss_dassert(scur->scmd_cur_cmd != NULL); - buf = gwbuf_clone(scur->scmd_cur_cmd->my_sescmd_buf); + buf = gwbuf_clone_all(scur->scmd_cur_cmd->my_sescmd_buf); CHK_GWBUF(buf); return buf; @@ -3891,7 +3966,12 @@ static bool sescmd_cursor_history_empty( sescmd_cursor_t* scur) { bool succp; - + + if(scur == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return true; + } CHK_SESCMD_CUR(scur); if (scur->scmd_cur_rses->rses_properties[RSES_PROP_TYPE_SESCMD] == NULL) @@ -3911,6 +3991,11 @@ static void sescmd_cursor_reset( sescmd_cursor_t* scur) { ROUTER_CLIENT_SES* rses; + if(scur == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } CHK_SESCMD_CUR(scur); CHK_CLIENT_RSES(scur->scmd_cur_rses); rses = scur->scmd_cur_rses; @@ -3927,6 +4012,11 @@ static bool execute_sescmd_history( { bool succp; sescmd_cursor_t* scur; + if(bref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return false; + } CHK_BACKEND_REF(bref); scur = &bref->bref_sescmd_cur; @@ -3962,7 +4052,12 @@ static bool execute_sescmd_in_backend( bool succp; int rc = 0; sescmd_cursor_t* scur; - + GWBUF* buf; + if(backend_ref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return false; + } if (BREF_IS_CLOSED(backend_ref)) { succp = false; @@ -3994,36 +4089,17 @@ static bool execute_sescmd_in_backend( /** Cursor is left active when function returns. */ sescmd_cursor_set_active(scur, true); } -#if defined(SS_DEBUG) - LOGIF(LT, tracelog_routed_query(scur->scmd_cur_rses, - "execute_sescmd_in_backend", - backend_ref, - sescmd_cursor_clone_querybuf(scur))); - { - GWBUF* tmpbuf = sescmd_cursor_clone_querybuf(scur); - uint8_t* ptr = GWBUF_DATA(tmpbuf); - unsigned char cmd = MYSQL_GET_COMMAND(ptr); - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [execute_sescmd_in_backend] Just before write, fd " - "%d : cmd %s.", - pthread_self(), - dcb->fd, - STRPACKETTYPE(cmd)))); - gwbuf_free(tmpbuf); - } -#endif /*< SS_DEBUG */ switch (scur->scmd_cur_cmd->my_sescmd_packet_type) { case MYSQL_COM_CHANGE_USER: /** This makes it possible to handle replies correctly */ gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); + buf = sescmd_cursor_clone_querybuf(scur); rc = dcb->func.auth( dcb, NULL, dcb->session, - sescmd_cursor_clone_querybuf(scur)); + buf); break; case MYSQL_COM_INIT_DB: @@ -4049,10 +4125,12 @@ static bool execute_sescmd_in_backend( * Mark session command buffer, it triggers writing * MySQL command to protocol */ + gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); + buf = sescmd_cursor_clone_querybuf(scur); rc = dcb->func.write( dcb, - sescmd_cursor_clone_querybuf(scur)); + buf); break; } @@ -4062,6 +4140,7 @@ static bool execute_sescmd_in_backend( } else { + while((buf = GWBUF_CONSUME_ALL(buf)) != NULL); succp = false; } return_succp: @@ -4083,6 +4162,12 @@ static bool sescmd_cursor_next( rses_property_t* prop_curr; rses_property_t* prop_next; + if(scur == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return false; + } + ss_dassert(scur != NULL); ss_dassert(*(scur->scmd_cur_ptr_property) != NULL); ss_dassert(SPINLOCK_IS_LOCKED( @@ -4409,11 +4494,21 @@ static bool route_session_write( * prevent it from being released before properties * are cleaned up as a part of router sessionclean-up. */ - prop = rses_property_init(RSES_PROP_TYPE_SESCMD); + if((prop = rses_property_init(RSES_PROP_TYPE_SESCMD)) == NULL) + { + skygw_log_write(LE,"Error: Router session property initialization failed"); + rses_end_locked_router_action(router_cli_ses); + return false; + } mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); /** Add sescmd property to router client session */ - rses_property_add(router_cli_ses, prop); + if(rses_property_add(router_cli_ses, prop) != 0) + { + skygw_log_write(LE,"Error: Session property addition failed."); + rses_end_locked_router_action(router_cli_ses); + return false; + } for (i=0; irses_nbackends; i++) { @@ -4536,7 +4631,10 @@ static void rwsplit_process_router_options( int i; char* value; select_criteria_t c; - + + if(options == NULL) + return; + for (i = 0; options[i]; i++) { if ((value = strchr(options[i], '=')) == NULL) @@ -4589,6 +4687,10 @@ static void rwsplit_process_router_options( { router->rwsplit_config.disable_slave_recovery = config_truth_value(value); } + else if(strcmp(options[i],"master_accept_reads") == 0) + { + router->rwsplit_config.master_reads = config_truth_value(value); + } } } /*< for */ } @@ -4620,7 +4722,7 @@ static void handleError ( SESSION* session; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *)router_session; - + CHK_DCB(backend_dcb); /** Reset error handle flag from a given DCB */ @@ -4687,14 +4789,15 @@ static void handleError ( { /** * This is called in hope of getting replacement for - * failed slave(s). + * failed slave(s). This call may free rses. */ *succp = handle_error_new_connection(inst, - rses, + &rses, backend_dcb, errmsgbuf); } - rses_end_locked_router_action(rses); + /* Free the lock if rses still exists */ + if (rses) rses_end_locked_router_action(rses); break; } @@ -4763,10 +4866,11 @@ static void handle_error_reply_client( */ static bool handle_error_new_connection( ROUTER_INSTANCE* inst, - ROUTER_CLIENT_SES* rses, + ROUTER_CLIENT_SES** rses, DCB* backend_dcb, GWBUF* errmsg) { + ROUTER_CLIENT_SES* myrses; SESSION* ses; int router_nservers; int max_nslaves; @@ -4774,7 +4878,8 @@ static bool handle_error_new_connection( backend_ref_t* bref; bool succp; - ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); + myrses = *rses; + ss_dassert(SPINLOCK_IS_LOCKED(&myrses->rses_lock)); ses = backend_dcb->session; CHK_SESSION(ses); @@ -4782,7 +4887,7 @@ static bool handle_error_new_connection( /** * If bref == NULL it has been replaced already with another one. */ - if ((bref = get_bref_from_dcb(rses, backend_dcb)) == NULL) + if ((bref = get_bref_from_dcb(myrses, backend_dcb)) == NULL) { succp = true; goto return_succp; @@ -4825,25 +4930,25 @@ static bool handle_error_new_connection( (void *)bref); router_nservers = router_get_servercount(inst); - max_nslaves = rses_get_max_slavecount(rses, router_nservers); - max_slave_rlag = rses_get_max_replication_lag(rses); + max_nslaves = rses_get_max_slavecount(myrses, router_nservers); + max_slave_rlag = rses_get_max_replication_lag(myrses); /** * Try to get replacement slave or at least the minimum * number of slave connections for router session. */ if(inst->rwsplit_config.disable_slave_recovery) { - succp = have_enough_servers(&rses,1,router_nservers,inst) ? true : false; + succp = have_enough_servers(&myrses,1,router_nservers,inst) ? true : false; } else { succp = select_connect_backend_servers( - &rses->rses_master_ref, - rses->rses_backend_ref, + &myrses->rses_master_ref, + myrses->rses_backend_ref, router_nservers, max_nslaves, max_slave_rlag, - rses->rses_config.rw_slave_select_criteria, + myrses->rses_config.rw_slave_select_criteria, ses, inst); } @@ -5080,10 +5185,9 @@ static int router_handle_state_switch( { backend_ref_t* bref; int rc = 1; - ROUTER_CLIENT_SES* rses; - SESSION* ses; SERVER* srv; - + ROUTER_CLIENT_SES* rses; + SESSION* ses; CHK_DCB(dcb); bref = (backend_ref_t *)data; CHK_BACKEND_REF(bref); @@ -5102,11 +5206,8 @@ static int router_handle_state_switch( srv->name, srv->port, STRSRVSTATUS(srv)))); - ses = dcb->session; - CHK_SESSION(ses); - - rses = (ROUTER_CLIENT_SES *)dcb->session->router_session; - CHK_CLIENT_RSES(rses); + CHK_SESSION(((SESSION*)dcb->session)); + CHK_CLIENT_RSES(((ROUTER_CLIENT_SES *)dcb->session->router_session)); switch (reason) { case DCB_REASON_NOT_RESPONDING: diff --git a/server/modules/routing/schemarouter/CMakeLists.txt b/server/modules/routing/schemarouter/CMakeLists.txt index 70e6348af..930366f2b 100644 --- a/server/modules/routing/schemarouter/CMakeLists.txt +++ b/server/modules/routing/schemarouter/CMakeLists.txt @@ -1,10 +1,10 @@ add_library(schemarouter SHARED schemarouter.c sharding_common.c) target_link_libraries(schemarouter log_manager utils query_classifier) -install(TARGETS schemarouter DESTINATION modules) +install(TARGETS schemarouter DESTINATION ${MAXSCALE_LIBDIR}) add_library(shardrouter SHARED shardrouter.c svcconn.c sharding_common.c) target_link_libraries(shardrouter log_manager utils query_classifier) -install(TARGETS shardrouter DESTINATION modules) +install(TARGETS shardrouter DESTINATION ${MAXSCALE_LIBDIR}) if(BUILD_TESTS) add_subdirectory(test) diff --git a/server/modules/routing/schemarouter/schemarouter.c b/server/modules/routing/schemarouter/schemarouter.c index cf60bae51..ed6eeeb55 100644 --- a/server/modules/routing/schemarouter/schemarouter.c +++ b/server/modules/routing/schemarouter/schemarouter.c @@ -1153,7 +1153,6 @@ static void closeSession( */ dcb_close(dcb); /** decrease server current connection counters */ - atomic_add(&bref->bref_backend->backend_server->stats.n_current, -1); atomic_add(&bref->bref_backend->backend_conn_count, -1); } } @@ -2854,11 +2853,16 @@ int bref_cmp_current_load( return ((1000 * s1->stats.n_current_ops) - b1->weight) - ((1000 * s2->stats.n_current_ops) - b2->weight); } - + static void bref_clear_state( backend_ref_t* bref, bref_state_t state) { + if(bref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } if (state != BREF_WAITING_RESULT) { bref->bref_state &= ~state; @@ -2867,10 +2871,10 @@ static void bref_clear_state( { int prev1; int prev2; - + /** Decrease waiter count */ prev1 = atomic_add(&bref->bref_num_result_wait, -1); - + if (prev1 <= 0) { atomic_add(&bref->bref_num_result_wait, 1); } @@ -2880,14 +2884,26 @@ static void bref_clear_state( prev2 = atomic_add( &bref->bref_backend->backend_server->stats.n_current_ops, -1); ss_dassert(prev2 > 0); - } + if(prev2 <= 0) + { + skygw_log_write(LE,"[%s] Error: negative current operation count in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } + } } } -static void bref_set_state( +static void bref_set_state( backend_ref_t* bref, bref_state_t state) { + if(bref == NULL) + { + skygw_log_write(LE,"[%s] Error: NULL parameter.",__FUNCTION__); + return; + } if (state != BREF_WAITING_RESULT) { bref->bref_state |= state; @@ -2896,15 +2912,28 @@ static void bref_set_state( { int prev1; int prev2; - + /** Increase waiter count */ prev1 = atomic_add(&bref->bref_num_result_wait, 1); ss_dassert(prev1 >= 0); - + if(prev1 < 0) + { + skygw_log_write(LE,"[%s] Error: negative number of connections waiting for results in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } /** Increase global operation count */ prev2 = atomic_add( &bref->bref_backend->backend_server->stats.n_current_ops, 1); - ss_dassert(prev2 >= 0); + ss_dassert(prev2 >= 0); + if(prev2 < 0) + { + skygw_log_write(LE,"[%s] Error: negative current operation count in backend %s:%u", + __FUNCTION__, + &bref->bref_backend->backend_server->name, + &bref->bref_backend->backend_server->port); + } } } diff --git a/server/modules/routing/schemarouter/sharding_common.c b/server/modules/routing/schemarouter/sharding_common.c index d69b12d02..cf6bf2c12 100644 --- a/server/modules/routing/schemarouter/sharding_common.c +++ b/server/modules/routing/schemarouter/sharding_common.c @@ -46,7 +46,7 @@ bool extract_database(GWBUF* buf, char* str) tok = strtok_r(query," ;",&saved); if(tok == NULL || strcasecmp(tok,"use") != 0) { - skygw_log_write(LOGFILE_ERROR,"Schemarouter: Malformed chage database packet."); + skygw_log_write(LOGFILE_ERROR,"extract_database: Malformed chage database packet."); succp = false; goto retblock; } @@ -54,7 +54,7 @@ bool extract_database(GWBUF* buf, char* str) tok = strtok_r(NULL," ;",&saved); if(tok == NULL) { - skygw_log_write(LOGFILE_ERROR,"Schemarouter: Malformed chage database packet."); + skygw_log_write(LOGFILE_ERROR,"extract_database: Malformed chage database packet."); succp = false; goto retblock; } diff --git a/server/modules/routing/schemarouter/shardrouter.c b/server/modules/routing/schemarouter/shardrouter.c index 94ab35974..1122b0b5c 100644 --- a/server/modules/routing/schemarouter/shardrouter.c +++ b/server/modules/routing/schemarouter/shardrouter.c @@ -319,7 +319,7 @@ parse_mapping_response(ROUTER_CLIENT_SES* rses, char* target, GWBUF* buf) if(PTR_IS_RESULTSET(((unsigned char*)buf->start)) && modutil_count_signal_packets(buf,0,0,&more) == 2) { - ptr = (char*)buf->start; + ptr = (unsigned char*)buf->start; if(ptr[5] != 1) { @@ -1701,7 +1701,7 @@ routeQuery(ROUTER* instance, querybuf))) { extract_database(querybuf,db); - snprintf(errbuf,"Unknown database: %s",db); + snprintf(errbuf,25+MYSQL_DATABASE_MAXLEN,"Unknown database: %s",db); create_error_reply(errbuf,router_cli_ses->replydcb); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, diff --git a/server/test/MaxScale_test.cnf b/server/test/maxscale_test.cnf similarity index 90% rename from server/test/MaxScale_test.cnf rename to server/test/maxscale_test.cnf index 06b783ca5..fdc843d8d 100644 --- a/server/test/MaxScale_test.cnf +++ b/server/test/maxscale_test.cnf @@ -1,5 +1,11 @@ [maxscale] threads=4 +libdir=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@ +logdir=@CMAKE_INSTALL_PREFIX@/ +datadir=@CMAKE_INSTALL_PREFIX@/ +cachedir=@CMAKE_INSTALL_PREFIX@/ +language=@CMAKE_INSTALL_PREFIX@/lib/maxscale/ +piddir=@CMAKE_INSTALL_PREFIX@/ [feedback] feedback_enable=true diff --git a/server/test/maxscale_test.h.in b/server/test/maxscale_test.h.in index d41ff181f..b5448295c 100644 --- a/server/test/maxscale_test.h.in +++ b/server/test/maxscale_test.h.in @@ -1,9 +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" +#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/CMakeLists.txt b/utils/CMakeLists.txt index 7f835c552..2ae917712 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,2 +1,2 @@ -add_library(utils skygw_utils.cc) +add_library(utils skygw_utils.cc ../server/core/atomic.c) target_link_libraries(utils stdc++) diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 99f7807fc..f9b586a0c 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -131,6 +131,7 @@ typedef enum skygw_chk_t { CHK_NUM_DCB, CHK_NUM_PROTOCOL, CHK_NUM_SESSION, + CHK_NUM_SERVER, CHK_NUM_ROUTER_SES, CHK_NUM_MY_SESCMD, CHK_NUM_ROUTER_PROPERTY, @@ -506,6 +507,12 @@ typedef enum skygw_chk_t { "Session under- or overflow"); \ } +#define CHK_SERVER(s) { \ + ss_info_dassert(s->server_chk_top == CHK_NUM_SERVER && \ + s->server_chk_tail == CHK_NUM_SERVER, \ + "Server under- or overflow"); \ + } + #define CHK_GWBUF(b) { \ ss_info_dassert(((char *)(b)->start <= (char *)(b)->end), \ "gwbuf start has passed the endpoint"); \ diff --git a/utils/skygw_utils.cc b/utils/skygw_utils.cc index 4e3f84dca..26a8e8b88 100644 --- a/utils/skygw_utils.cc +++ b/utils/skygw_utils.cc @@ -29,6 +29,7 @@ #include #include #include "skygw_utils.h" +#include #if defined(MLIST) @@ -71,23 +72,6 @@ static void mlist_free_memory(mlist_t* ml, char* name); static void thread_free_memory(skygw_thread_t* th, char* name); /** End of static function declarations */ -int atomic_add( - int *variable, - int value) -{ -#ifdef __GNUC__ - return (int) __sync_fetch_and_add (variable, value); -#else - asm volatile( - "lock; xaddl %%eax, %2;" - :"=a" (value) - : "a" (value), "m" (*variable) - : "memory" ); - return value; -#endif -} - - /** mutexed list, mlist */ #if defined(MLIST) @@ -395,6 +379,7 @@ mlist_cursor_t* mlist_cursor_init( c = (mlist_cursor_t *)calloc(1, sizeof(mlist_cursor_t)); if (c == NULL) { + simple_mutex_unlock(&list->mlist_mutex); goto return_cursor; } c->mlcursor_chk_top = CHK_NUM_MLIST_CURSOR; @@ -581,6 +566,7 @@ bool mlist_cursor_move_to_first( simple_mutex_lock(&list->mlist_mutex, true); if (mc->mlcursor_list->mlist_deleted) { + simple_mutex_unlock(&list->mlist_mutex); return false; } /** Set position point to first node */ @@ -1839,21 +1825,12 @@ int skygw_file_write( bool flush) { int rc; -#if !defined(LAPTOP_TEST) - int err = 0; size_t nwritten; int fd; static int writecount; -#else - struct timespec ts1; - ts1.tv_sec = 0; - ts1.tv_nsec = DISKWRITE_LATENCY*1000000; -#endif - + CHK_FILE(file); -#if defined(LAPTOP_TEST) - nanosleep(&ts1, NULL); -#else + nwritten = fwrite(data, nbytes, 1, file->sf_file); if (nwritten != 1) { @@ -1871,11 +1848,11 @@ int skygw_file_write( if (flush || writecount == FSYNCLIMIT) { fd = fileno(file->sf_file); - err = fflush(file->sf_file); - err = fsync(fd); + fflush(file->sf_file); + fsync(fd); writecount = 0; } -#endif + rc = 0; CHK_FILE(file); return_rc: diff --git a/utils/skygw_utils.h b/utils/skygw_utils.h index b2022cc54..a6118bd48 100644 --- a/utils/skygw_utils.h +++ b/utils/skygw_utils.h @@ -263,8 +263,6 @@ int skygw_rwlock_rdlock(skygw_rwlock_t* rwlock); int skygw_rwlock_unlock(skygw_rwlock_t* rwlock); int skygw_rwlock_init(skygw_rwlock_t** rwlock); -int atomic_add(int *variable, int value); - EXTERN_C_BLOCK_BEGIN size_t get_decimal_len(size_t s);