From 1a1c95fd3b59e1c9a510d39dccbacb8e6b291d5e Mon Sep 17 00:00:00 2001 From: ekorh475 Date: Mon, 21 Nov 2016 10:38:18 +0200 Subject: [PATCH 01/48] Fix memory leak in script execution (MXS-1008) --- server/core/externcmd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/externcmd.c b/server/core/externcmd.c index f066d76f2..5d74474e3 100644 --- a/server/core/externcmd.c +++ b/server/core/externcmd.c @@ -230,6 +230,7 @@ bool externcmd_substitute_arg(EXTERNCMD* cmd, const char* match, const char* rep } } } + pcre2_code_free(re); } else { From afa175c3ab7392ac96f7f36d2aadb85fecf43b1b Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 21 Nov 2016 12:58:33 +0200 Subject: [PATCH 02/48] Update 2.0.2 Release Notes (part 2) --- Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md index e50c25a00..188c9457d 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md @@ -27,6 +27,7 @@ To cater for this situation there is now a `set server stale` command. [Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/browse/MXS-976?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.0.2) * [MXS-1018](https://jira.mariadb.org/browse/MXS-1018): Internal connections don't use TLS +* [MXS-1008](https://jira.mariadb.org/browse/MXS-1008): MySQL Monitor with scripts leaks memory * [MXS-976](https://jira.mariadb.org/browse/MXS-976): Crash in libqc_sqlite * [MXS-975](https://jira.mariadb.org/browse/MXS-975): TCP backlog is capped at 1280 * [MXS-970](https://jira.mariadb.org/browse/MXS-970): A fatal problem with maxscale automatically shut down @@ -35,6 +36,7 @@ To cater for this situation there is now a `set server stale` command. * [MXS-965](https://jira.mariadb.org/browse/MXS-965): galeramon erlaubt keine TLS verschlüsselte Verbindung * [MXS-960](https://jira.mariadb.org/browse/MXS-960): MaxScale Binlog Server does not allow comma to be in password * [MXS-957](https://jira.mariadb.org/browse/MXS-957): Temporary table creation from another temporary table isn't detected +* [MXS-956](https://jira.mariadb.org/browse/MXS-956): Removing DCB 0x7fbf94016760 but was in state DCB_STATE_DISCONNECTED which is not legal for a call to dcb_close * [MXS-955](https://jira.mariadb.org/browse/MXS-955): MaxScale 2.0.1 doesn't recognize user and passwd options in .maxadmin file * [MXS-953](https://jira.mariadb.org/browse/MXS-953): Charset error when server configued in utf8mb4 * [MXS-942](https://jira.mariadb.org/browse/MXS-942): describe table query not routed to shard that contains the schema From 14326774065fd211a79e5935414d149fb8a17660 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 21 Nov 2016 23:02:59 +0200 Subject: [PATCH 03/48] Fix regression in prepared statement routing The prepared statements were router according to the real type instead of being router to the master. This was caused by the change in the route target function. --- server/modules/routing/readwritesplit/readwritesplit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 62e3cf545..0bb306afc 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -1391,6 +1391,8 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, else if (!trx_active && !load_active && !QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) && !QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) && + !QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) && + !QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) && (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || From 15a03375c24cc5979cc9f265b8b662b8a8f48f94 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 11:06:18 +0200 Subject: [PATCH 04/48] Install correct README file The README was renamed to README.md which broke installation. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69f9539ec..590c8f07d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,7 +219,7 @@ if(WITH_MAXSCALE_CNF AND (NOT TARGET_COMPONENT OR "core" STREQUAL "${TARGET_COMP endif() install_file(${CMAKE_SOURCE_DIR}/COPYRIGHT core) -install_file(${CMAKE_SOURCE_DIR}/README core) +install_file(${CMAKE_SOURCE_DIR}/README.md core) install_file(${CMAKE_SOURCE_DIR}/LICENSE.TXT core) install_file(${CMAKE_SOURCE_DIR}/LICENSE-THIRDPARTY.TXT core) install_file(etc/lsyncd_example.conf core) From 79a03049a10f0687fa17a86aeb9f57eea65911a3 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 22 Nov 2016 10:57:23 +0200 Subject: [PATCH 05/48] Enable fetching of stale items It is now possible to specify that a cache item should be returned if it exists, even if it is stale. --- server/modules/filter/cache/cache.c | 2 +- server/modules/filter/cache/cache_storage_api.h | 11 +++++++++++ .../storage/storage_rocksdb/rocksdbstorage.cc | 15 ++++++++++++--- .../storage/storage_rocksdb/rocksdbstorage.h | 2 +- .../storage/storage_rocksdb/storage_rocksdb.cc | 7 +++++-- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.c index 302b40a5e..c633d6661 100644 --- a/server/modules/filter/cache/cache.c +++ b/server/modules/filter/cache/cache.c @@ -1087,7 +1087,7 @@ static bool route_using_cache(CACHE_SESSION_DATA *csdata, if (result == CACHE_RESULT_OK) { - result = csdata->api->getValue(csdata->storage, csdata->key, value); + result = csdata->api->getValue(csdata->storage, csdata->key, CACHE_FLAGS_NONE, value); } else { diff --git a/server/modules/filter/cache/cache_storage_api.h b/server/modules/filter/cache/cache_storage_api.h index a413afe54..a68aecd93 100644 --- a/server/modules/filter/cache/cache_storage_api.h +++ b/server/modules/filter/cache/cache_storage_api.h @@ -26,10 +26,17 @@ typedef enum cache_result { CACHE_RESULT_OK, CACHE_RESULT_NOT_FOUND, + CACHE_RESULT_STALE, CACHE_RESULT_OUT_OF_RESOURCES, CACHE_RESULT_ERROR } cache_result_t; +typedef enum cache_flags +{ + CACHE_FLAGS_NONE = 0x00, + CACHE_FLAGS_INCLUDE_STALE = 0x01, +} cache_flags_t; + typedef void* CACHE_STORAGE; enum @@ -88,14 +95,18 @@ typedef struct cache_storage_api * * @param storage Pointer to a CACHE_STORAGE. * @param key A key generated with getKey. + * @param flags Mask of cache_flags_t values. * @param result Pointer to variable that after a successful return will * point to a GWBUF. * @return CACHE_RESULT_OK if item was found, + * CACHE_RESULT_STALE if CACHE_FLAGS_INCLUDE_STALE was specified in + * flags and the item was found but stale, * CACHE_RESULT_NOT_FOUND if item was not found (which may be because * the ttl was reached), or some other error code. */ cache_result_t (*getValue)(CACHE_STORAGE* storage, const char* key, + uint32_t flags, GWBUF** result); /** diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc index 966bfdf8b..75a8005ff 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc @@ -403,7 +403,7 @@ cache_result_t RocksDBStorage::getKey(const char* zDefaultDB, const GWBUF* pQuer return CACHE_RESULT_OK; } -cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) +cache_result_t RocksDBStorage::getValue(const char* pKey, uint32_t flags, GWBUF** ppResult) { // Use the root DB so that we get the value *with* the timestamp at the end. rocksdb::DB* pDb = m_sDb->GetRootDB(); @@ -419,7 +419,9 @@ cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) case rocksdb::Status::kOk: if (value.length() >= RocksDBInternals::TS_LENGTH) { - if (!RocksDBInternals::IsStale(value, m_ttl, rocksdb::Env::Default())) + bool isStale = RocksDBInternals::IsStale(value, m_ttl, rocksdb::Env::Default()); + + if (!isStale || ((flags & CACHE_FLAGS_INCLUDE_STALE) != 0)) { size_t length = value.length() - RocksDBInternals::TS_LENGTH; @@ -429,7 +431,14 @@ cache_result_t RocksDBStorage::getValue(const char* pKey, GWBUF** ppResult) { memcpy(GWBUF_DATA(*ppResult), value.data(), length); - result = CACHE_RESULT_OK; + if (isStale) + { + result = CACHE_RESULT_STALE; + } + else + { + result = CACHE_RESULT_OK; + } } } else diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h index a3bf85ba7..1a3187517 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h @@ -30,7 +30,7 @@ public: ~RocksDBStorage(); cache_result_t getKey(const char* zDefaultDB, const GWBUF* pQuery, char* pKey); - cache_result_t getValue(const char* pKey, GWBUF** ppResult); + cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppResult); cache_result_t putValue(const char* pKey, const GWBUF* pValue); private: diff --git a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc index d5e72aabd..b21037129 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc @@ -86,7 +86,10 @@ cache_result_t getKey(CACHE_STORAGE* pStorage, return result; } -cache_result_t getValue(CACHE_STORAGE* pStorage, const char* pKey, GWBUF** ppResult) +cache_result_t getValue(CACHE_STORAGE* pStorage, + const char* pKey, + uint32_t flags, + GWBUF** ppResult) { ss_dassert(pStorage); ss_dassert(pKey); @@ -96,7 +99,7 @@ cache_result_t getValue(CACHE_STORAGE* pStorage, const char* pKey, GWBUF** ppRes try { - result = reinterpret_cast(pStorage)->getValue(pKey, ppResult); + result = reinterpret_cast(pStorage)->getValue(pKey, flags, ppResult); } catch (const std::bad_alloc&) { From 53637af98f63d86236a090be694961273e6a1257 Mon Sep 17 00:00:00 2001 From: ekorh475 Date: Mon, 21 Nov 2016 15:13:28 +0200 Subject: [PATCH 06/48] Log monitor script argument values MXS-843. When a monitor executes an external script in response to a server event, the execution is logged to the MaxScale main log. Previously, the log message only contained the script name as given in the configuration file. Now, the message contains the script name and argument values as given to the execvp-call. --- server/core/monitor.c | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index 33a4dd2f0..50feaae6b 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -372,7 +372,7 @@ void monitorRemoveServer(MONITOR *mon, SERVER *server) if (ptr) { - monitor_server_free(ptr); + monitor_server_free(ptr); } if (old_state == MONITOR_STATE_RUNNING) @@ -845,7 +845,7 @@ mon_get_event_type(MONITOR_SERVERS* node) /* Was running and still is */ if ((!prev_bits || !present_bits || prev_bits == present_bits) && - prev & (SERVER_MASTER | SERVER_SLAVE | SERVER_JOINED | SERVER_NDB)) + prev & (SERVER_MASTER | SERVER_SLAVE | SERVER_JOINED | SERVER_NDB)) { /* We used to know what kind of server it was */ event_type = LOSS_EVENT; @@ -1042,8 +1042,48 @@ monitor_launch_script(MONITOR* mon, MONITOR_SERVERS* ptr, char* script) } else { + ss_dassert(cmd->argv != NULL && cmd->argv[0] != NULL); + // Construct a string with the script + arguments + char *scriptStr = NULL; + int totalStrLen = 0; + bool memError = false; + for (int i = 0; cmd->argv[i]; i++) + { + totalStrLen += strlen(cmd->argv[i]) + 1; // +1 for space and one \0 + } + int spaceRemaining = totalStrLen; + if ((scriptStr = MXS_CALLOC(totalStrLen, sizeof(char))) != NULL) + { + char *currentPos = scriptStr; + // The script name should not begin with a space + int len = snprintf(currentPos, spaceRemaining, "%s", cmd->argv[0]); + currentPos += len; + spaceRemaining -= len; + + for (int i = 1; cmd->argv[i]; i++) + { + if ((cmd->argv[i])[0] == '\0') + { + continue; // Empty argument, print nothing + } + len = snprintf(currentPos, spaceRemaining, " %s", cmd->argv[i]); + currentPos += len; + spaceRemaining -= len; + } + ss_dassert(spaceRemaining > 0); + *currentPos = '\0'; + } + else + { + memError = true; + scriptStr = cmd->argv[0]; // print at least something + } MXS_NOTICE("Executed monitor script '%s' on event '%s'.", - script, mon_get_event_name(ptr)); + scriptStr, mon_get_event_name(ptr)); + if (!memError) + { + MXS_FREE(scriptStr); + } } externcmd_free(cmd); From 8572eb34a07d52976d2b9bf87c1ee482b8b51e74 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 5 Oct 2016 09:13:56 +0300 Subject: [PATCH 07/48] qc_sqlite: Dequote more properly When a backslash is encountered, the backslash should not be copied but only the character after that. For the sake of completeness, a few more characters would have to be handled explicitly, but as the content of a string will not affect the statement's classification there is not much point in doing that. --- query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c b/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c index 2030e0e0c..c9ce17de8 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/util.c @@ -227,9 +227,11 @@ int sqlite3Dequote(char *z){ // TODO: removed. break; }else if ( z[i]=='\\' ){ - z[j++] = '\\'; + // If we want to dequote properly, a few more characters would have to be + // handled explicitly. That would not affect the classification, however, + // so we won't do that. if ( z[i+1]==quote || z[i+1]=='\\' ){ - z[j++] = quote; + z[j++] = z[i+1]; i++; } } else From 1457b88606458787af070119e920d4ab71ced2dc Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 21:25:20 +0200 Subject: [PATCH 08/48] Fix transaction tracking The transaction tracking functionality used the wrong pointer type for buffer data. This caused the query command comparison to always fail. --- server/modules/protocol/MySQL/MySQLClient/mysql_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 49695b136..df10a1d1f 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -1440,7 +1440,7 @@ static int route_by_statement(SESSION* session, uint64_t capabilities, GWBUF** p if (rcap_type_required(capabilities, RCAP_TYPE_TRANSACTION_TRACKING)) { - uint32_t *data = GWBUF_DATA(packetbuf); + uint8_t *data = GWBUF_DATA(packetbuf); if (MYSQL_GET_COMMAND(data) == MYSQL_COM_QUERY) { From dcd98900ea09b1b43ca3d463bb52c8cc940541b4 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 09:51:31 +0200 Subject: [PATCH 09/48] Remove dependency on embedded library The test programs of the query classifier do not need the embedded library headers. --- query_classifier/test/CMakeLists.txt | 56 ++++++++++------------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/query_classifier/test/CMakeLists.txt b/query_classifier/test/CMakeLists.txt index ba817cb5e..8a11ba495 100644 --- a/query_classifier/test/CMakeLists.txt +++ b/query_classifier/test/CMakeLists.txt @@ -1,44 +1,28 @@ -# Include the embedded library headers -if (BUILD_QC_MYSQLEMBEDDED) - subdirs(MYSQL_INCLUDE_DIR_ALL ${MYSQL_EMBEDDED_INCLUDE_DIR}) - foreach(DIR ${MYSQL_INCLUDE_DIR_ALL}) - include_directories(${DIR}) - endforeach() - include_directories(${MYSQL_EMMBEDDED_INCLUDE_DIR}/..) +# Use the client libraries in MaxScale core +include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR}) - if(${ERRMSG} MATCHES "ERRMSG-NOTFOUND") - message(FATAL_ERROR "The errmsg.sys file was not found, please define the path with -DERRMSG=") - else() - if(${CMAKE_VERSION} VERSION_LESS 2.8) - execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR}) - else() - file(COPY ${ERRMSG} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - endif() - endif() +add_executable(classify classify.c) +target_link_libraries(classify maxscale-common) - add_executable(classify classify.c) - target_link_libraries(classify maxscale-common) +add_executable(compare compare.cc) +target_link_libraries(compare maxscale-common) - add_executable(compare compare.cc) - target_link_libraries(compare maxscale-common) +add_executable(crash_qc_sqlite crash_qc_sqlite.c) +target_link_libraries(crash_qc_sqlite maxscale-common) - add_executable(crash_qc_sqlite crash_qc_sqlite.c) - target_link_libraries(crash_qc_sqlite maxscale-common) +add_test(TestQC_Crash_qcsqlite crash_qc_sqlite) - add_test(TestQC_Crash_qcsqlite crash_qc_sqlite) +add_test(TestQC_MySQLEmbedded classify qc_mysqlembedded ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) +add_test(TestQC_SqLite classify qc_sqlite ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) - add_test(TestQC_MySQLEmbedded classify qc_mysqlembedded ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) - add_test(TestQC_SqLite classify qc_sqlite ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) - - add_test(TestQC_CompareCreate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/create.test) - add_test(TestQC_CompareDelete compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/delete.test) - add_test(TestQC_CompareInsert compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/insert.test) - add_test(TestQC_CompareJoin compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/join.test) - add_test(TestQC_CompareSelect compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/select.test) - add_test(TestQC_CompareSet compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/set.test) - add_test(TestQC_CompareUpdate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/update.test) - add_test(TestQC_CompareMaxScale compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/maxscale.test) - add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ") -endif() +add_test(TestQC_CompareCreate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/create.test) +add_test(TestQC_CompareDelete compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/delete.test) +add_test(TestQC_CompareInsert compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/insert.test) +add_test(TestQC_CompareJoin compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/join.test) +add_test(TestQC_CompareSelect compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/select.test) +add_test(TestQC_CompareSet compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/set.test) +add_test(TestQC_CompareUpdate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/update.test) +add_test(TestQC_CompareMaxScale compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/maxscale.test) +add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ") add_subdirectory(canonical_tests) From 83ffdcf4ed81909a0775c2fb63540f0e9deb0bb4 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 09:55:35 +0200 Subject: [PATCH 10/48] Change defines into inline functions To prevent bugs caused by pointers having the wrong type (e.g. uint32_t instead of uint8_t), some macros are changed into inline functions so that the normal type-checking is performed. The macros MYSQL_GET_ERRCODE, MYSQL_GET_STMTOK_NPARAM, MYSQL_GET_STMTOK_NATTR, and MYSQL_GET_NATTR were not changed, because they may be too specific to be present in a general purpose header in the first place. --- include/maxscale/protocol/mysql.h | 54 ++++++++++++------- .../qc_mysqlembedded/qc_mysqlembedded.cc | 5 ++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 49c175615..daca421df 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -289,30 +289,46 @@ typedef struct #define MYSQL_REPLY_OK 0x00 #define MYSQL_REPLY_AUTHSWITCHREQUEST 0xfe -/* - * Let's try this with proper enums instead of numbers -#define MYSQL_GET_COMMAND(payload) (payload[4]) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) -#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) -#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) -#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) -#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02) -#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11) -#define MYSQL_GET_NATTR(payload) ((int)payload[4]) -*/ -#define MYSQL_GET_COMMAND(payload) ((mysql_server_cmd_t)((payload)[4])) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) +static inline mysql_server_cmd_t MYSQL_GET_COMMAND(const uint8_t* header) +{ + return (mysql_server_cmd_t)header[4]; +} + +static inline uint8_t MYSQL_GET_PACKET_NO(const uint8_t* header) +{ + return header[3]; +} + +static inline uint8_t MYSQL_GET_PACKET_LEN(const uint8_t* header) +{ + return gw_mysql_get_byte3(header); +} + #define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) #define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9])) #define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) -#define MYSQL_IS_ERROR_PACKET(payload) ((int)MYSQL_GET_COMMAND(payload)==MYSQL_REPLY_ERR) -#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_QUIT) -#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_INIT_DB) -#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_CHANGE_USER) #define MYSQL_GET_NATTR(payload) ((int)payload[4]) +static inline bool MYSQL_IS_ERROR_PACKET(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_REPLY_ERR; +} + +static inline bool MYSQL_IS_COM_QUIT(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_QUIT; +} + +static inline bool MYSQL_IS_COM_INIT_DB(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_INIT_DB; +} + +static inline bool MYSQL_IS_CHANGE_USER(const uint8_t* header) +{ + return MYSQL_GET_COMMAND(header) == MYSQL_COM_CHANGE_USER; +} + /* The following can be compared using memcmp to detect a null password */ extern uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]; diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index e298181a1..c1d336d3e 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -58,6 +58,11 @@ #include #include #include +// assumes it is being compiled agains Connector-C, +// so we need to make certain Connector-C constants visible. +#define MYSQL_COM_QUIT COM_QUIT +#define MYSQL_COM_INIT_DB COM_INIT_DB +#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER #include #include From 502eba8b4f67852ae8af7e025d28cda730ff9722 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 21:31:57 +0200 Subject: [PATCH 11/48] Fix OK packet generation The packet was generated with the wrong number of elements due to usage of sizeof on an integer where the correct type was an uint8_t. This only fixes the malformed packets but does not fix the root cause of the problem. The affected rows and last insert ID are length encoded integers which should be handled. The current code treats them as one byte fields. --- include/maxscale/protocol/mysql.h | 2 +- server/modules/protocol/MySQL/mysql_common.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index daca421df..1f91d4730 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -394,7 +394,7 @@ bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer); mxs_auth_state_t gw_send_backend_auth(DCB *dcb); /** Write an OK packet to a DCB */ -int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* message); +int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* message); /** Check for OK packet */ bool mxs_mysql_is_ok_packet(GWBUF *buffer); diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 9873b30ae..9dad9552a 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1086,11 +1086,11 @@ bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session) * @param dcb DCB where packet is written * @param sequence Packet sequence number * @param affected_rows Number of affected rows - * * @param message SQL message + * @param message SQL message * @return 1 on success, 0 on error * */ -int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* message) +int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* message) { uint8_t *outbuf = NULL; uint32_t mysql_payload_size = 0; From 0a7d1390b6609289ca13417abcf22dfb84d2d8d6 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 21 Nov 2016 13:08:39 +0200 Subject: [PATCH 12/48] MXS-929: Add module command calls to maxadmin The maxadmin interface now supports calls to registered module functions. It is also capable of listing all the registered functions. --- server/modules/routing/debugcli/debugcmd.c | 136 ++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 8934abf11..a7a94e03a 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -51,11 +51,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -285,6 +287,59 @@ struct subcommand showoptions[] = { EMPTY_OPTION} }; +bool listfuncs_cb(const MODULECMD *cmd, void *data) +{ + DCB *dcb = (DCB*)data; + + dcb_printf(dcb, "%s::%s(", cmd->domain, cmd->identifier); + + + for (int i = 0; i < cmd->arg_count_max; i++) + { + modulecmd_arg_type_t *type = &cmd->arg_types[i]; + + if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT) + { + char *t = modulecmd_argtype_to_str(&cmd->arg_types[i]); + + if (t) + { + dcb_printf(dcb, "%s%s", t, i < cmd->arg_count_max - 1 ? " " : ""); + MXS_FREE(t); + } + } + } + + dcb_printf(dcb, ")\n"); + + + for (int i = 0; i < cmd->arg_count_max; i++) + { + modulecmd_arg_type_t *type = &cmd->arg_types[i]; + + if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT) + { + + char *t = modulecmd_argtype_to_str(&cmd->arg_types[i]); + + if (t) + { + dcb_printf(dcb, " %s - %s\n", t, cmd->arg_types[i].description); + MXS_FREE(t); + } + } + } + + dcb_printf(dcb, "\n"); + + return true; +} + +void dListFunctions(DCB *dcb) +{ + modulecmd_foreach(NULL, NULL, listfuncs_cb, dcb); +} + /** * The subcommands of the list command */ @@ -350,6 +405,12 @@ struct subcommand listoptions[] = "List the status of the polling threads in MaxScale", {0, 0, 0} }, + { + "functions", 0, 0, dListFunctions, + "List registered functions", + "List all registered functions", + {0} + }, { EMPTY_OPTION} }; @@ -1311,6 +1372,78 @@ struct subcommand alteroptions[] = } }; +static inline bool requires_output_dcb(const MODULECMD *cmd) +{ + modulecmd_arg_type_t *type = &cmd->arg_types[0]; + return cmd->arg_count_max > 0 && MODULECMD_GET_TYPE(type) == MODULECMD_ARG_OUTPUT; +} + +static void callFunction(DCB *dcb, char *domain, char *id, char *v3, + char *v4, char *v5, char *v6, char *v7, char *v8, char *v9, + char *v10, char *v11, char *v12) +{ + const void *values[11] = {v3, v4, v5, v6, v7, v8, v9, v10, v11, v12}; + const int valuelen = sizeof(values) / sizeof(values[0]); + int numargs = 0; + + while (numargs < valuelen && values[numargs]) + { + numargs++; + } + + const MODULECMD *cmd = modulecmd_find_command(domain, id); + + if (cmd) + { + if (requires_output_dcb(cmd)) + { + /** The function requires a DCB for output, add the client DCB + * as the first argument */ + for (int i = valuelen - 1; i > 0; i--) + { + values[i] = values[i - 1]; + } + values[0] = dcb; + numargs += numargs + 1 < valuelen - 1 ? 1 : 0; + } + + MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, numargs, values); + + if (arg) + { + if (!modulecmd_call_command(cmd, arg)) + { + dcb_printf(dcb, "Failed to call function: %s\n", modulecmd_get_error()); + } + modulecmd_arg_free(arg); + } + else + { + dcb_printf(dcb, "Failed to parse arguments: %s\n", modulecmd_get_error()); + } + } + else + { + dcb_printf(dcb, "Function not found: %s\n", modulecmd_get_error()); + } +} + +struct subcommand calloptions[] = +{ + { + "function", 2, 12, callFunction, + "Call module function", + "Usage: call function NAMESPACE FUNCTION ARGS...\n" + "To list all registered functions, run 'list functions'.\n", + { ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING} + }, + { + EMPTY_OPTION + } +}; + /** * The debug command table */ @@ -1335,7 +1468,8 @@ static struct { "restart", restartoptions }, { "shutdown", shutdownoptions }, { "show", showoptions }, - { "sync", syncoptions }, + { "sync", syncoptions }, + { "call", calloptions }, { NULL, NULL } }; From 53413a3260e7d99f032222314cfdc48cf2fb93cc Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 21 Nov 2016 14:07:53 +0200 Subject: [PATCH 13/48] MXS-929: Add reloading of rules to dbfwfilter The rules and users are stored in thread-local pointers which removes the need to hold global locks. This allows greater scalability but causes a slightly larger memory footprint. Usually the increase in memory consumption is trivial compared to the benefits in scaling. Using a per-thread rule and user list allows changes to be applied immediately without locking on the instance level. The updating of the rules uses the new functinality described in the debugcmd.h header. The module registers two functions at startup; one for reloading rules and one for printing them. These custom functions can be seen in the `maxadmin list functions` output. --- server/modules/filter/dbfwfilter/dbfwfilter.c | 328 +++++++++++------- 1 file changed, 211 insertions(+), 117 deletions(-) diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index 42fcbada9..e4774459e 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -60,26 +60,32 @@ *@endcode */ -#include +#include + #include -#include #include +#include +#include +#include +#include + +#include #include +#include #include #include #include #include +#include #include -#include -#include -#include +#include #include -#include "dbfwfilter.h" -#include -#include -#include #include +#include "dbfwfilter.h" +#include "ruleparser.yy.h" +#include "lex.yy.h" + /** Older versions of Bison don't include the parsing function in the header */ #ifndef dbfw_yyparse int dbfw_yyparse(void*); @@ -167,6 +173,8 @@ const char* rule_names[] = "CLAUSE" }; +const int rule_names_len = sizeof(rule_names) / sizeof(char**); + /** * Linked list of strings. */ @@ -228,6 +236,10 @@ typedef struct rulebook_t struct rulebook_t* next; /*< The next rule in the book */ } RULE_BOOK; +thread_local int thr_rule_version = 0; +thread_local RULE *thr_rules = NULL; +thread_local HASHTABLE *thr_users = NULL; + /** * A temporary template structure used in the creation of actual users. * This is also used to link the user definitions with the rules. @@ -260,12 +272,12 @@ typedef struct user_t */ typedef struct { - HASHTABLE* users; /*< User hashtable */ - RULE* rules; /*< List of all the rules */ enum fw_actions action; /*< Default operation mode, defaults to deny */ int log_match; /*< Log matching and/or non-matching queries */ SPINLOCK lock; /*< Instance spinlock */ int idgen; /*< UID generator */ + char *rulefile; /*< Path to the rule file */ + int rule_version; /*< Latest rule file version, incremented on reload */ } FW_INSTANCE; /** @@ -281,6 +293,24 @@ typedef struct bool parse_at_times(const char** tok, char** saveptr, RULE* ruledef); bool parse_limit_queries(FW_INSTANCE* instance, RULE* ruledef, const char* rule, char** saveptr); +static void rule_free_all(RULE* rule); +static bool process_rule_file(const char* filename, RULE** rules, HASHTABLE **users); +bool replace_rules(FW_INSTANCE* instance); + +static void print_rule(RULE *rules, char *dest) +{ + int type = 0; + + if ((int)rules->type > 0 && (int)rules->type < rule_names_len) + { + type = (int)rules->type; + } + + sprintf(dest, "%s, %s, %d", + rules->name, + rule_names[type], + rules->times_matched); +} /** * Push a string onto a string stack @@ -674,6 +704,95 @@ TIMERANGE* split_reverse_time(TIMERANGE* tr) return tmp; } +bool dbfw_reload_rules(const MODULECMD_ARG *argv) +{ + bool rval = true; + FILTER_DEF *filter = argv->argv[0].value.filter; + FW_INSTANCE *inst = (FW_INSTANCE*)filter->filter; + + if (modulecmd_arg_is_present(argv, 1)) + { + /** We need to change the rule file */ + char *newname = MXS_STRDUP(argv->argv[1].value.string); + + if (newname) + { + spinlock_acquire(&inst->lock); + + char *oldname = inst->rulefile; + inst->rulefile = newname; + + spinlock_release(&inst->lock); + + MXS_FREE(oldname); + } + else + { + modulecmd_set_error("Memory allocation failed"); + rval = false; + } + } + + spinlock_acquire(&inst->lock); + char filename[strlen(inst->rulefile) + 1]; + strcpy(filename, inst->rulefile); + spinlock_release(&inst->lock); + + RULE *rules = NULL; + HASHTABLE *users = NULL; + + if (rval && access(filename, R_OK) == 0) + { + if (process_rule_file(filename, &rules, &users)) + { + atomic_add(&inst->rule_version, 1); + } + else + { + modulecmd_set_error("Failed to process rule file '%s'. See log " + "file for more details.", filename); + rval = false; + } + } + else + { + char err[MXS_STRERROR_BUFLEN]; + modulecmd_set_error("Failed to read rules at '%s': %d, %s", filename, + errno, strerror_r(errno, err, sizeof(err))); + rval = false; + } + + rule_free_all(rules); + hashtable_free(users); + + return rval; +} + +bool dbfw_show_rules(const MODULECMD_ARG *argv) +{ + DCB *dcb = argv->argv[0].value.dcb; + FILTER_DEF *filter = argv->argv[1].value.filter; + FW_INSTANCE *inst = (FW_INSTANCE*)filter->filter; + + dcb_printf(dcb, "Rule, Type, Times Matched\n"); + + if (!thr_rules || !thr_users) + { + if (!replace_rules(inst)) + { + return 0; + } + } + + for (RULE *rule = thr_rules; rule; rule = rule->next) + { + char buf[strlen(rule->name) + 200]; // Some extra space + print_rule(rule, buf); + dcb_printf(dcb, "%s\n", buf); + } + + return true; +} /** * Implementation of the mandatory version entry point * @@ -692,7 +811,23 @@ char * version() /*lint -e14 */ void ModuleInit() { + modulecmd_arg_type_t args_rules_reload[] = + { + {MODULECMD_ARG_FILTER, "Filter to reload"}, + {MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, "Path to rule file"} + }; + + modulecmd_register_command("dbfwfilter", "rules/reload", dbfw_reload_rules, 2, args_rules_reload); + + modulecmd_arg_type_t args_rules_show[] = + { + {MODULECMD_ARG_OUTPUT, "DCB where result is written"}, + {MODULECMD_ARG_FILTER, "Filter to inspect"} + }; + + modulecmd_register_command("dbfwfilter", "rules", dbfw_show_rules, 2, args_rules_show); } + /*lint +e14 */ /** @@ -708,60 +843,6 @@ FILTER_OBJECT * GetModuleObject() return &MyObject; } -/** - * Apply a rule set to a user - * - * @param instance Filter instance - * @param user User name - * @param rulebook List of rules to apply - * @param type Matching type, one of FWTOK_MATCH_ANY, FWTOK_MATCH_ALL or FWTOK_MATCH_STRICT_ALL - * @return True of the rules were successfully applied. False if memory allocation - * fails - */ -static bool apply_rule_to_user(FW_INSTANCE *instance, char *username, - RULE_BOOK *rulebook, enum match_type type) -{ - DBFW_USER* user; - ss_dassert(type == FWTOK_MATCH_ANY || type == FWTOK_MATCH_STRICT_ALL || type == FWTOK_MATCH_ALL); - if ((user = (DBFW_USER*) hashtable_fetch(instance->users, username)) == NULL) - { - /**New user*/ - if ((user = (DBFW_USER*) MXS_CALLOC(1, sizeof(DBFW_USER))) == NULL) - { - return false; - } - spinlock_init(&user->lock); - } - - user->name = (char*) MXS_STRDUP_A(username); - user->qs_limit = NULL; - RULE_BOOK *tl = (RULE_BOOK*) rulebook_clone(rulebook); - RULE_BOOK *tail = tl; - - while (tail && tail->next) - { - tail = tail->next; - } - - switch (type) - { - case FWTOK_MATCH_ANY: - tail->next = user->rules_or; - user->rules_or = tl; - break; - case FWTOK_MATCH_STRICT_ALL: - tail->next = user->rules_and; - user->rules_strict_and = tl; - break; - case FWTOK_MATCH_ALL: - tail->next = user->rules_and; - user->rules_and = tl; - break; - } - hashtable_add(instance->users, (void *) username, (void *) user); - return true; -} - /** * Free a TIMERANGE struct * @param tr pointer to a TIMERANGE struct @@ -1238,6 +1319,7 @@ static bool process_user_templates(HASHTABLE *users, user_template_t *templates, user->rules_and = NULL; user->rules_or = NULL; user->rules_strict_and = NULL; + user->qs_limit = NULL; spinlock_init(&user->lock); hashtable_add(users, user->name, user); } @@ -1360,36 +1442,45 @@ static bool process_rule_file(const char* filename, RULE** rules, HASHTABLE **us return rc == 0; } - /** - * @brief Replace the rule file used by this filter instance + * @brief Replace the rule file used by this thread * - * This function does no locking. An external lock needs to protect this function - * call to prevent any connections from using the data when it is being replaced. + * This function replaces or initializes the thread local list of rules and users. * - * @param filename File where the rules are located * @param instance Filter instance - * @return True on success, false on error. If the return value is false, the - * old rules remain active. + * @return True if the session can continue, false on fatal error. */ -bool replace_rule_file(const char* filename, FW_INSTANCE* instance) +bool replace_rules(FW_INSTANCE* instance) { - bool rval = false; + bool rval = true; + spinlock_acquire(&instance->lock); + + size_t len = strlen(instance->rulefile); + char filename[len + 1]; + strcpy(filename, instance->rulefile); + + spinlock_release(&instance->lock); + RULE *rules; HASHTABLE *users; if (process_rule_file(filename, &rules, &users)) { - /** Rules processed successfully, free the old ones */ - rule_free_all(instance->rules); - hashtable_free(instance->users); - instance->rules = rules; - instance->users = users; + rule_free_all(thr_rules); + hashtable_free(thr_users); + thr_rules = rules; + thr_users = users; rval = true; } + else if (thr_rules && thr_users) + { + MXS_ERROR("Failed to parse rules at '%s'. Old rules are still used.", filename); + } else { - MXS_ERROR("Failed to process rule file at '%s', old rules are still active.", filename); + MXS_ERROR("Failed to parse rules at '%s'. No previous rules available, " + "closing session.", filename); + rval = false; } return rval; @@ -1474,11 +1565,22 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params) err = true; } - if (err || !process_rule_file(filename, &my_instance->rules, &my_instance->users)) + RULE *rules = NULL; + HASHTABLE *users = NULL; + my_instance->rulefile = MXS_STRDUP(filename); + + if (err || !my_instance->rulefile || !process_rule_file(filename, &rules, &users)) { MXS_FREE(my_instance); my_instance = NULL; } + else + { + atomic_add(&my_instance->rule_version, 1); + } + + rule_free_all(rules); + hashtable_free(users); return (FILTER *) my_instance; } @@ -1830,15 +1932,15 @@ bool rule_matches(FW_INSTANCE* my_instance, break; case RT_PERMISSION: - { - matches = true; - msg = MXS_STRDUP_A("Permission denied at this time."); - char buffer[32]; // asctime documentation requires 26 - asctime_r(&tm_now, buffer); - MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulebook->rule->name, buffer); - goto queryresolved; - } - break; + { + matches = true; + msg = MXS_STRDUP_A("Permission denied at this time."); + char buffer[32]; // asctime documentation requires 26 + asctime_r(&tm_now, buffer); + MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulebook->rule->name, buffer); + goto queryresolved; + } + break; case RT_COLUMN: if (is_sql && is_real) @@ -2205,6 +2307,16 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) DCB *dcb = my_session->session->client_dcb; int rval = 0; ss_dassert(dcb && dcb->session); + int rule_version = my_instance->rule_version; + + if (thr_rule_version < rule_version) + { + if (!replace_rules(my_instance)) + { + return 0; + } + thr_rule_version = rule_version; + } if (modutil_is_SQL(queue) && modutil_count_statements(queue) > 1) { @@ -2217,7 +2329,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) } else { - DBFW_USER *user = find_user_data(my_instance->users, dcb->user, dcb->remote); + DBFW_USER *user = find_user_data(thr_users, dcb->user, dcb->remote); bool query_ok = false; if (user) @@ -2304,6 +2416,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) rval = dcb->func.write(dcb, forward); } } + return rval; } @@ -2321,34 +2434,15 @@ static void diagnostic(FILTER *instance, void *fsession, DCB *dcb) { FW_INSTANCE *my_instance = (FW_INSTANCE *) instance; - RULE* rules; - int type; - if (my_instance) + dcb_printf(dcb, "Firewall Filter\n"); + dcb_printf(dcb, "Rule, Type, Times Matched\n"); + + for (RULE *rule = thr_rules; rule; rule = rule->next) { - spinlock_acquire(&my_instance->lock); - rules = my_instance->rules; - - dcb_printf(dcb, "Firewall Filter\n"); - dcb_printf(dcb, "%-24s%-24s%-24s\n", "Rule", "Type", "Times Matched"); - while (rules) - { - if ((int) rules->type > 0 && - (int) rules->type < sizeof(rule_names) / sizeof(char**)) - { - type = (int) rules->type; - } - else - { - type = 0; - } - dcb_printf(dcb, "%-24s%-24s%-24d\n", - rules->name, - rule_names[type], - rules->times_matched); - rules = rules->next; - } - spinlock_release(&my_instance->lock); + char buf[strlen(rule->name) + 200]; + print_rule(rule, buf); + dcb_printf(dcb, "%s\n", buf); } } From 2ecd5f3340b27c4465fbd9114cfdc55e073cfa16 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 22 Nov 2016 16:28:33 +0200 Subject: [PATCH 14/48] Prevent stale item from being fetched more than once If a cached entry becomes stale, then if no extra measures are taken then every thread hitting that item while it is being fetched from the server will also refresh it, even though it obviously is sufficient that one does it. Now the knowledge that the item is being refreshed is recorded, so that all other clients are simply returned the stale item. That way, we'll hit the server once per stale item. --- server/modules/filter/cache/cache.c | 202 +++++++++++++++++++++++----- server/modules/filter/cache/cache.h | 2 +- 2 files changed, 170 insertions(+), 34 deletions(-) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.c index c633d6661..0a5da258b 100644 --- a/server/modules/filter/cache/cache.c +++ b/server/modules/filter/cache/cache.c @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "rules.h" #include "storage.h" @@ -119,11 +121,13 @@ static const CACHE_CONFIG DEFAULT_CONFIG = typedef struct cache_instance { - const char *name; - CACHE_CONFIG config; - CACHE_RULES *rules; - CACHE_STORAGE_MODULE *module; - CACHE_STORAGE *storage; + const char *name; // The name of the instance; the section name in the config. + CACHE_CONFIG config; // The configuration of the cache instance. + CACHE_RULES *rules; // The rules of the cache instance. + CACHE_STORAGE_MODULE *module; // The storage module. + CACHE_STORAGE *storage; // The storage API. + HASHTABLE *pending; // Pending items; being fetched from the backend. + SPINLOCK pending_lock; // Lock used for protecting 'pending'. } CACHE_INSTANCE; typedef enum cache_session_state @@ -149,17 +153,18 @@ static void cache_response_state_reset(CACHE_RESPONSE_STATE *state); typedef struct cache_session_data { - CACHE_INSTANCE *instance; /**< The cache instance the session is associated with. */ - CACHE_STORAGE_API *api; /**< The storage API to be used. */ - CACHE_STORAGE *storage; /**< The storage to be used with this session data. */ - DOWNSTREAM down; /**< The previous filter or equivalent. */ - UPSTREAM up; /**< The next filter or equivalent. */ - CACHE_RESPONSE_STATE res; /**< The response state. */ - SESSION *session; /**< The session this data is associated with. */ - char key[CACHE_KEY_MAXLEN]; /**< Key storage. */ - char *default_db; /**< The default database. */ - char *use_db; /**< Pending default database. Needs server response. */ - cache_session_state_t state; + CACHE_INSTANCE *instance; /**< The cache instance the session is associated with. */ + CACHE_STORAGE_API *api; /**< The storage API to be used. */ + CACHE_STORAGE *storage; /**< The storage to be used with this session data. */ + DOWNSTREAM down; /**< The previous filter or equivalent. */ + UPSTREAM up; /**< The next filter or equivalent. */ + CACHE_RESPONSE_STATE res; /**< The response state. */ + SESSION *session; /**< The session this data is associated with. */ + char key[CACHE_KEY_MAXLEN]; /**< Key storage. */ + char *default_db; /**< The default database. */ + char *use_db; /**< Pending default database. Needs server response. */ + cache_session_state_t state; /**< What state is the session in, what data is expected. */ + bool refreshing; /**< Whether the session is updating a stale cache entry. */ } CACHE_SESSION_DATA; static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, SESSION *session); @@ -172,12 +177,58 @@ static int handle_expecting_rows(CACHE_SESSION_DATA *csdata); static int handle_expecting_use_response(CACHE_SESSION_DATA *csdata); static int handle_ignoring_response(CACHE_SESSION_DATA *csdata); static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); -static bool route_using_cache(CACHE_SESSION_DATA *sdata, const GWBUF *key, GWBUF **value); +static cache_result_t get_cached_response(CACHE_SESSION_DATA *sdata, const GWBUF *key, GWBUF **value); static int send_upstream(CACHE_SESSION_DATA *csdata); static void store_result(CACHE_SESSION_DATA *csdata); +/** + * Hashes a cache key to an integer. + * + * @param key Pointer to cache key. + * + * @returns Corresponding integer hash. + */ +static int hash_of_key(const void* key) +{ + int hash = 0; + + const char* i = (const char*)key; + const char* end = i + CACHE_KEY_MAXLEN; + + while (i < end) + { + int c = *i; + hash = c + (hash << 6) + (hash << 16) - hash; + ++i; + } + + return hash; +} + +static int hashfn(const void* address) +{ + // TODO: Hash the address; pointers are not evenly distributed. + return (long)address; +} + +static int hashcmp(const void* address1, const void* address2) +{ + return (long)address2 - (long)address1; +} + +#define DUMMY_VALUE (void*)0xdeadbeef + +// Initial size of hashtable used for storing keys of queries that +// are being fetches. +#define CACHE_PENDING_ITEMS 50 + +static inline bool log_decisions(const CACHE_SESSION_DATA* csdata) +{ + return csdata->instance->config.debug & CACHE_DEBUG_DECISIONS ? true : false; +} + // // API BEGIN // @@ -212,7 +263,10 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER if (rules) { - if ((cinstance = MXS_CALLOC(1, sizeof(CACHE_INSTANCE))) != NULL) + cinstance = MXS_CALLOC(1, sizeof(CACHE_INSTANCE)); + HASHTABLE* pending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); + + if (cinstance && pending) { CACHE_STORAGE_MODULE *module = cache_storage_open(config.storage); @@ -231,6 +285,7 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER cinstance->rules = rules; cinstance->module = module; cinstance->storage = storage; + cinstance->pending = pending; MXS_NOTICE("Cache storage %s opened and initialized.", config.storage); } @@ -240,6 +295,7 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER cache_rules_free(rules); cache_storage_close(module); MXS_FREE(cinstance); + hashtable_free(pending); cinstance = NULL; } } @@ -251,6 +307,14 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER cinstance = NULL; } } + else + { + MXS_FREE(cinstance); + if (pending) + { + hashtable_free(pending); + } + } } } @@ -348,7 +412,7 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) ss_dassert(GWBUF_LENGTH(packet) >= MYSQL_HEADER_LEN + 1); ss_dassert(MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN == GWBUF_LENGTH(packet)); - bool use_default = true; + bool fetch_from_server = true; cache_response_state_reset(&csdata->res); csdata->state = CACHE_IGNORING_RESPONSE; @@ -398,26 +462,81 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) { if (cache_rules_should_use(cinstance->rules, csdata->session)) { - GWBUF *result; - use_default = !route_using_cache(csdata, packet, &result); + GWBUF *response; + cache_result_t result = get_cached_response(csdata, packet, &response); - if (use_default) + switch (result) + { + case CACHE_RESULT_STALE: + { + // The value was found, but it was stale. Now we need to + // figure out whether somebody else is already fetching it. + + long key = hash_of_key(csdata->key); + + spinlock_acquire(&cinstance->pending_lock); + // TODO: Remove the internal locking of hashtable. The internal + // TODO: locking is no good if you need transactional behaviour. + // TODO: Now we lock twice. + void *value = hashtable_fetch(cinstance->pending, (void*)key); + if (!value) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(cinstance->pending, (void*)key, DUMMY_VALUE); + } + spinlock_release(&cinstance->pending_lock); + + if (!value) + { + // We were the first ones who hit the stale item. It's + // our responsibility now to fetch it. + if (log_decisions(csdata)) + { + MXS_NOTICE("Cache data is stale, fetching fresh from server."); + } + csdata->refreshing = true; + fetch_from_server = true; + break; + } + else + { + // Somebody is already fetching the new value. So, let's + // use the stale value. No point in hitting the server twice. + if (log_decisions(csdata)) + { + MXS_NOTICE("Cache data is stale but returning it, fresh " + "data is being fetched already."); + } + fetch_from_server = false; + } + } + break; + + case CACHE_RESULT_OK: + if (log_decisions(csdata)) + { + MXS_NOTICE("Using fresh data from cache."); + } + fetch_from_server = false; + break; + + default: + fetch_from_server = true; + } + + if (fetch_from_server) { csdata->state = CACHE_EXPECTING_RESPONSE; } else { csdata->state = CACHE_EXPECTING_NOTHING; - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Using data from cache."); - } gwbuf_free(packet); DCB *dcb = csdata->session->client_dcb; // TODO: This is not ok. Any filters before this filter, will not // TODO: see this data. - rv = dcb->func.write(dcb, result); + rv = dcb->func.write(dcb, response); } } } @@ -444,7 +563,7 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) } } - if (use_default) + if (fetch_from_server) { rv = csdata->down.routeQuery(csdata->down.instance, csdata->down.session, packet); } @@ -1079,22 +1198,24 @@ static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONF * @param value The result. * @return True if the query was satisfied from the query. */ -static bool route_using_cache(CACHE_SESSION_DATA *csdata, - const GWBUF *query, - GWBUF **value) +static cache_result_t get_cached_response(CACHE_SESSION_DATA *csdata, + const GWBUF *query, + GWBUF **value) { cache_result_t result = csdata->api->getKey(csdata->storage, csdata->default_db, query, csdata->key); if (result == CACHE_RESULT_OK) { - result = csdata->api->getValue(csdata->storage, csdata->key, CACHE_FLAGS_NONE, value); + uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; + + result = csdata->api->getValue(csdata->storage, csdata->key, flags, value); } else { MXS_ERROR("Could not create cache key."); } - return result == CACHE_RESULT_OK; + return result; } /** @@ -1138,4 +1259,19 @@ static void store_result(CACHE_SESSION_DATA *csdata) MXS_ERROR("Could not store cache item."); } } + + if (csdata->refreshing) + { + long key = hash_of_key(csdata->key); + + CACHE_INSTANCE *instance = csdata->instance; + + spinlock_acquire(&instance->pending_lock); + ss_dassert(hashtable_fetch(instance->pending, (void*)key) == DUMMY_VALUE); + ss_debug(int n =) hashtable_delete(instance->pending, (void*)key); + ss_dassert(n == 1); + spinlock_release(&instance->pending_lock); + + csdata->refreshing = false; + } } diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index 5b725f147..0b03cdeb5 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -29,7 +29,7 @@ MXS_BEGIN_DECLS #define CACHE_DEBUG_RULES (CACHE_DEBUG_MATCHING | CACHE_DEBUG_NON_MATCHING) #define CACHE_DEBUG_USAGE (CACHE_DEBUG_USE | CACHE_DEBUG_NON_USE) #define CACHE_DEBUG_MIN CACHE_DEBUG_NONE -#define CACHE_DEBUG_MAX (CACHE_DEBUG_RULES | CACHE_DEBUG_USAGE) +#define CACHE_DEBUG_MAX (CACHE_DEBUG_RULES | CACHE_DEBUG_USAGE | CACHE_DEBUG_DECISIONS) // Count #define CACHE_DEFAULT_MAX_RESULTSET_ROWS UINT_MAX From e9029b183e24b3cfb84462a8f83ec8714b5bb641 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 22 Nov 2016 18:41:10 +0200 Subject: [PATCH 15/48] Cache: Allow entries to be explicitly deleted --- .../modules/filter/cache/cache_storage_api.h | 11 +++++++ .../storage/storage_rocksdb/rocksdbstorage.cc | 11 +++++++ .../storage/storage_rocksdb/rocksdbstorage.h | 1 + .../storage_rocksdb/storage_rocksdb.cc | 31 ++++++++++++++++++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/server/modules/filter/cache/cache_storage_api.h b/server/modules/filter/cache/cache_storage_api.h index a68aecd93..e5bfeacb5 100644 --- a/server/modules/filter/cache/cache_storage_api.h +++ b/server/modules/filter/cache/cache_storage_api.h @@ -123,6 +123,17 @@ typedef struct cache_storage_api cache_result_t (*putValue)(CACHE_STORAGE* storage, const char* key, const GWBUF* value); + + /** + * Delete a value from the cache. + * + * @param storage Pointer to a CACHE_STORAGE. + * @param key A key generated with getKey. + * @return CACHE_RESULT_OK if item was successfully deleted. Note that + * CACHE_RESULT_OK may be returned also if the entry was not present. + */ + cache_result_t (*delValue)(CACHE_STORAGE* storage, + const char* key); } CACHE_STORAGE_API; #define CACHE_STORAGE_ENTRY_POINT "CacheGetStorageAPI" diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc index 75a8005ff..2de463818 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc @@ -476,3 +476,14 @@ cache_result_t RocksDBStorage::putValue(const char* pKey, const GWBUF* pValue) return status.ok() ? CACHE_RESULT_OK : CACHE_RESULT_ERROR; } + +cache_result_t RocksDBStorage::delValue(const char* pKey) +{ + ss_dassert(pKey); + + rocksdb::Slice key(pKey, ROCKSDB_KEY_LENGTH); + + rocksdb::Status status = m_sDb->Delete(writeOptions(), key); + + return status.ok() ? CACHE_RESULT_OK : CACHE_RESULT_ERROR; +} diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h index 1a3187517..a416be72a 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.h @@ -32,6 +32,7 @@ public: cache_result_t getKey(const char* zDefaultDB, const GWBUF* pQuery, char* pKey); cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppResult); cache_result_t putValue(const char* pKey, const GWBUF* pValue); + cache_result_t delValue(const char* pKey); private: RocksDBStorage(std::unique_ptr& sDb, diff --git a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc index b21037129..5c32a9b2e 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc @@ -147,6 +147,34 @@ cache_result_t putValue(CACHE_STORAGE* pStorage, return result; } +cache_result_t delValue(CACHE_STORAGE* pStorage, + const char* pKey) +{ + ss_dassert(pStorage); + ss_dassert(pKey); + + cache_result_t result = CACHE_RESULT_ERROR; + + try + { + result = reinterpret_cast(pStorage)->delValue(pKey); + } + catch (const std::bad_alloc&) + { + MXS_OOM(); + } + catch (const std::exception& x) + { + MXS_ERROR("Standard exception caught: %s", x.what()); + } + catch (...) + { + MXS_ERROR("Unknown exception caught."); + } + + return result; +} + } extern "C" @@ -161,7 +189,8 @@ CACHE_STORAGE_API* CacheGetStorageAPI() freeInstance, getKey, getValue, - putValue + putValue, + delValue, }; return &api; From 9d3e5a715bf604aa9cf769b726fdc6037d6f1c43 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 22 Nov 2016 18:49:47 +0200 Subject: [PATCH 16/48] Cache: Remove item if putting fails If an item cannot be put to the database, it is explicitly removed to ensure that we cannot have the situation that a stale item is continuously returned because the updating of the value fails for whatever reason. --- server/modules/filter/cache/cache.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.c index 0a5da258b..69d518944 100644 --- a/server/modules/filter/cache/cache.c +++ b/server/modules/filter/cache/cache.c @@ -1256,7 +1256,14 @@ static void store_result(CACHE_SESSION_DATA *csdata) if (result != CACHE_RESULT_OK) { - MXS_ERROR("Could not store cache item."); + MXS_ERROR("Could not store cache item, deleting it."); + + result = csdata->api->delValue(csdata->storage, csdata->key); + + if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) + { + MXS_ERROR("Could not delete cache item."); + } } } From 6d1265ade1852c3e2e23944def18113b4d8f3a8b Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 13:53:06 +0200 Subject: [PATCH 17/48] Length of a packet requires 32 bit. --- include/maxscale/protocol/mysql.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 1f91d4730..01fdd1615 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -299,7 +299,7 @@ static inline uint8_t MYSQL_GET_PACKET_NO(const uint8_t* header) return header[3]; } -static inline uint8_t MYSQL_GET_PACKET_LEN(const uint8_t* header) +static inline uint32_t MYSQL_GET_PACKET_LEN(const uint8_t* header) { return gw_mysql_get_byte3(header); } From 35620f8daeee3604947ea239457fc568171050e6 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 14:35:46 +0200 Subject: [PATCH 18/48] Build compare using embedded library For whatever reason compare fails to load qc_mysqlembedded if it has not been built with the embedded library. Needs to be sorted out at some point. --- query_classifier/test/CMakeLists.txt | 56 ++++++++++++++++++---------- query_classifier/test/compare.cc | 3 ++ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/query_classifier/test/CMakeLists.txt b/query_classifier/test/CMakeLists.txt index 8a11ba495..68de663a7 100644 --- a/query_classifier/test/CMakeLists.txt +++ b/query_classifier/test/CMakeLists.txt @@ -1,28 +1,44 @@ -# Use the client libraries in MaxScale core -include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR}) +# Include the embedded library headers +if (BUILD_QC_MYSQLEMBEDDED) + subdirs(MYSQL_INCLUDE_DIR_ALL ${MYSQL_EMBEDDED_INCLUDE_DIR}) + foreach(DIR ${MYSQL_INCLUDE_DIR_ALL}) + include_directories(${DIR}) + endforeach() + include_directories(${MYSQL_EMBEDDED_INCLUDE_DIR}/..) -add_executable(classify classify.c) -target_link_libraries(classify maxscale-common) + if(${ERRMSG} MATCHES "ERRMSG-NOTFOUND") + message(FATAL_ERROR "The errmsg.sys file was not found, please define the path with -DERRMSG=") + else() + if(${CMAKE_VERSION} VERSION_LESS 2.8) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR}) + else() + file(COPY ${ERRMSG} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + endif() + endif() -add_executable(compare compare.cc) -target_link_libraries(compare maxscale-common) + add_executable(classify classify.c) + target_link_libraries(classify maxscale-common) -add_executable(crash_qc_sqlite crash_qc_sqlite.c) -target_link_libraries(crash_qc_sqlite maxscale-common) + add_executable(compare compare.cc) + target_link_libraries(compare maxscale-common) -add_test(TestQC_Crash_qcsqlite crash_qc_sqlite) + add_executable(crash_qc_sqlite crash_qc_sqlite.c) + target_link_libraries(crash_qc_sqlite maxscale-common) -add_test(TestQC_MySQLEmbedded classify qc_mysqlembedded ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) -add_test(TestQC_SqLite classify qc_sqlite ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) + add_test(TestQC_Crash_qcsqlite crash_qc_sqlite) -add_test(TestQC_CompareCreate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/create.test) -add_test(TestQC_CompareDelete compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/delete.test) -add_test(TestQC_CompareInsert compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/insert.test) -add_test(TestQC_CompareJoin compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/join.test) -add_test(TestQC_CompareSelect compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/select.test) -add_test(TestQC_CompareSet compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/set.test) -add_test(TestQC_CompareUpdate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/update.test) -add_test(TestQC_CompareMaxScale compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/maxscale.test) -add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ") + add_test(TestQC_MySQLEmbedded classify qc_mysqlembedded ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) + add_test(TestQC_SqLite classify qc_sqlite ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) + + add_test(TestQC_CompareCreate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/create.test) + add_test(TestQC_CompareDelete compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/delete.test) + add_test(TestQC_CompareInsert compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/insert.test) + add_test(TestQC_CompareJoin compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/join.test) + add_test(TestQC_CompareSelect compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/select.test) + add_test(TestQC_CompareSet compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/set.test) + add_test(TestQC_CompareUpdate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/update.test) + add_test(TestQC_CompareMaxScale compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/maxscale.test) + add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ") +endif() add_subdirectory(canonical_tests) diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 102b9e42b..1a5d6fd20 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -20,6 +20,9 @@ #include #include #include +#define MYSQL_COM_QUIT COM_QUIT +#define MYSQL_COM_INIT_DB COM_INIT_DB +#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER #include #include #include From d9642dd5aedd3f48d55ffcad0d109d1bd87987f8 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 14:47:24 +0200 Subject: [PATCH 19/48] MXS-1025: All logging now behing the log_level Earlier a successful parsing but failure to classify was always logged. --- query_classifier/qc_sqlite/qc_sqlite.c | 33 ++++++++++--------- .../test/qc_sqlite_unsupported.test | 8 +++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 5e9b1eb42..a6f351c0d 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -444,25 +444,28 @@ static void parse_query_string(const char* query, size_t len) } } } - else + else if (!this_thread.info->initializing) // If we are initializing, the query will not be classified. { - if (qc_info_was_tokenized(this_thread.info->status)) + if (this_unit.log_level > QC_LOG_NOTHING) { - // This suggests a callback from the parser into this module is not made. - format = - "Statement was classified only based on keywords, " - "even though the statement was parsed: \"%.*s%s\""; + if (qc_info_was_tokenized(this_thread.info->status)) + { + // This suggests a callback from the parser into this module is not made. + format = + "Statement was classified only based on keywords, " + "even though the statement was parsed: \"%.*s%s\""; - MXS_WARNING(format, l, query, suffix); - } - else if (!qc_info_was_parsed(this_thread.info->status)) - { - // This suggests there are keywords that should be recognized but are not, - // a tentative classification cannot be (or is not) made using the keywords - // seen and/or a callback from the parser into this module is not made. - format = "Statement was parsed, but not classified: \"%.*s%s\""; + MXS_WARNING(format, l, query, suffix); + } + else if (!qc_info_was_parsed(this_thread.info->status)) + { + // This suggests there are keywords that should be recognized but are not, + // a tentative classification cannot be (or is not) made using the keywords + // seen and/or a callback from the parser into this module is not made. + format = "Statement was parsed, but not classified: \"%.*s%s\""; - MXS_ERROR(format, l, query, suffix); + MXS_WARNING(format, l, query, suffix); + } } } diff --git a/query_classifier/test/qc_sqlite_unsupported.test b/query_classifier/test/qc_sqlite_unsupported.test index ae2ffd9f1..defc70dd7 100644 --- a/query_classifier/test/qc_sqlite_unsupported.test +++ b/query_classifier/test/qc_sqlite_unsupported.test @@ -34,3 +34,11 @@ set @`TeST`=4; # (Sqlite3 error: SQL logic error or missing database, unrecognized token: "@"): "set @=4" # # sqlite3GetToken needs to be modified to accept a quoted variable name. + +SAVEPOINT sa_savepoint_1 +#warning: [qc_sqlite] Statement was neither parsed nor recognized from keywords +# (Sqlite3 error: SQL logic error or missing database, near "SNAPSHOT": syntax error): "SNAPSHOT s" + +RELEASE SAVEPOINT sa_savepoint_1 +# warning: [qc_sqlite] Statement was neither parsed nor recognized from keywords +# (Sqlite3 error: SQL logic error or missing database, near "RELEASE": syntax error): "RELEASE SNAPSHOT s" \ No newline at end of file From 221f2f79f07fb504a4a1943db0eaef03ecdbc4f2 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 24 Nov 2016 02:49:08 +0200 Subject: [PATCH 20/48] Only call destroyInstance for valid instances If the router of filter failed to create an instance, destroyInstance should not be called. --- server/core/service.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/core/service.c b/server/core/service.c index 9380d7c44..cbc14e781 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -1889,7 +1889,7 @@ void service_shutdown() { svc->svc_do_shutdown = true; /* Call destroyInstance hook for routers */ - if (svc->router->destroyInstance) + if (svc->router->destroyInstance && svc->router_instance) { svc->router->destroyInstance(svc->router_instance); } @@ -1898,7 +1898,7 @@ void service_shutdown() FILTER_DEF **filters = svc->filters; for (int i=0; i < svc->n_filters; i++) { - if (filters[i]->obj->destroyInstance) + if (filters[i]->obj->destroyInstance && filters[i]->filter) { /* Call destroyInstance hook for filters */ filters[i]->obj->destroyInstance(filters[i]->filter); From d309444540d50fce420d426fe42dd71510d9916c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 20:07:15 +0200 Subject: [PATCH 21/48] Add module command documetation Added a document that describes the module command system and added the necessary information in the dbfwfilter documentation. The release notes also point to the newly created document. --- Documentation/Documentation-Contents.md | 1 + .../Filters/Database-Firewall-Filter.md | 19 +++++- Documentation/Reference/Module-Commands.md | 65 +++++++++++++++++++ .../MaxScale-2.1.0-Release-Notes.md | 16 +++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 Documentation/Reference/Module-Commands.md diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index 0b2fe2a43..5e7b41084 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -32,6 +32,7 @@ - [Routing Hints](Reference/Hint-Syntax.md) - [MaxBinlogCheck](Reference/MaxBinlogCheck.md) - [MaxScale REST API](REST-API/API.md) + - [Module Commands](Reference/Module-Commands.md) ## Tutorials diff --git a/Documentation/Filters/Database-Firewall-Filter.md b/Documentation/Filters/Database-Firewall-Filter.md index 4301ffacd..615109fe2 100644 --- a/Documentation/Filters/Database-Firewall-Filter.md +++ b/Documentation/Filters/Database-Firewall-Filter.md @@ -161,12 +161,29 @@ The `users` directive defines the users to which the rule should be applied. The first keyword is `users`, which identifies this line as a user definition line. -The second component is a list of user names and network addresses in the format *`user`*`@`*`0.0.0.0`*. The first part is the user name and the second part is the network address. You can use the `%` character as the wildcard to enable user name matching from any address or network matching for all users. After the list of users and networks the keyword match is expected. +The second component is a list of user names and network addresses in the format *`user`*`@`*`0.0.0.0`*. The first part is the user name and the second part is the network address. You can use the `%` character as the wildcard to enable user name matching from any address or network matching for all users. After the list of users and networks the keyword match is expected. After this either the keyword `any` `all` or `strict_all` is expected. This defined how the rules are matched. If `any` is used when the first rule is matched the query is considered blocked and the rest of the rules are skipped. If instead the `all` keyword is used all rules must match for the query to be blocked. The `strict_all` is the same as `all` but it checks the rules from left to right in the order they were listed. If one of these does not match, the rest of the rules are not checked. This could be useful in situations where you would for example combine `limit_queries` and `regex` rules. By using `strict_all` you can have the `regex` rule first and the `limit_queries` rule second. This way the rule only matches if the `regex` rule matches enough times for the `limit_queries` rule to match. After the matching part comes the rules keyword after which a list of rule names is expected. This allows reusing of the rules and enables varying levels of query restriction. +## Module commands + +Read [Module Commands](../Reference/Module-Commands.md) documentation for details about module commands. + +The dbfwfilter supports the following module commands. + +### `dbfwfilter::rules/reload [FILE]` + +Load a new rule file or reload the current rules. New rules are only taken into +use if they are successfully loaded and in cases where loading of the rules +fail, the old rules remain in use. The _FILE_ argument is an optional path to a +rule file and if it is not defined, the current rule file is used. + +### `dbfwfilter::rules` + +Shows the current statistics of the rules. + ## Use Cases ### Use Case 1 - Prevent rapid execution of specific queries diff --git a/Documentation/Reference/Module-Commands.md b/Documentation/Reference/Module-Commands.md new file mode 100644 index 000000000..2e4f177e7 --- /dev/null +++ b/Documentation/Reference/Module-Commands.md @@ -0,0 +1,65 @@ +# Module commands + +Introduced in MaxScale 2.1, the module commands are special, module-specific +commands. They allow the modules to expand beyond the capabilities of the +module API. Currently, only MaxAdmin implements an interface to the module +commands. + +All registered module commands can be shown with `maxadmin list functions` and +they can be executed with `maxadmin call function ARGS...` where +__ is the domain where the module registered the function and __ +is the name of the function. _ARGS_ is a function specific list of arguments. + +## Developer reference + +The module command API is defined in the _modulecmd.h_ header. It consists of +various functions to register and call module commands. Read the function +documentation in the header for more details. + +The following example registers the module command _my_command_ in the _my_module_ domain. + +``` +#include + +bool my_simple_cmd(const MODULECMD_ARG *argv) +{ + printf("%d arguments given\n", argv->argc); +} + +int main(int argc, char **argv) +{ + modulecmd_arg_type_t my_args[] = + { + {MODULECMD_ARG_BOOLEAN, "This is a boolean parameter"}, + {MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, "This is an optional string parameter"} + }; + + // Register the command + modulecmd_register_command("my_module", "my_command", my_simple_cmd, 2, my_args); + + // Find the registered command + const MODULECMD *cmd = modulecmd_find_command("my_module", "my_command"); + + // Parse the arguments for the command + const void *arglist[] = {"true", "optional string"}; + MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, arglist, 2); + + // Call the module command + modulecmd_call_command(cmd, arg); + + // Free the parsed arguments + modulecmd_arg_free(arg); + return 0; +} +``` + +The array of _modulecmd_arg_type_t_ type is used to tell what kinds of arguments +the command expects. The first argument is a SERVER which will be replaced with a +pointer to a server. The second argument is an optional string argument. + +Arguments are passed to the parsing function as an array of void pointers. They +are interpreted as the types the command expects. + +When the module command is executed, the _argv_ parameter for the +_my_simple_cmd_ contains the parsed arguments received from the caller of the +command. diff --git a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md index 907b549bf..6d77070ed 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md @@ -104,6 +104,22 @@ following new commands were added to maxadmin, see output of `maxadmin help With these new features, you can start MaxScale without the servers and define them later. +# Module commands + +## Module commands + +Introduced in MaxScale 2.1, the module commands are special, module-specific +commands. They allow the modules to expand beyound the capabilities of the +module API. Currently, only MaxAdmin implements an interface to the module +commands. + +All registered module commands can be shown with `maxadmin list functions` and +they can be executed with `maxadmin call function ARGS...` where +__ is the domain where the module registered the function and __ +is the name of the function. _ARGS_ is a function specific list of arguments. + +Read [Module Commands](../Reference/Module-Commands.md) documentation for more details. + ### Amazon RDS Aurora monitor The new [Aurora Monitor](../Monitors/Aurora-Monitor.md) module allows monitoring From 8ef99c9066878dcd18e3316acd6edb0e79989b40 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 15:18:04 +0200 Subject: [PATCH 22/48] Move configuration changes to a common file The config_runtime.h header contains functions that can be used to manipulate the running configuration. Currently the header contains the function to create, add, remove and destroy servers. --- include/maxscale/config_runtime.h | 81 +++++++++ include/maxscale/monitor.h | 2 +- include/maxscale/server.h | 37 ++-- include/maxscale/service.h | 2 +- server/core/CMakeLists.txt | 2 +- server/core/config_runtime.c | 190 +++++++++++++++++++++ server/core/monitor.c | 2 +- server/core/server.c | 106 +----------- server/core/service.c | 2 +- server/modules/routing/debugcli/debugcmd.c | 50 +----- 10 files changed, 295 insertions(+), 179 deletions(-) create mode 100644 include/maxscale/config_runtime.h create mode 100644 server/core/config_runtime.c diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h new file mode 100644 index 000000000..775902eeb --- /dev/null +++ b/include/maxscale/config_runtime.h @@ -0,0 +1,81 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +/** + * @file config_runtime.h - Functions for runtime configuration modifications + */ + +#include + +#include +#include +#include +#include + +/** + * @brief Create a new server + * + * This function creates a new, persistent server by first allocating a new + * server and then storing the resulting configuration file on disk. This + * function should be used only from administrative interface modules and internal + * modules should use server_alloc() instead. + * + * @param name Server name + * @param address Network address + * @param port Network port + * @param protocol Protocol module name + * @param authenticator Authenticator module name + * @param options Options for the authenticator module + * @return True on success, false if an error occurred + */ +bool runtime_create_server(const char *name, const char *address, + const char *port, const char *protocol, + const char *authenticator, const char *options); + +/** + * @brief Destroy a server + * + * This removes any created server configuration files and marks the server removed + * If the server is not in use. + * + * @param server Server to destroy + * @return True if server was destroyed + */ +bool runtime_destroy_server(SERVER *server); + +/** + * @brief Link a server to an object + * + * This function links the server to another object. The target can be either + * a monitor or a service. + * + * @param server Server to link + * @param target The monitor or service where the server is added + * @return True if the object was found and the server was linked to it, false + * if no object matching @c target was found + */ +bool runtime_link_server(SERVER *server, const char *target); + +/** + * @brief Unlink a server from an object + * + * This function unlinks the server from another object. The target can be either + * a monitor or a service. + * + * @param server Server to unlink + * @param target The monitor or service from which the server is removed + * @return True if the object was found and the server was unlinked from it, false + * if no object matching @c target was found + */ +bool runtime_unlink_server(SERVER *server, const char *target); diff --git a/include/maxscale/monitor.h b/include/maxscale/monitor.h index 46a9df814..5c458ce46 100644 --- a/include/maxscale/monitor.h +++ b/include/maxscale/monitor.h @@ -203,7 +203,7 @@ struct monitor extern MONITOR *monitor_alloc(char *, char *); extern void monitor_free(MONITOR *); -extern MONITOR *monitor_find(char *); +extern MONITOR *monitor_find(const char *); extern void monitorAddServer(MONITOR *mon, SERVER *server); extern void monitorRemoveServer(MONITOR *mon, SERVER *server); extern void monitorAddUser(MONITOR *, char *, char *); diff --git a/include/maxscale/server.h b/include/maxscale/server.h index cfde36a10..dbbd9d7e4 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -227,25 +227,20 @@ extern SERVER* server_alloc(const char *name, const char *address, unsigned shor const char *auth_options); /** - * @brief Create a new server + * @brief Find a server that can be reused * - * This function creates a new, persistent server by first allocating a new - * server and then storing the resulting configuration file on disk. This - * function should be used only from administrative interface modules and internal - * modules should use server_alloc() instead. + * A server that has been destroyed will not be deleted but only deactivated. * - * @param name Server name - * @param address Network address - * @param port Network port - * @param protocol Protocol module name - * @param authenticator Authenticator module name - * @param options Options for the authenticator module - * @return True on success, false if an error occurred + * @param name Name of the server + * @param protocol Protocol used by the server + * @param authenticator The authenticator module of the server + * @param auth_options Options for the authenticator + * @return Reusable SERVER or NULL if no servers matching the criteria were + * found + * @see runtime_create_server */ -extern bool server_create(const char *name, const char *address, const char *port, - const char *protocol, const char *authenticator, - const char *options); - +SERVER* server_find_destroyed(const char *name, const char *protocol, + const char *authenticator, const char *auth_options); /** * @brief Serialize a server to a file * @@ -258,16 +253,6 @@ extern bool server_create(const char *name, const char *address, const char *por */ bool server_serialize(const SERVER *server); -/** - * @brief Destroy a server - * - * This removes any created server configuration files and marks the server removed - * If the server is not in use. - * @param server Server to destroy - * @return True if server was destroyed - */ -bool server_destroy(SERVER *server); - extern int server_free(SERVER *); extern SERVER *server_find_by_unique_name(const char *name); extern SERVER *server_find(char *, unsigned short); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 95ac954c6..8e2fa8ab9 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -190,7 +190,7 @@ typedef enum count_spec_t extern SERVICE *service_alloc(const char *, const char *); extern int service_free(SERVICE *); -extern SERVICE *service_find(char *); +extern SERVICE *service_find(const char *); extern int service_isvalid(SERVICE *); extern int serviceAddProtocol(SERVICE *service, char *name, char *protocol, char *address, unsigned short port, diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index d232fe559..847d2fa42 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c modulecmd.c) +add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c modulecmd.c ) target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++) diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c new file mode 100644 index 000000000..05760e113 --- /dev/null +++ b/server/core/config_runtime.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include +#include + +static SPINLOCK crt_lock = SPINLOCK_INIT; + +bool runtime_link_server(SERVER *server, const char *target) +{ + spinlock_acquire(&crt_lock); + + bool rval = false; + SERVICE *service = service_find(target); + MONITOR *monitor = service ? NULL : monitor_find(target); + + if (service || monitor) + { + rval = true; + + if (service) + { + serviceAddBackend(service, server); + service_serialize_servers(service); + } + else if (monitor) + { + monitorAddServer(monitor, server); + monitor_serialize_servers(monitor); + } + + const char *type = service ? "service" : "monitor"; + + MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, type, target); + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_unlink_server(SERVER *server, const char *target) +{ + spinlock_acquire(&crt_lock); + + bool rval = false; + SERVICE *service = service_find(target); + MONITOR *monitor = service ? NULL : monitor_find(target); + + if (service || monitor) + { + rval = true; + + if (service) + { + serviceRemoveBackend(service, server); + service_serialize_servers(service); + } + else if (monitor) + { + monitorRemoveServer(monitor, server); + monitor_serialize_servers(monitor); + } + + const char *type = service ? "service" : "monitor"; + MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, type, target); + } + + spinlock_release(&crt_lock); + return rval; +} + + +bool runtime_create_server(const char *name, const char *address, const char *port, + const char *protocol, const char *authenticator, + const char *authenticator_options) +{ + spinlock_acquire(&crt_lock); + bool rval = false; + + if (server_find_by_unique_name(name) == NULL) + { + // TODO: Get default values from the protocol module + if (port == NULL) + { + port = "3306"; + } + if (protocol == NULL) + { + protocol = "MySQLBackend"; + } + if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) + { + MXS_ERROR("No authenticator defined for server '%s' and no default " + "authenticator for protocol '%s'.", name, protocol); + spinlock_release(&crt_lock); + return false; + } + + /** First check if this service has been created before */ + SERVER *server = server_find_destroyed(name, protocol, authenticator, + authenticator_options); + + if (server) + { + /** Found old server, replace network details with new ones and + * reactivate it */ + snprintf(server->name, sizeof(server->name), "%s", address); + server->port = atoi(port); + server->is_active = true; + rval = true; + } + else + { + /** + * server_alloc will add the server to the global list of + * servers so we don't need to manually add it. + */ + server = server_alloc(name, address, atoi(port), protocol, + authenticator, authenticator_options); + } + + if (server && server_serialize(server)) + { + rval = true; + } + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_destroy_server(SERVER *server) +{ + spinlock_acquire(&crt_lock); + bool rval = false; + + if (service_server_in_use(server) || monitor_server_in_use(server)) + { + MXS_ERROR("Cannot destroy server '%s' as it is used by at least one " + "service or monitor", server->unique_name); + } + else + { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), + server->unique_name); + + if (unlink(filename) == -1) + { + if (errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else + { + rval = true; + MXS_WARNING("Server '%s' was not created at runtime. Remove the " + "server manually from the correct configuration file.", + server->unique_name); + } + } + else + { + rval = true; + } + + if (rval) + { + MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name, + server->name, server->port); + server->is_active = false; + } + } + + spinlock_release(&crt_lock); + return rval; +} diff --git a/server/core/monitor.c b/server/core/monitor.c index 50feaae6b..6a22ee70a 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -515,7 +515,7 @@ monitorList(DCB *dcb) * @return Pointer to the monitor or NULL */ MONITOR * -monitor_find(char *name) +monitor_find(const char *name) { MONITOR *ptr; diff --git a/server/core/server.c b/server/core/server.c index 570b47a6e..a06088baf 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -1228,8 +1228,7 @@ bool server_serialize(const SERVER *server) return rval; } -/** Try to find a server with a matching name that has been destroyed */ -static SERVER* find_destroyed_server(const char *name, const char *protocol, +SERVER* server_find_destroyed(const char *name, const char *protocol, const char *authenticator, const char *auth_options) { spinlock_acquire(&server_spin); @@ -1254,106 +1253,3 @@ static SERVER* find_destroyed_server(const char *name, const char *protocol, return server; } - -bool server_create(const char *name, const char *address, const char *port, - const char *protocol, const char *authenticator, - const char *authenticator_options) -{ - bool rval = false; - - if (server_find_by_unique_name(name) == NULL) - { - // TODO: Get default values from the protocol module - if (port == NULL) - { - port = "3306"; - } - if (protocol == NULL) - { - protocol = "MySQLBackend"; - } - if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL) - { - MXS_ERROR("No authenticator defined for server '%s' and no default " - "authenticator for protocol '%s'.", name, protocol); - return false; - } - - /** First check if this service has been created before */ - SERVER *server = find_destroyed_server(name, protocol, authenticator, - authenticator_options); - - if (server) - { - /** Found old server, replace network details with new ones and - * reactivate it */ - snprintf(server->name, sizeof(server->name), "%s", address); - server->port = atoi(port); - server->is_active = true; - rval = true; - } - else - { - /** - * server_alloc will add the server to the global list of - * servers so we don't need to manually add it. - */ - server = server_alloc(name, address, atoi(port), protocol, - authenticator, authenticator_options); - } - - if (server && server_serialize(server)) - { - rval = true; - } - } - - return rval; -} - -bool server_destroy(SERVER *server) -{ - bool rval = false; - - if (service_server_in_use(server) || monitor_server_in_use(server)) - { - MXS_ERROR("Cannot destroy server '%s' as it is used by at least one " - "service or monitor", server->unique_name); - } - else - { - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), - server->unique_name); - - if (unlink(filename) == -1) - { - if (errno != ENOENT) - { - char err[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s", - filename, errno, strerror_r(errno, err, sizeof(err))); - } - else - { - rval = true; - MXS_WARNING("Server '%s' was not created at runtime. Remove the " - "server manually from the correct configuration file.", - server->unique_name); - } - } - else - { - rval = true; - } - - if (rval) - { - MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name, - server->name, server->port); - server->is_active = false; - } - } - - return rval; -} diff --git a/server/core/service.c b/server/core/service.c index cbc14e781..707360e06 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -1218,7 +1218,7 @@ serviceSetFilters(SERVICE *service, char *filters) * @return The service or NULL if not found */ SERVICE * -service_find(char *servname) +service_find(const char *servname) { SERVICE *service; diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index a7a94e03a..24e272398 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -73,6 +73,7 @@ #include #include #include +#include #include #include @@ -804,28 +805,9 @@ static void cmd_AddServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3 for (int i = 0; i < items && values[i]; i++) { - SERVICE *service = service_find(values[i]); - MONITOR *monitor = monitor_find(values[i]); - - if (service || monitor) + if (runtime_link_server(server, values[i])) { - ss_dassert(service == NULL || monitor == NULL); - - if (service) - { - serviceAddBackend(service, server); - service_serialize_servers(service); - } - else if (monitor) - { - monitorAddServer(monitor, server); - monitor_serialize_servers(monitor); - } - - const char *target = service ? "service" : "monitor"; - - MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, target, values[i]); - dcb_printf(dcb, "Added server '%s' to %s '%s'\n", server->unique_name, target, values[i]); + dcb_printf(dcb, "Added server '%s' to '%s'\n", server->unique_name, values[i]); } else { @@ -872,27 +854,9 @@ static void cmd_RemoveServer(DCB *dcb, SERVER *server, char *v1, char *v2, char for (int i = 0; i < items && values[i]; i++) { - SERVICE *service = service_find(values[i]); - MONITOR *monitor = monitor_find(values[i]); - - if (service || monitor) + if (runtime_unlink_server(server, values[i])) { - ss_dassert(service == NULL || monitor == NULL); - - if (service) - { - serviceRemoveBackend(service, server); - service_serialize_servers(service); - } - else if (monitor) - { - monitorRemoveServer(monitor, server); - monitor_serialize_servers(monitor); - } - - const char *target = service ? "service" : "monitor"; - MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, target, values[i]); - dcb_printf(dcb, "Removed server '%s' from %s '%s'\n", server->unique_name, target, values[i]); + dcb_printf(dcb, "Removed server '%s' from '%s'\n", server->unique_name, values[i]); } else { @@ -1044,7 +1008,7 @@ static void createServer(DCB *dcb, char *name, char *address, char *port, if (server_find_by_unique_name(name) == NULL) { - if (server_create(name, address, port, protocol, authenticator, authenticator_options)) + if (runtime_create_server(name, address, port, protocol, authenticator, authenticator_options)) { dcb_printf(dcb, "Created server '%s'\n", name); } @@ -1093,7 +1057,7 @@ static void destroyServer(DCB *dcb, SERVER *server) char name[strlen(server->unique_name) + 1]; strcpy(name, server->unique_name); - if (server_destroy(server)) + if (runtime_destroy_server(server)) { dcb_printf(dcb, "Destroyed server '%s'\n", name); } From e75a27e8db0ea3708ed1b74aeead045e357c689c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 16:01:43 +0200 Subject: [PATCH 23/48] Add server alteration to config_runtime.h Moved the alteration of servers done in debugcmd.c to config_runtime.c and altered them to be better suited as API functions. --- include/maxscale/config_runtime.h | 27 +++++ server/core/config_runtime.c | 70 ++++++++++++ server/modules/routing/debugcli/debugcmd.c | 125 ++++++++------------- 3 files changed, 143 insertions(+), 79 deletions(-) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h index 775902eeb..db8674493 100644 --- a/include/maxscale/config_runtime.h +++ b/include/maxscale/config_runtime.h @@ -79,3 +79,30 @@ bool runtime_link_server(SERVER *server, const char *target); * if no object matching @c target was found */ bool runtime_unlink_server(SERVER *server, const char *target); + +/** + * @brief Alter server parameters + * + * @param server Server to alter + * @param key Key to modify + * @param value New value + * @return True if @c key was one of the supported parameters + */ +bool runtime_alter_server(SERVER *server, char *key, char *value); + +/** + * @brief Enable SSL for a server + * + * The @c key , @c cert and @c ca parameters are required. @c version and @c depth + * are optional. + * + * @param server Server to configure + * @param key Path to SSL private key + * @param cert Path to SSL public certificate + * @param ca Path to certificate authority + * @param version Required SSL Version + * @param depth Certificate verification depth + * @return True if SSL was successfully enabled + */ +bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, + const char *ca, const char *version, const char *depth); diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 05760e113..2fa5fabc0 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -11,6 +11,7 @@ * Public License. */ +#include #include #include #include @@ -188,3 +189,72 @@ bool runtime_destroy_server(SERVER *server) spinlock_release(&crt_lock); return rval; } + +bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, + const char *ca, const char *version, const char *depth) +{ + spinlock_acquire(&crt_lock); + bool rval = false; + + if (key && cert && ca) + { + CONFIG_CONTEXT *obj = config_context_create(server->unique_name); + + if (obj && config_add_param(obj, "ssl_key", key) && + config_add_param(obj, "ssl_cert", cert) && + config_add_param(obj, "ssl_ca_cert", ca) && + (!version || config_add_param(obj, "ssl_version", version)) && + (!depth || config_add_param(obj, "ssl_cert_verify_depth", depth))) + { + int err = 0; + SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err); + + if (err == 0 && ssl && listener_init_SSL(ssl) == 0) + { + /** Sync to prevent reads on partially initialized server_ssl */ + atomic_synchronize(); + + server->server_ssl = ssl; + if (server_serialize(server)) + { + rval = true; + } + } + } + + config_context_free(obj); + } + + spinlock_release(&crt_lock); + return rval; +} + +bool runtime_alter_server(SERVER *server, char *key, char *value) +{ + spinlock_acquire(&crt_lock); + bool valid = true; + + if (strcmp(key, "address") == 0) + { + server_update_address(server, value); + } + else if (strcmp(key, "port") == 0) + { + server_update_port(server, atoi(value)); + } + else if (strcmp(key, "monuser") == 0) + { + server_update_credentials(server, value, server->monpw); + } + else if (strcmp(key, "monpw") == 0) + { + server_update_credentials(server, server->monuser, value); + } + else + { + valid = false; + } + + spinlock_release(&crt_lock); + return valid; +} diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 24e272398..2f215733c 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1080,71 +1080,6 @@ struct subcommand destroyoptions[] = } }; -static bool handle_alter_server(SERVER *server, char *key, char *value) -{ - bool valid = true; - - if (strcmp(key, "address") == 0) - { - server_update_address(server, value); - } - else if (strcmp(key, "port") == 0) - { - server_update_port(server, atoi(value)); - } - else if (strcmp(key, "monuser") == 0) - { - server_update_credentials(server, value, server->monpw); - } - else if (strcmp(key, "monpw") == 0) - { - server_update_credentials(server, server->monuser, value); - } - else - { - valid = false; - } - - return valid; -} - -void handle_server_ssl(DCB *dcb, SERVER *server, CONFIG_CONTEXT *obj) -{ - if (config_have_required_ssl_params(obj)) - { - int err = 0; - SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err); - - if (err == 0 && ssl && listener_init_SSL(ssl) == 0) - { - /** Sync to prevent reads on partially initialized server_ssl */ - atomic_synchronize(); - - server->server_ssl = ssl; - if (server_serialize(server)) - { - dcb_printf(dcb, "SSL enabled for server '%s'\n", server->unique_name); - } - else - { - dcb_printf(dcb, "SSL enabled for server '%s' but persisting " - "it to disk failed, see log for more details.\n", - server->unique_name); - } - } - else - { - dcb_printf(dcb, "Enabling SSL for server '%s' failed, see log " - "for more details.\n", server->unique_name); - } - } - else - { - dcb_printf(dcb, "Error: SSL configuration requires the following parameters:\n" - "ssl=required ssl_key=PATH ssl_cert=PATH ssl_ca_cert=PATH\n"); - } -} - /** * @brief Process multiple alter operations at once * @@ -1159,6 +1094,12 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11}; const int items = sizeof(values) / sizeof(values[0]); CONFIG_CONTEXT *obj = NULL; + char *ssl_key = NULL; + char *ssl_cert = NULL; + char *ssl_ca = NULL; + char *ssl_version = NULL; + char *ssl_depth = NULL; + bool enable = false; for (int i = 0; i < items && values[i]; i++) { @@ -1171,19 +1112,33 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, if (config_is_ssl_parameter(key)) { - /** - * All the required SSL parameters must be defined at once to - * enable SSL for created servers. This removes the problem - * of partial configuration and allows a somewhat atomic - * operation. - */ - if ((obj == NULL && (obj = config_context_create(server->unique_name)) == NULL) || - (!config_add_param(obj, key, value))) + if (strcmp("ssl_cert", key) == 0) { - dcb_printf(dcb, "Internal error, see log for more details\n"); + ssl_cert = value; + } + else if (strcmp("ssl_ca_cert", key) == 0) + { + ssl_ca = value; + } + else if (strcmp("ssl_key", key) == 0) + { + ssl_key = value; + } + else if (strcmp("ssl_version", key) == 0) + { + ssl_version = value; + } + else if (strcmp("ssl_cert_verify_depth", key) == 0) + { + ssl_depth = value; + } + else + { + enable = strcmp("ssl", key) == 0 && strcmp(value, "required") == 0; + /** Must be 'ssl' */ } } - else if (!handle_alter_server(server, key, value)) + else if (!runtime_alter_server(server, key, value)) { dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value); } @@ -1194,11 +1149,23 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, } } - if (obj) + if (enable || ssl_key || ssl_cert || ssl_ca) { - /** We have SSL parameters, try to process them */ - handle_server_ssl(dcb, server, obj); - config_context_free(obj); + if (enable && ssl_key && ssl_cert && ssl_ca) + { + /** We have SSL parameters, try to process them */ + if (!runtime_enable_server_ssl(server, ssl_key, ssl_cert, ssl_ca, + ssl_version, ssl_depth)) + { + dcb_printf(dcb, "Enabling SSL for server '%s' failed, see log " + "for more details.\n", server->unique_name); + } + } + else + { + dcb_printf(dcb, "Error: SSL configuration requires the following parameters:\n" + "ssl=required ssl_key=PATH ssl_cert=PATH ssl_ca_cert=PATH\n"); + } } } From 498395cd3d206d7a0bd6dba311dcb9f782950eba Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 22 Nov 2016 16:06:18 +0200 Subject: [PATCH 24/48] Add monitor alteration to config_runtime.h Moved the monitor alteration to config_runtime.c. --- include/maxscale/config_runtime.h | 10 +++ server/core/config_runtime.c | 77 ++++++++++++++++++++++ server/modules/routing/debugcli/debugcmd.c | 77 +--------------------- 3 files changed, 88 insertions(+), 76 deletions(-) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h index db8674493..acb56bba8 100644 --- a/include/maxscale/config_runtime.h +++ b/include/maxscale/config_runtime.h @@ -106,3 +106,13 @@ bool runtime_alter_server(SERVER *server, char *key, char *value); */ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, const char *ca, const char *version, const char *depth); + +/** + * @brief Alter monitor parameters + * + * @param monitor Monitor to aler + * @param key Key to modify + * @param value New value + * @return True if @c key was one of the supported parameters + */ +bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value); diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 2fa5fabc0..854be86be 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -258,3 +258,80 @@ bool runtime_alter_server(SERVER *server, char *key, char *value) spinlock_release(&crt_lock); return valid; } + +/** + * @brief Convert a string value to a positive integer + * + * If the value is not a positive integer, an error is printed to @c dcb. + * + * @param value String value + * @return 0 on error, otherwise a positive integer + */ +static long get_positive_int(const char *value) +{ + char *endptr; + long ival = strtol(value, &endptr, 10); + + if (*endptr == '\0' && ival > 0) + { + return ival; + } + + return 0; +} + +bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value) +{ + spinlock_acquire(&crt_lock); + bool valid = false; + + if (strcmp(key, "user") == 0) + { + valid = true; + monitorAddUser(monitor, value, monitor->password); + } + else if (strcmp(key, "password") == 0) + { + valid = true; + monitorAddUser(monitor, monitor->user, value); + } + else if (strcmp(key, "monitor_interval") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetInterval(monitor, ival); + } + } + else if (strcmp(key, "backend_connect_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_write_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); + } + } + else if (strcmp(key, "backend_read_timeout") == 0) + { + long ival = get_positive_int(value); + if (ival) + { + valid = true; + monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); + } + } + + spinlock_release(&crt_lock); + return valid; +} diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 2f215733c..492ebc741 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1169,81 +1169,6 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3, } } -/** - * @brief Convert a string value to a positive integer - * - * If the value is not a positive integer, an error is printed to @c dcb. - * - * @param value String value - * @return 0 on error, otherwise a positive integer - */ -static long get_positive_int(const char *value) -{ - char *endptr; - long ival = strtol(value, &endptr, 10); - - if (*endptr == '\0' && ival > 0) - { - return ival; - } - - return 0; -} - -static bool handle_alter_monitor(MONITOR *monitor, char *key, char *value) -{ - bool valid = false; - - if (strcmp(key, "user") == 0) - { - valid = true; - monitorAddUser(monitor, value, monitor->password); - } - else if (strcmp(key, "password") == 0) - { - valid = true; - monitorAddUser(monitor, monitor->user, value); - } - else if (strcmp(key, "monitor_interval") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetInterval(monitor, ival); - } - } - else if (strcmp(key, "backend_connect_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival); - } - } - else if (strcmp(key, "backend_write_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival); - } - } - else if (strcmp(key, "backend_read_timeout") == 0) - { - long ival = get_positive_int(value); - if (ival) - { - valid = true; - monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival); - } - } - - return valid; -} - static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v3, char *v4, char *v5, char *v6, char *v7, char *v8, char *v9, char *v10, char *v11) @@ -1260,7 +1185,7 @@ static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v { *value++ = '\0'; - if (!handle_alter_monitor(monitor, key, value)) + if (!runtime_alter_monitor(monitor, key, value)) { dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value); } From ff54771cd1573793429d4f5970628b7b9bba7f21 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 08:48:09 +0200 Subject: [PATCH 25/48] Store old server SSL configurations If the SSL configuration of a server was altered successfully, it would overwrite an existing configuration leading to a true memory leak. Converting the SSL_LISTENER structure to a list allows it to store the old configurations without leaking the memory. This has no functional benefits apart from storing references which could aid in debugging. In the future, the discarded configurations could be freed once all connections that use it are closed. --- include/maxscale/config_runtime.h | 4 +++- include/maxscale/gw_ssl.h | 1 + server/core/config_runtime.c | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h index acb56bba8..cc29f775c 100644 --- a/include/maxscale/config_runtime.h +++ b/include/maxscale/config_runtime.h @@ -96,6 +96,8 @@ bool runtime_alter_server(SERVER *server, char *key, char *value); * The @c key , @c cert and @c ca parameters are required. @c version and @c depth * are optional. * + * @note SSL cannot be disabled at runtime. + * * @param server Server to configure * @param key Path to SSL private key * @param cert Path to SSL public certificate @@ -110,7 +112,7 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert /** * @brief Alter monitor parameters * - * @param monitor Monitor to aler + * @param monitor Monitor to alter * @param key Key to modify * @param value New value * @return True if @c key was one of the supported parameters diff --git a/include/maxscale/gw_ssl.h b/include/maxscale/gw_ssl.h index d15733e79..db6be67c1 100644 --- a/include/maxscale/gw_ssl.h +++ b/include/maxscale/gw_ssl.h @@ -71,6 +71,7 @@ typedef struct ssl_listener 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 */ + struct ssl_listener *next; /*< Next SSL configuration, currently used to store obsolete configurations */ } SSL_LISTENER; int ssl_authenticate_client(struct dcb *dcb, bool is_capable); diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 854be86be..5858a13b6 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -211,6 +211,12 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert if (err == 0 && ssl && listener_init_SSL(ssl) == 0) { + /** TODO: Properly discard old SSL configurations + * + * This could cause the loss of a pointer if two update + * operations are done at the same time.*/ + ssl->next = server->server_ssl; + /** Sync to prevent reads on partially initialized server_ssl */ atomic_synchronize(); From b24a28285c84d697f3eaa92ddd31db4d668af210 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 09:42:26 +0200 Subject: [PATCH 26/48] Make listener creation const-correct The parameters passed to functions that create new listeners are now of type const char*. --- include/maxscale/listener.h | 6 ++--- include/maxscale/service.h | 7 +++--- server/core/listener.c | 50 +++++++++++++++++++++---------------- server/core/service.c | 12 ++++----- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/include/maxscale/listener.h b/include/maxscale/listener.h index 0df0f5d8d..a5530eb85 100644 --- a/include/maxscale/listener.h +++ b/include/maxscale/listener.h @@ -59,9 +59,9 @@ typedef struct servlistener struct servlistener *next; /**< Next service protocol */ } SERV_LISTENER; -SERV_LISTENER *listener_alloc(struct service* service, char *name, char *protocol, - char *address, unsigned short port, char *authenticator, - char* options, SSL_LISTENER *ssl); +SERV_LISTENER* listener_alloc(struct service* service, const char* name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char* auth_options, SSL_LISTENER *ssl); void listener_free(SERV_LISTENER* listener); int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version); void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 8e2fa8ab9..e4c23a67a 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -192,10 +192,9 @@ extern SERVICE *service_alloc(const char *, const char *); extern int service_free(SERVICE *); extern SERVICE *service_find(const char *); extern int service_isvalid(SERVICE *); -extern int serviceAddProtocol(SERVICE *service, char *name, char *protocol, - char *address, unsigned short port, - char *authenticator, char *options, - SSL_LISTENER *ssl); +extern bool serviceAddProtocol(SERVICE *service, const char *name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl); extern int serviceHasProtocol(SERVICE *service, const char *protocol, const char* address, unsigned short port); extern void serviceAddBackend(SERVICE *, SERVER *); diff --git a/server/core/listener.c b/server/core/listener.c index 075ae8eff..a669d2f30 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -55,61 +55,67 @@ static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength); * @return New listener object or NULL if unable to allocate */ SERV_LISTENER * -listener_alloc(struct service* service, char* name, char *protocol, char *address, - unsigned short port, char *authenticator, char* auth_options, SSL_LISTENER *ssl) +listener_alloc(struct service* service, const char* name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char* auth_options, SSL_LISTENER *ssl) { + char *my_address = NULL; if (address) { - address = MXS_STRDUP(address); - if (!address) + my_address = MXS_STRDUP(address); + if (!my_address) { return NULL; } } + char *my_authenticator = NULL; + if (authenticator) { - authenticator = MXS_STRDUP(authenticator); + my_authenticator = MXS_STRDUP(authenticator); } - else if ((authenticator = (char*)get_default_authenticator(protocol)) == NULL || - (authenticator = MXS_STRDUP(authenticator)) == NULL) + else if ((authenticator = get_default_authenticator(protocol)) == NULL || + (my_authenticator = MXS_STRDUP(authenticator)) == NULL) { MXS_ERROR("No authenticator defined for listener '%s' and could not get " "default authenticator for protocol '%s'.", name, protocol); + MXS_FREE(my_address); + return NULL; } void *auth_instance = NULL; - if (!authenticator_init(&auth_instance, authenticator, auth_options)) + if (!authenticator_init(&auth_instance, my_authenticator, auth_options)) { MXS_ERROR("Failed to initialize authenticator module '%s' for " - "listener '%s'.", authenticator, name); - MXS_FREE(address); - MXS_FREE(authenticator); + "listener '%s'.", my_authenticator, name); + MXS_FREE(my_address); + MXS_FREE(my_authenticator); return NULL; } - protocol = MXS_STRDUP(protocol); - name = MXS_STRDUP(name); + char *my_protocol = MXS_STRDUP(protocol); + char *my_name = MXS_STRDUP(name); SERV_LISTENER *proto = (SERV_LISTENER*)MXS_MALLOC(sizeof(SERV_LISTENER)); - if (!protocol || !proto || !name || !authenticator) + if (!my_protocol || !proto || !my_name || !my_authenticator) { - MXS_FREE(authenticator); - MXS_FREE(protocol); - MXS_FREE(address); + MXS_FREE(my_authenticator); + MXS_FREE(my_protocol); + MXS_FREE(my_address); + MXS_FREE(my_name); MXS_FREE(proto); - MXS_FREE(name); return NULL; } - proto->name = name; + proto->name = my_name; proto->listener = NULL; proto->service = service; - proto->protocol = protocol; - proto->address = address; + proto->protocol = my_protocol; + proto->address = my_address; proto->port = port; - proto->authenticator = authenticator; + proto->authenticator = my_authenticator; proto->ssl = ssl; proto->users = NULL; proto->resources = NULL; diff --git a/server/core/service.c b/server/core/service.c index 707360e06..a9a673f8f 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -688,11 +688,11 @@ service_free(SERVICE *service) * @param ssl SSL configuration * @return TRUE if the protocol/port could be added */ -int -serviceAddProtocol(SERVICE *service, char *name, char *protocol, char *address, - unsigned short port, char *authenticator, char *options, - SSL_LISTENER *ssl) +bool serviceAddProtocol(SERVICE *service, const char *name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl) { + bool rval = false; SERV_LISTENER *proto = listener_alloc(service, name, protocol, address, port, authenticator, options, ssl); @@ -702,10 +702,10 @@ serviceAddProtocol(SERVICE *service, char *name, char *protocol, char *address, proto->next = service->ports; service->ports = proto; spinlock_release(&service->spin); - return 1; + rval = true; } - return 0; + return rval; } /** From e8af6908c1a4a3f6ef40b54a9e9adf1a756b5a12 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 10:00:15 +0200 Subject: [PATCH 27/48] Allow creation of listener at runtime Listeners can now be created and started at runtime. If SSL is to be used, the required parameters must be present. --- include/maxscale/config_runtime.h | 26 ++++++ include/maxscale/service.h | 2 +- server/core/config.c | 14 +--- server/core/config_runtime.c | 133 ++++++++++++++++++++++++------ server/core/service.c | 22 ++--- 5 files changed, 151 insertions(+), 46 deletions(-) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h index cc29f775c..8384ebbb9 100644 --- a/include/maxscale/config_runtime.h +++ b/include/maxscale/config_runtime.h @@ -118,3 +118,29 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert * @return True if @c key was one of the supported parameters */ bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value); + +/** + * @brief Create a new listener for a service + * + * This function adds a new listener to a service and starts it. + * + * @param service Service where the listener is added + * @param name Name of the listener + * @param addr Listening address, NULL for default of 0.0.0.0 + * @param port Listening port, NULL for default of 3306 + * @param proto Listener protocol, NULL for default of "MySQLClient" + * @param auth Listener authenticator, NULL for protocol default authenticator + * @param auth_opt Options for the authenticator, NULL for no options + * @param ssl_key SSL key, NULL for no key + * @param ssl_cert SSL cert, NULL for no cert + * @param ssl_ca SSL CA cert, NULL for no CA cert + * @param ssl_version SSL version, NULL for default of "MAX" + * @param ssl_depth SSL cert verification depth, NULL for default + * + * @return True if the listener was successfully created and started + */ +bool runtime_create_listener(SERVICE *service, const char *name, const char *addr, + const char *port, const char *proto, const char *auth, + const char *auth_opt, const char *ssl_key, + const char *ssl_cert, const char *ssl_ca, + const char *ssl_version, const char *ssl_depth); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index e4c23a67a..73c282142 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -204,7 +204,7 @@ extern void serviceAddRouterOption(SERVICE *, char *); extern void serviceClearRouterOptions(SERVICE *); extern int serviceStart(SERVICE *); extern int serviceStartAll(); -extern void serviceStartProtocol(SERVICE *, char *, int); +extern bool serviceListen(SERVICE *service, unsigned short port); extern int serviceStop(SERVICE *); extern int serviceRestart(SERVICE *); extern int serviceSetUser(SERVICE *, char *, char *); diff --git a/server/core/config.c b/server/core/config.c index 3e62de294..65d22e9af 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -98,7 +98,7 @@ bool config_has_duplicate_sections(const char* config, DUPLICATE_CONTEXT* contex int create_new_service(CONFIG_CONTEXT *obj); int create_new_server(CONFIG_CONTEXT *obj); int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash); -int create_new_listener(CONFIG_CONTEXT *obj, bool startnow); +int create_new_listener(CONFIG_CONTEXT *obj); int create_new_filter(CONFIG_CONTEXT *obj); int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj); @@ -815,7 +815,7 @@ process_config_context(CONFIG_CONTEXT *context) } else if (!strcmp(type, "listener")) { - error_count += create_new_listener(obj, false); + error_count += create_new_listener(obj); } else if (!strcmp(type, "monitor")) { @@ -3094,7 +3094,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* * @param startnow If true, start the listener now * @return Number of errors */ -int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) +int create_new_listener(CONFIG_CONTEXT *obj) { int error_count = 0; char *service_name = config_get_value(obj->parameters, "service"); @@ -3123,10 +3123,6 @@ int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) { serviceAddProtocol(service, obj->object, protocol, socket, 0, authenticator, authenticator_options, ssl_info); - if (startnow) - { - serviceStartProtocol(service, protocol, 0); - } } } @@ -3144,10 +3140,6 @@ int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) { serviceAddProtocol(service, obj->object, protocol, address, atoi(port), authenticator, authenticator_options, ssl_info); - if (startnow) - { - serviceStartProtocol(service, protocol, atoi(port)); - } } } diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 5858a13b6..84740391a 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -11,6 +11,7 @@ * Public License. */ +#include #include #include #include @@ -190,17 +191,16 @@ bool runtime_destroy_server(SERVER *server) return rval; } -bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, - const char *ca, const char *version, const char *depth) +static SSL_LISTENER* create_ssl(const char *name, const char *key, const char *cert, + const char *ca, const char *version, const char *depth) { - spinlock_acquire(&crt_lock); - bool rval = false; + SSL_LISTENER *rval = NULL; + CONFIG_CONTEXT *obj = config_context_create(name); - if (key && cert && ca) + if (obj) { - CONFIG_CONTEXT *obj = config_context_create(server->unique_name); - - if (obj && config_add_param(obj, "ssl_key", key) && + if (config_add_param(obj, "ssl", "required") && + config_add_param(obj, "ssl_key", key) && config_add_param(obj, "ssl_cert", cert) && config_add_param(obj, "ssl_ca_cert", ca) && (!version || config_add_param(obj, "ssl_version", version)) && @@ -211,27 +211,44 @@ bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert if (err == 0 && ssl && listener_init_SSL(ssl) == 0) { - /** TODO: Properly discard old SSL configurations - * - * This could cause the loss of a pointer if two update - * operations are done at the same time.*/ - ssl->next = server->server_ssl; - - /** Sync to prevent reads on partially initialized server_ssl */ - atomic_synchronize(); - - server->server_ssl = ssl; - if (server_serialize(server)) - { - rval = true; - } + rval = ssl; } } config_context_free(obj); } - spinlock_release(&crt_lock); + return rval; +} + +bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert, + const char *ca, const char *version, const char *depth) +{ + bool rval = false; + + if (key && cert && ca) + { + spinlock_acquire(&crt_lock); + SSL_LISTENER *ssl = create_ssl(server->unique_name, key, cert, ca, version, depth); + + if (ssl) + { + /** TODO: Properly discard old SSL configurations.This could cause the + * loss of a pointer if two update operations are done at the same time.*/ + ssl->next = server->server_ssl; + + /** Sync to prevent reads on partially initialized server_ssl */ + atomic_synchronize(); + server->server_ssl = ssl; + + if (server_serialize(server)) + { + rval = true; + } + } + spinlock_release(&crt_lock); + } + return rval; } @@ -341,3 +358,73 @@ bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value) spinlock_release(&crt_lock); return valid; } + +bool runtime_create_listener(SERVICE *service, const char *name, const char *addr, + const char *port, const char *proto, const char *auth, + const char *auth_opt, const char *ssl_key, + const char *ssl_cert, const char *ssl_ca, + const char *ssl_version, const char *ssl_depth) +{ + SSL_LISTENER *ssl = NULL; + bool rval = true; + + if (addr == NULL || strcasecmp(addr, "default") == 0) + { + addr = "0.0.0.0"; + } + if (port == NULL || strcasecmp(port, "default") == 0) + { + port = "3306"; + } + if (proto == NULL || strcasecmp(proto, "default") == 0) + { + proto = "MySQLClient"; + } + + if (auth && strcasecmp(auth, "default") == 0) + { + /** Set auth to NULL so the protocol default authenticator is used */ + auth = NULL; + } + + if (auth_opt && strcasecmp(auth_opt, "default") == 0) + { + /** Don't pass options to the authenticator */ + auth_opt = NULL; + } + + unsigned short u_port = atoi(port); + + if (ssl_key && ssl_cert && ssl_ca) + { + ssl = create_ssl(name, ssl_key, ssl_cert, ssl_ca, ssl_version, ssl_depth); + + if (ssl == NULL) + { + MXS_ERROR("SSL initialization for listener '%s' failed.", name); + rval = false; + } + } + + spinlock_acquire(&crt_lock); + + if (rval) + { + const char *print_addr = addr ? addr : "0.0.0.0"; + + if (serviceAddProtocol(service, name, proto, addr, u_port, auth, auth_opt, ssl) && + serviceListen(service, u_port)) + { + MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created", + name, print_addr, port, service->name); + } + else + { + MXS_ERROR("Failed to start listener '%s' at %s:%s.", name, print_addr, port); + rval = false; + } + } + + spinlock_release(&crt_lock); + return rval; +} diff --git a/server/core/service.c b/server/core/service.c index a9a673f8f..2c04f8e54 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -506,25 +506,25 @@ serviceStart(SERVICE *service) * Start an individual listener * * @param service The service to start the listener for - * @param protocol The name of the protocol * @param port The port number */ -void -serviceStartProtocol(SERVICE *service, char *protocol, int port) +bool serviceListen(SERVICE *service, unsigned short port) { - SERV_LISTENER *ptr; - - ptr = service->ports; - while (ptr) + bool rval = false; + for (SERV_LISTENER *ptr = service->ports; ptr; ptr = ptr->next) { - if (strcmp(ptr->protocol, protocol) == 0 && ptr->port == port) + if (ptr->port == port) { - serviceStartPort(service, ptr); + if (serviceStartPort(service, ptr)) + { + rval = true; + } + break; } - ptr = ptr->next; } -} + return rval; +} /** * Start all the services From 67c443fb51de692db9ba9420dba44645978926ce Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 17:32:39 +0200 Subject: [PATCH 28/48] Fix maxadmin argument processing The arguments were limited to a hard-coded value which wasn't what the MAXARGS define stated. --- server/modules/routing/debugcli/debugcmd.c | 25 ++++++---------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 492ebc741..f6693c8c1 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1447,7 +1447,7 @@ execute_cmd(CLI_SESSION *cli) args[0] = cli->cmdbuf; ptr = args[0]; lptr = ptr; - i = 0; + i = 1; /* * Break the command line into a number of words. Whitespace is used * to delimit words and may be escaped by use of the \ character or @@ -1455,7 +1455,7 @@ execute_cmd(CLI_SESSION *cli) * The array args contains the broken down words, one per index. */ - while (*ptr) + while (*ptr && i <= MAXARGS + 2) { if (escape_next) { @@ -1479,19 +1479,7 @@ execute_cmd(CLI_SESSION *cli) break; } - if (args[i] == ptr) - { - args[i] = ptr + 1; - } - else - { - i++; - if (i >= MAXARGS - 1) - { - break; - } - args[i] = ptr + 1; - } + args[i++] = ptr + 1; ptr++; lptr++; } @@ -1513,14 +1501,13 @@ execute_cmd(CLI_SESSION *cli) } } *lptr = 0; - args[MXS_MIN(MAXARGS - 1, i + 1)] = NULL; + args[i] = NULL; if (args[0] == NULL || *args[0] == 0) { return 1; } - for (i = 0; args[i] && *args[i]; i++) - ; + argc = i - 2; /* The number of extra arguments to commands */ if (!strcasecmp(args[0], "help")) @@ -1561,7 +1548,7 @@ execute_cmd(CLI_SESSION *cli) dcb_printf(dcb, "Available options to the %s command:\n", args[1]); for (j = 0; cmds[i].options[j].arg1; j++) { - dcb_printf(dcb, "'%s' - %s\n\n\t%s\n\n", cmds[i].options[j].arg1, + dcb_printf(dcb, "'%s' - %s\n\n%s\n\n", cmds[i].options[j].arg1, cmds[i].options[j].help, cmds[i].options[j].devhelp); } From 4730e28ef7ab5a5f48daef9ec9331e02c856c816 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 23 Nov 2016 17:33:52 +0200 Subject: [PATCH 29/48] Add creation of listeners to maxadmin Maxadmin now supports the runtime creation of listeners. The new 'default' value can be used to signal values that don't need to be configured and the default value should be used. --- server/modules/routing/debugcli/debugcmd.c | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index f6693c8c1..a63b81603 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1025,6 +1025,23 @@ static void createServer(DCB *dcb, char *name, char *address, char *port, spinlock_release(&server_mod_lock); } +static void createListener(DCB *dcb, SERVICE *service, char *name, char *address, + char *port, char *protocol, char *authenticator, + char *authenticator_options, char *key, char *cert, + char *ca, char *version, char *depth) +{ + if (runtime_create_listener(service, name, address, port, protocol, + authenticator, authenticator_options, + key, cert, ca, version, depth)) + { + dcb_printf(dcb, "Listener '%s' created\n", name); + } + else + { + dcb_printf(dcb, "Failed to create listener '%s', see log for more details\n", name); + } +} + struct subcommand createoptions[] = { { @@ -1044,6 +1061,33 @@ struct subcommand createoptions[] = ARG_TYPE_STRING, ARG_TYPE_STRING } }, + { + "listener", 2, 12, createListener, + "Create a new listener for a service", + "Usage: create listener SERVICE NAME [HOST] [PORT] [PROTOCOL] [AUTHENTICATOR] [OPTIONS]\n" + " [SSL_KEY] [SSL_CERT] [SSL_CA] [SSL_VERSION] [SSL_VERIFY_DEPTH]\n\n" + "Create a new server from the following parameters.\n" + "SERVICE Service where this listener is added\n" + "NAME Listener name\n" + "HOST Listener host address (default 0.0.0.0)\n" + "PORT Listener port (default 3306)\n" + "PROTOCOL Listener protocol (default MySQLClient)\n" + "AUTHENTICATOR Authenticator module name (default MySQLAuth)\n" + "OPTIONS Options for the authenticator module\n" + "SSL_KEY Path to SSL private key\n" + "SSL_CERT Path to SSL certificate\n" + "SSL_CA Path to CA certificate\n" + "SSL_VERSION SSL version (default MAX)\n" + "SSL_VERIFY_DEPTH Certificate verification depth\n\n" + "The first two parameters are required, the others are optional.\n" + "Any of the optional parameters can also have the value 'default'\n" + "which will be replaced with the default value.\n", + { + ARG_TYPE_SERVICE, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, + } + }, { EMPTY_OPTION } From 04753bbb76faf360d43ce00701ab46e570211b8f Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 15:45:43 +0200 Subject: [PATCH 30/48] Make MODULE_INFO const correct --- include/maxscale/modinfo.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/maxscale/modinfo.h b/include/maxscale/modinfo.h index 09e413bd0..151f460b3 100644 --- a/include/maxscale/modinfo.h +++ b/include/maxscale/modinfo.h @@ -81,10 +81,10 @@ typedef struct */ typedef struct { - MODULE_API modapi; + MODULE_API modapi; MODULE_STATUS status; MODULE_VERSION api_version; - char *description; + const char *description; } MODULE_INFO; MXS_END_DECLS From 265aacaf15daa8f98987b8c7bd2f49ba853394da Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 13:31:29 +0200 Subject: [PATCH 31/48] GWBUF_DATA(...) explicitly returns uint8_t* --- include/maxscale/buffer.h | 2 +- server/core/buffer.c | 6 +++--- server/core/dcb.c | 4 ++-- server/core/test/testbuffer.c | 4 ++-- .../filter/cache/storage/storage_rocksdb/rocksdbstorage.cc | 2 +- server/modules/filter/hintfilter/hintparser.c | 4 ++-- server/modules/protocol/CDC/cdc.c | 2 +- server/modules/protocol/maxscaled/maxscaled.c | 6 +++--- server/modules/protocol/telnetd/telnetd.c | 4 ++-- server/modules/routing/avro/avro_client.c | 2 +- server/modules/routing/binlog/blr.c | 4 ++-- server/modules/routing/binlog/blr_slave.c | 2 +- server/modules/routing/cli/cli.c | 2 +- server/modules/routing/debugcli/debugcli.c | 2 +- server/modules/routing/maxinfo/maxinfo.c | 5 +++-- .../routing/readwritesplit/rwsplit_tmp_table_multi.c | 2 +- 16 files changed, 27 insertions(+), 26 deletions(-) diff --git a/include/maxscale/buffer.h b/include/maxscale/buffer.h index 3e9995faf..d303ce5d9 100644 --- a/include/maxscale/buffer.h +++ b/include/maxscale/buffer.h @@ -151,7 +151,7 @@ typedef struct gwbuf * Macros to access the data in the buffers */ /*< First valid, unconsumed byte in the buffer */ -#define GWBUF_DATA(b) ((b)->start) +#define GWBUF_DATA(b) ((uint8_t*)(b)->start) /*< Number of bytes in the individual buffer */ #define GWBUF_LENGTH(b) ((char *)(b)->end - (char *)(b)->start) diff --git a/server/core/buffer.c b/server/core/buffer.c index e91e79b58..ff753c1f7 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -854,9 +854,9 @@ gwbuf_get_property(GWBUF *buf, char *name) GWBUF * gwbuf_make_contiguous(GWBUF *orig) { - GWBUF *newbuf; - char *ptr; - int len; + GWBUF *newbuf; + uint8_t *ptr; + int len; if (orig == NULL) { diff --git a/server/core/dcb.c b/server/core/dcb.c index 35ceee80d..cc76a3a89 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2286,10 +2286,10 @@ dcb_printf(DCB *dcb, const char *fmt, ...) return; } va_start(args, fmt); - vsnprintf(GWBUF_DATA(buf), 10240, fmt, args); + vsnprintf((char*)GWBUF_DATA(buf), 10240, fmt, args); va_end(args); - buf->end = (void *)((char *)GWBUF_DATA(buf) + strlen(GWBUF_DATA(buf))); + buf->end = (void *)((char *)GWBUF_DATA(buf) + strlen((char*)GWBUF_DATA(buf))); dcb->func.write(dcb, buf); } diff --git a/server/core/test/testbuffer.c b/server/core/test/testbuffer.c index 119bd464d..ac645f1cd 100644 --- a/server/core/test/testbuffer.c +++ b/server/core/test/testbuffer.c @@ -349,12 +349,12 @@ test1() ss_dfprintf(stderr, "\t..done\nSet a property for the buffer"); gwbuf_add_property(buffer, "name", "value"); ss_info_dassert(0 == strcmp("value", gwbuf_get_property(buffer, "name")), "Should now have correct property"); - strcpy(GWBUF_DATA(buffer), "The quick brown fox jumps over the lazy dog"); + strcpy((char*)GWBUF_DATA(buffer), "The quick brown fox jumps over the lazy dog"); ss_dfprintf(stderr, "\t..done\nLoad some data into the buffer"); ss_info_dassert('q' == GWBUF_DATA_CHAR(buffer, 4), "Fourth character of buffer must be 'q'"); ss_info_dassert(-1 == GWBUF_DATA_CHAR(buffer, 105), "Hundred and fifth character of buffer must return -1"); ss_info_dassert(0 == GWBUF_IS_SQL(buffer), "Must say buffer is not SQL, as it does not have marker"); - strcpy(GWBUF_DATA(buffer), "1234\x03SELECT * FROM sometable"); + strcpy((char*)GWBUF_DATA(buffer), "1234\x03SELECT * FROM sometable"); ss_dfprintf(stderr, "\t..done\nLoad SQL data into the buffer"); ss_info_dassert(1 == GWBUF_IS_SQL(buffer), "Must say buffer is SQL, as it does have marker"); transform = gwbuf_clone_transform(buffer, GWBUF_TYPE_PLAINSQL); diff --git a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc index 2de463818..d09c45d1d 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/rocksdbstorage.cc @@ -470,7 +470,7 @@ cache_result_t RocksDBStorage::putValue(const char* pKey, const GWBUF* pValue) ss_dassert(GWBUF_IS_CONTIGUOUS(pValue)); rocksdb::Slice key(pKey, ROCKSDB_KEY_LENGTH); - rocksdb::Slice value(static_cast(GWBUF_DATA(pValue)), GWBUF_LENGTH(pValue)); + rocksdb::Slice value((char*)GWBUF_DATA(pValue), GWBUF_LENGTH(pValue)); rocksdb::Status status = m_sDb->Put(writeOptions(), key, value); diff --git a/server/modules/filter/hintfilter/hintparser.c b/server/modules/filter/hintfilter/hintparser.c index 1f3dbe79f..0db3498fd 100644 --- a/server/modules/filter/hintfilter/hintparser.c +++ b/server/modules/filter/hintfilter/hintparser.c @@ -220,7 +220,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) if (buf) { len = GWBUF_LENGTH(buf); - ptr = GWBUF_DATA(buf); + ptr = (char*)GWBUF_DATA(buf); } } while (buf); @@ -243,7 +243,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) buf = buf->next; if (buf) { - ptr = GWBUF_DATA(buf); + ptr = (char*)GWBUF_DATA(buf); } else { diff --git a/server/modules/protocol/CDC/cdc.c b/server/modules/protocol/CDC/cdc.c index e4612780e..05031ed84 100644 --- a/server/modules/protocol/CDC/cdc.c +++ b/server/modules/protocol/CDC/cdc.c @@ -191,7 +191,7 @@ cdc_read_event(DCB* dcb) case CDC_STATE_HANDLE_REQUEST: // handle CLOSE command, it shoudl be routed as well and client connection closed after last transmission - if (strncmp(GWBUF_DATA(head), "CLOSE", GWBUF_LENGTH(head)) == 0) + if (strncmp((char*)GWBUF_DATA(head), "CLOSE", GWBUF_LENGTH(head)) == 0) { MXS_INFO("%s: Client [%s] has requested CLOSE action", dcb->service->name, dcb->remote != NULL ? dcb->remote : ""); diff --git a/server/modules/protocol/maxscaled/maxscaled.c b/server/modules/protocol/maxscaled/maxscaled.c index d3a7a7eeb..60d809e81 100644 --- a/server/modules/protocol/maxscaled/maxscaled.c +++ b/server/modules/protocol/maxscaled/maxscaled.c @@ -102,7 +102,7 @@ static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb) username = gwbuf_alloc(strlen(protocol->username) + 1); - strcpy(GWBUF_DATA(username), protocol->username); + strcpy((char*)GWBUF_DATA(username), protocol->username); /* Authenticate the user */ if (dcb->authfunc.extract(dcb, username) == 0 && @@ -261,7 +261,7 @@ static int maxscaled_read_event(DCB* dcb) { case MAXSCALED_STATE_LOGIN: { - maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + maxscaled->username = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); maxscaled->state = MAXSCALED_STATE_PASSWD; dcb_printf(dcb, MAXADMIN_AUTH_PASSWORD_PROMPT); gwbuf_free(head); @@ -270,7 +270,7 @@ static int maxscaled_read_event(DCB* dcb) case MAXSCALED_STATE_PASSWD: { - char *password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + char *password = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); if (admin_verify_inet_user(maxscaled->username, password)) { dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY); diff --git a/server/modules/protocol/telnetd/telnetd.c b/server/modules/protocol/telnetd/telnetd.c index df802bf8e..a855d12d8 100644 --- a/server/modules/protocol/telnetd/telnetd.c +++ b/server/modules/protocol/telnetd/telnetd.c @@ -181,7 +181,7 @@ static int telnetd_read_event(DCB* dcb) switch (telnetd->state) { case TELNETD_STATE_LOGIN: - telnetd->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + telnetd->username = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); /* Strip the cr/lf from the username */ t = strstr(telnetd->username, "\r\n"); if (t) @@ -194,7 +194,7 @@ static int telnetd_read_event(DCB* dcb) gwbuf_consume(head, GWBUF_LENGTH(head)); break; case TELNETD_STATE_PASSWD: - password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + password = strndup((char*)GWBUF_DATA(head), GWBUF_LENGTH(head)); /* Strip the cr/lf from the username */ t = strstr(password, "\r\n"); if (t) diff --git a/server/modules/routing/avro/avro_client.c b/server/modules/routing/avro/avro_client.c index 1caa06221..fc1341e0a 100644 --- a/server/modules/routing/avro/avro_client.c +++ b/server/modules/routing/avro/avro_client.c @@ -130,7 +130,7 @@ avro_client_do_registration(AVRO_INSTANCE *router, AVRO_CLIENT *client, GWBUF *d const char reg_uuid[] = "REGISTER UUID="; const int reg_uuid_len = strlen(reg_uuid); int data_len = GWBUF_LENGTH(data) - reg_uuid_len; - char *request = GWBUF_DATA(data); + char *request = (char*)GWBUF_DATA(data); int ret = 0; if (strstr(request, reg_uuid) != NULL) diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 8f131f0ad..25b4db681 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -1822,7 +1822,7 @@ int blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { char result[BLRM_COM_STATISTICS_SIZE + 1] = ""; - char *ptr; + uint8_t *ptr; GWBUF *ret; unsigned long len; @@ -1858,7 +1858,7 @@ blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) int blr_ping(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { - char *ptr; + uint8_t *ptr; GWBUF *ret; if ((ret = gwbuf_alloc(5)) == NULL) diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index 7a3219ab6..96a7ba1f7 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -345,7 +345,7 @@ blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) char *ptr; extern char *strcasestr(); - qtext = GWBUF_DATA(queue); + qtext = (char*)GWBUF_DATA(queue); query_len = extract_field((uint8_t *)qtext, 24) - 1; qtext += 5; // Skip header and first byte of the payload query_text = strndup(qtext, query_len); diff --git a/server/modules/routing/cli/cli.c b/server/modules/routing/cli/cli.c index 478a2dcdf..61e6a21f8 100644 --- a/server/modules/routing/cli/cli.c +++ b/server/modules/routing/cli/cli.c @@ -270,7 +270,7 @@ execute(ROUTER *instance, void *router_session, GWBUF *queue) /* Extract the characters */ while (queue && (cmdlen < CMDBUFLEN - 1)) { - const char* data = GWBUF_DATA(queue); + const char* data = (char*)GWBUF_DATA(queue); int len = GWBUF_LENGTH(queue); int n = MXS_MIN(len, CMDBUFLEN - cmdlen - 1); diff --git a/server/modules/routing/debugcli/debugcli.c b/server/modules/routing/debugcli/debugcli.c index 1f86c82fc..6660c00bc 100644 --- a/server/modules/routing/debugcli/debugcli.c +++ b/server/modules/routing/debugcli/debugcli.c @@ -291,7 +291,7 @@ execute(ROUTER *instance, void *router_session, GWBUF *queue) /* Extract the characters */ while (queue && (cmdlen < CMDBUFLEN - 1)) { - const char* data = GWBUF_DATA(queue); + const char* data = (char*)GWBUF_DATA(queue); int len = GWBUF_LENGTH(queue); int n = MXS_MIN(len, CMDBUFLEN - cmdlen - 1); diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 05eaa8d4d..3115899d8 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -422,7 +422,8 @@ getCapabilities(void) static int maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) { - char result[1000], *ptr; + char result[1000]; + uint8_t *ptr; GWBUF *ret; int len; @@ -456,7 +457,7 @@ maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) static int maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) { - char *ptr; + uint8_t *ptr; GWBUF *ret; int len; diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c index b6b43cb23..8eaa689ea 100644 --- a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c +++ b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c @@ -327,7 +327,7 @@ bool check_for_multi_stmt(GWBUF *buf, void *protocol, mysql_server_cmd_t packet_ if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS && packet_type == MYSQL_COM_QUERY) { - char *ptr, *data = GWBUF_DATA(buf) + 5; + char *ptr, *data = (char*)GWBUF_DATA(buf) + 5; /** Payload size without command byte */ int buflen = gw_mysql_get_byte3((uint8_t *)GWBUF_DATA(buf)) - 1; From dd63253261cefdabac669352b7e9dd456671c934 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 24 Nov 2016 14:00:11 +0200 Subject: [PATCH 32/48] Fix internal test suite failures The server test used the wrong name. MySQL users test loaded multiple modules in one function call and wasn't appropriate for an internal test suite test as it requires a working installation. The cache filter didn't set the library paths before trying to load modules. The binlogrouter was missing a NULL check which caused a crash. --- server/core/test/testserver.c | 2 +- .../authenticator/MySQLAuth/CMakeLists.txt | 6 - .../MySQLAuth/test_mysql_users.c | 546 ------------------ server/modules/filter/cache/test/testrules.c | 4 + server/modules/routing/binlog/blr_slave.c | 37 +- 5 files changed, 20 insertions(+), 575 deletions(-) delete mode 100644 server/modules/authenticator/MySQLAuth/test_mysql_users.c diff --git a/server/core/test/testserver.c b/server/core/test/testserver.c index 67991f864..481281834 100644 --- a/server/core/test/testserver.c +++ b/server/core/test/testserver.c @@ -70,7 +70,7 @@ test1() ss_info_dassert(0 == strcmp("value", serverGetParameter(server, "name")), "Parameter should be returned correctly"); ss_dfprintf(stderr, "\t..done\nTesting Unique Name for Server."); - ss_info_dassert(NULL == server_find_by_unique_name("uniquename"), + ss_info_dassert(NULL == server_find_by_unique_name("non-existent"), "Should not find non-existent unique name."); mxs_log_flush_sync(); ss_info_dassert(server == server_find_by_unique_name("uniquename"), "Should find by unique name."); diff --git a/server/modules/authenticator/MySQLAuth/CMakeLists.txt b/server/modules/authenticator/MySQLAuth/CMakeLists.txt index b27f39766..9bbcd952a 100644 --- a/server/modules/authenticator/MySQLAuth/CMakeLists.txt +++ b/server/modules/authenticator/MySQLAuth/CMakeLists.txt @@ -2,9 +2,3 @@ add_library(MySQLAuth SHARED mysql_auth.c dbusers.c) target_link_libraries(MySQLAuth maxscale-common MySQLCommon) set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0") install_module(MySQLAuth core) - -if (BUILD_TESTS) - add_executable(test_mysql_users test_mysql_users.c) - target_link_libraries(test_mysql_users MySQLAuth MySQLCommon maxscale-common) - add_test(TestMySQLUsers test_mysql_users) -endif() diff --git a/server/modules/authenticator/MySQLAuth/test_mysql_users.c b/server/modules/authenticator/MySQLAuth/test_mysql_users.c deleted file mode 100644 index 1a30b5e03..000000000 --- a/server/modules/authenticator/MySQLAuth/test_mysql_users.c +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright (c) 2016 MariaDB Corporation Ab - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file and at www.mariadb.com/bsl. - * - * Change Date: 2019-07-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2 or later of the General - * Public License. - */ - -/** - * - * @verbatim - * Revision History - * - * Date Who Description - * 14/02/2014 Massimiliano Pinto Initial implementation - * 17/02/2014 Massimiliano Pinto Added check ipv4 - * 03/10/2014 Massimiliano Pinto Added check for wildcard hosts - * - * @endverbatim - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include "dbusers.h" -#include -#include -#include -#include -#include - -extern int setipaddress(); - -int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char *password) -{ - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - MYSQL_USER_HOST find_key; - USERS *mysql_users; - char ret_ip[200] = ""; - char *fetch_data; - char *db = ""; - DCB *dcb; - SERVICE *service; - SERV_LISTENER dummy; - unsigned long fix_ipv4; - - dcb = dcb_alloc(DCB_ROLE_INTERNAL, &dummy); - - if (dcb == NULL) - { - fprintf(stderr, "dcb_alloc() failed\n"); - return 1; - } - if ((service = (SERVICE *)MXS_CALLOC(1, sizeof(SERVICE))) == NULL) - { - dcb_close(dcb); - return 1; - } - - if (ipv4 > UINT_MAX) - { - fix_ipv4 = UINT_MAX; - } - else - { - fix_ipv4 = ipv4; - } - - mysql_users = mysql_users_alloc(); - /* prepare the user@host data struct */ - memset(&key, 0, sizeof(key)); - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - memcpy(&(serv_addr).sin_addr.s_addr, &fix_ipv4, sizeof(ipv4)); - - key.user = username; - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); - - fprintf(stderr, "IPv4 passed/fixed [%lu/%lu] is [%s]\n", ipv4, fix_ipv4, ret_ip); - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (!mysql_users_add(mysql_users, &key, password)) - { - fprintf(stderr, "Failed adding %s@%s(%lu)\n", username, ret_ip, fix_ipv4); - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - return 1; - } - - memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&find_key, 0, sizeof(find_key)); - - find_key.user = username; - memcpy(&(serv_addr).sin_addr.s_addr, &ipv4, sizeof(ipv4)); - find_key.resource = db; - - memcpy(&find_key.ipv4, &serv_addr, sizeof(serv_addr)); - - fetch_data = mysql_users_fetch(mysql_users, &find_key); - - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - - if (!fetch_data) - { - return 1; - } - - return 0; -} - -int set_and_get_single_mysql_users(char *username, char *hostname, char *password) -{ - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - USERS *mysql_users; - char ret_ip[200] = ""; - char *fetch_data; - char *db = ""; - - mysql_users = mysql_users_alloc(); - - /* prepare the user@host data struct */ - memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&key, 0, sizeof(key)); - - - if (hostname) - if (!setipaddress(&serv_addr.sin_addr, hostname)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", hostname); - users_free(mysql_users); - return 1; - } - if (username) - { - key.user = username; - } - - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); - - fprintf(stderr, "set/get [%s@%s]: IPV4 %lu is [%u].[%u].[%u].[%u]\n", username, hostname, - (unsigned long) serv_addr.sin_addr.s_addr, serv_addr.sin_addr.s_addr & 0xFF, - (serv_addr.sin_addr.s_addr & 0xFF00), (serv_addr.sin_addr.s_addr & 0xFF0000), - ((serv_addr.sin_addr.s_addr & 0xFF000000) / (256 * 256 * 256))); - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (!mysql_users_add(mysql_users, &key, password)) - { - fprintf(stderr, "mysql_users_add() failed for %s@%s\n", username, hostname); - users_free(mysql_users); - return 1; - } - - memset(&serv_addr, 0, sizeof(serv_addr)); - - if (hostname) - if (!setipaddress(&serv_addr.sin_addr, hostname)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", hostname); - users_free(mysql_users); - return 1; - } - key.user = username; - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - key.resource = db; - - fetch_data = mysql_users_fetch(mysql_users, &key); - - users_free(mysql_users); - - if (!fetch_data) - { - return 1; - } - - return 0; -} - -int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, - char *db, char *db_from) -{ - USERS *mysql_users; - int ret = -1; - struct sockaddr_in client_addr; - DCB *dcb; - SERVICE *service; - MYSQL_session *data; - - if ((service = (SERVICE *)MXS_CALLOC(1, sizeof(SERVICE))) == NULL) - { - return ret; - } - - SERV_LISTENER *port = listener_alloc(service, "testlistener", "MySQLClient", NULL, 4006, "MySQLAuth", NULL, NULL); - - dcb = dcb_alloc(DCB_ROLE_INTERNAL, port); - - if (dcb == NULL) - { - fprintf(stderr, "dcb_alloc() failed\n"); - return ret; - } - - memset(&client_addr, 0, sizeof(client_addr)); - - if (hostname) - { - if (!setipaddress(&client_addr.sin_addr, from)) - { - fprintf(stderr, "setipaddress failed for host [%s]\n", from); - MXS_FREE(service); - dcb_close(dcb); - return ret; - } - } - - if ((data = (MYSQL_session *) MXS_CALLOC(1, sizeof(MYSQL_session))) == NULL) - { - MXS_FREE(service); - dcb_close(dcb); - return ret; - } - - - /* client IPv4 in raw data*/ - memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in)); - - dcb->service = service; - - mysql_users = mysql_users_alloc(); - - service->ports = port; - service->ports->users = mysql_users; - - if (db_from != NULL) - { - strcpy(data->db, db_from); - } - else - { - data->db[0] = 0; - } - - /* freed by dcb_close(dcb) */ - dcb->data = data; - - // the routine returns 1 on success - if (anydb != NULL) - { - if (strcmp(anydb, "N") == 0) - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, anydb, db); - } - else if (strcmp(anydb, "Y") == 0) - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "Y", ""); - } - else - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); - } - } - else - { - ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); - } - - if (ret == 0) - { - fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password); - } - else - { - unsigned char db_passwd[100] = ""; - - dcb->remote = MXS_STRDUP_A(from); - - // returns 0 on success - ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); - } - - users_free(mysql_users); - MXS_FREE(service); - dcb_close(dcb); - - return ret; -} - -int main() -{ - int ret; - int i = 0; - int k = 0; - time_t t; - - fprintf(stderr, "----------------\n"); - - time(&t); - fprintf(stderr, "%s\n", asctime(localtime(&t))); - fprintf(stderr, ">>> Started MySQL load, set & get users@host\n"); - - - ret = set_and_get_single_mysql_users("pippo", "localhost", "xyz"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("pippo", "127.0.0.2", "xyz"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("pippo", "%", "xyz"); - assert(ret == 1); - ret = set_and_get_single_mysql_users("rootuser", NULL, "wwwww"); - assert(ret == 0); - ret = set_and_get_single_mysql_users("nullpwd", "this_host_does_not_exists", NULL); - assert(ret == 1); - ret = set_and_get_single_mysql_users("myuser", "345.-1.5.40997", "password"); - assert(ret == 1); - ret = set_and_get_single_mysql_users(NULL, NULL, NULL); - assert(ret == 1); - - ret = set_and_get_single_mysql_users_ipv4("negative", -467295, "_ncd"); - assert(ret == 1); - ret = set_and_get_single_mysql_users_ipv4("extra", 0xFFFFFFFFFUL * 100, "JJcd"); - assert(ret == 1); - ret = set_and_get_single_mysql_users_ipv4("aaapo", 0, "JJcd"); - assert(ret == 0); - ret = set_and_get_single_mysql_users_ipv4(NULL, '\0', "JJcd"); - assert(ret == 1); - - - for (i = 256 * 256 * 256; i <= 256 * 256 * 256 + 5; i++) - { - char user[129] = ""; - snprintf(user, 128, "user_%i", k); - ret = set_and_get_single_mysql_users_ipv4(user, i, "JJcd"); - assert(ret == 0); - k++; - } - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "", "127.0.0.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "%", "two", "192.168.2.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.168.4.%", "ffoo", "192.168.2.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.68.0.2", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "Y", NULL, "cossa"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, N, NULL, ragione\n"); - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "N", NULL, "ragione"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.1", "foo", "192.0.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.1.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "foo", "192.3.2.1", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", "Y", NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", - "matto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", - "fatto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "matto", - "fatto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "", "fto"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", NULL, "grewao"); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.254.%", "foo", "192.254.254.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.254.%", "foo", "192.254.0.242", NULL, NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - ret = set_and_get_mysql_users_wildcards("riccio", "192.0.0.%", "foo", "192.134.0.2", NULL, NULL, NULL); - if (ret) - { - fprintf(stderr, "\t-- Expecting no match\n"); - } - assert(ret == 1); - - ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "12345678901234567890123456789012345678901234", - "192.254.254.245", "Y", NULL, NULL); - if (!ret) - { - fprintf(stderr, "\t-- Expecting ok\n"); - } - assert(ret == 0); - - fprintf(stderr, "----------------\n"); - fprintf(stderr, "<<< Test completed\n"); - - time(&t); - fprintf(stderr, "%s\n", asctime(localtime(&t))); - - return 0; -} - diff --git a/server/modules/filter/cache/test/testrules.c b/server/modules/filter/cache/test/testrules.c index 601d0e2bc..71bee9956 100644 --- a/server/modules/filter/cache/test/testrules.c +++ b/server/modules/filter/cache/test/testrules.c @@ -13,6 +13,8 @@ #include #include "rules.h" +#include +#include #include #include #include @@ -234,8 +236,10 @@ int main() if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) { + set_libdir(MXS_STRDUP_A("../../../../../query_classifier/qc_sqlite/")); if (qc_init("qc_sqlite", "")) { + set_libdir(MXS_STRDUP_A("../")); rc = test(); } else diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index 96a7ba1f7..c49b047a6 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -4585,35 +4585,28 @@ blr_handle_change_master_token(char *input, char *error, CHANGE_MASTER_OPTIONS * static char * blr_get_parsed_command_value(char *input) { - /* space+TAB+= */ - char *sep = " \t="; char *ret = NULL; - char *word; - char *value = NULL; - if (strlen(input)) + if (input && *input) { - value = MXS_STRDUP_A(input); - } - else - { - return ret; - } + char value[strlen(input) + 1]; + strcpy(value, input); - if ((word = get_next_token(NULL, sep, &input)) != NULL) - { - char *ptr; + /* space+TAB+= */ + char *sep = " \t="; + char *word; - /* remove trailing spaces */ - ptr = value + strlen(value) - 1; - while (ptr > value && isspace(*ptr)) + if ((word = get_next_token(NULL, sep, &input)) != NULL) { - *ptr-- = 0; + /* remove trailing spaces */ + char *ptr = value + strlen(value) - 1; + while (ptr > value && isspace(*ptr)) + { + *ptr-- = 0; + } + + ret = MXS_STRDUP_A(strstr(value, word)); } - - ret = MXS_STRDUP_A(strstr(value, word)); - - MXS_FREE(value); } return ret; From e31afa28e4d14807d7d407701302deca43c5089e Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 24 Nov 2016 11:53:40 +0200 Subject: [PATCH 33/48] Serialize created listeners to disk The created listeners are now stored to disk like created servers are. This allows them to be used even after a restart. Currently, the listeners cannot be deleted and need to be manually removed. --- include/maxscale/listener.h | 13 +++ include/maxscale/service.h | 8 +- server/core/config.c | 8 +- server/core/config_runtime.c | 5 +- server/core/listener.c | 143 +++++++++++++++++++++++++++++++++ server/core/service.c | 28 ++----- server/core/test/testservice.c | 4 +- 7 files changed, 176 insertions(+), 33 deletions(-) diff --git a/include/maxscale/listener.h b/include/maxscale/listener.h index a5530eb85..f40a7b310 100644 --- a/include/maxscale/listener.h +++ b/include/maxscale/listener.h @@ -49,6 +49,7 @@ typedef struct servlistener unsigned short port; /**< Port to listen on */ char *address; /**< Address to listen with */ char *authenticator; /**< Name of authenticator */ + char *auth_options; /**< Authenticator options */ void *auth_instance; /**< Authenticator instance created in GWAUTHENTICATOR::initialize() */ SSL_LISTENER *ssl; /**< Structure of SSL data or NULL */ struct dcb *listener; /**< The DCB for the listener */ @@ -59,6 +60,18 @@ typedef struct servlistener struct servlistener *next; /**< Next service protocol */ } SERV_LISTENER; +/** + * @brief Serialize a listener to a file + * + * This converts @c listener into an INI format file. This allows created listeners + * to be persisted to disk. This will replace any existing files with the same + * name. + * + * @param listener Listener to serialize + * @return True if the serialization of the listener was successful, false if it fails + */ +bool listener_serialize(const SERV_LISTENER *listener); + SERV_LISTENER* listener_alloc(struct service* service, const char* name, const char *protocol, const char *address, unsigned short port, const char *authenticator, const char* auth_options, SSL_LISTENER *ssl); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 73c282142..fab8f3128 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -192,9 +192,9 @@ extern SERVICE *service_alloc(const char *, const char *); extern int service_free(SERVICE *); extern SERVICE *service_find(const char *); extern int service_isvalid(SERVICE *); -extern bool serviceAddProtocol(SERVICE *service, const char *name, const char *protocol, - const char *address, unsigned short port, const char *authenticator, - const char *options, SSL_LISTENER *ssl); +extern SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const char *protocol, + const char *address, unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl); extern int serviceHasProtocol(SERVICE *service, const char *protocol, const char* address, unsigned short port); extern void serviceAddBackend(SERVICE *, SERVER *); @@ -204,7 +204,7 @@ extern void serviceAddRouterOption(SERVICE *, char *); extern void serviceClearRouterOptions(SERVICE *); extern int serviceStart(SERVICE *); extern int serviceStartAll(); -extern bool serviceListen(SERVICE *service, unsigned short port); +extern bool serviceListen(SERVICE *service, SERV_LISTENER *port); extern int serviceStop(SERVICE *); extern int serviceRestart(SERVICE *); extern int serviceSetUser(SERVICE *, char *, char *); diff --git a/server/core/config.c b/server/core/config.c index 65d22e9af..58d01c8cb 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -3121,8 +3121,8 @@ int create_new_listener(CONFIG_CONTEXT *obj) } else { - serviceAddProtocol(service, obj->object, protocol, socket, 0, - authenticator, authenticator_options, ssl_info); + serviceCreateListener(service, obj->object, protocol, socket, 0, + authenticator, authenticator_options, ssl_info); } } @@ -3138,8 +3138,8 @@ int create_new_listener(CONFIG_CONTEXT *obj) } else { - serviceAddProtocol(service, obj->object, protocol, address, atoi(port), - authenticator, authenticator_options, ssl_info); + serviceCreateListener(service, obj->object, protocol, address, atoi(port), + authenticator, authenticator_options, ssl_info); } } diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 84740391a..54c60550d 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -411,9 +411,10 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add if (rval) { const char *print_addr = addr ? addr : "0.0.0.0"; + SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr, + u_port, auth, auth_opt, ssl); - if (serviceAddProtocol(service, name, proto, addr, u_port, auth, auth_opt, ssl) && - serviceListen(service, u_port)) + if (listener && listener_serialize(listener) && serviceListen(service, listener)) { MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created", name, print_addr, port, service->name); diff --git a/server/core/listener.c b/server/core/listener.c index a669d2f30..1d811290b 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -30,13 +30,16 @@ #include #include #include +#include #include +#include #include #include #include #include #include #include +#include static RSA *rsa_512 = NULL; static RSA *rsa_1024 = NULL; @@ -69,6 +72,14 @@ listener_alloc(struct service* service, const char* name, const char *protocol, } } + char *my_auth_options = NULL; + + if (auth_options && (my_auth_options = MXS_STRDUP(auth_options)) == NULL) + { + MXS_FREE(my_address); + return NULL; + } + char *my_authenticator = NULL; if (authenticator) @@ -116,6 +127,7 @@ listener_alloc(struct service* service, const char* name, const char *protocol, proto->address = my_address; proto->port = port; proto->authenticator = my_authenticator; + proto->auth_options = my_auth_options; proto->ssl = ssl; proto->users = NULL; proto->resources = NULL; @@ -146,6 +158,8 @@ void listener_free(SERV_LISTENER* listener) MXS_FREE(listener->address); MXS_FREE(listener->authenticator); + MXS_FREE(listener->auth_options); + MXS_FREE(listener->name); MXS_FREE(listener->protocol); MXS_FREE(listener); } @@ -376,3 +390,132 @@ tmp_rsa_callback(SSL *s, int is_export, int keylength) } return(rsa_tmp); } + +/** + * Creates a listener configuration at the location pointed by @c filename + * + * @param listener Listener to serialize into a configuration + * @param filename Filename where configuration is written + * @return True on success, false on error + */ +static bool create_listener_config(const SERV_LISTENER *listener, const char *filename) +{ + int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (file == -1) + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to open file '%s' when serializing listener '%s': %d, %s", + filename, listener->name, errno, strerror_r(errno, errbuf, sizeof(errbuf))); + return false; + } + + // TODO: Check for return values on all of the dprintf calls + dprintf(file, "[%s]\n", listener->name); + dprintf(file, "type=listener\n"); + dprintf(file, "protocol=%s\n", listener->protocol); + dprintf(file, "service=%s\n", listener->service->name); + dprintf(file, "address=%s\n", listener->address); + dprintf(file, "port=%u\n", listener->port); + dprintf(file, "authenticator=%s\n", listener->authenticator); + + if (listener->auth_options) + { + dprintf(file, "authenticator_options=%s\n", listener->auth_options); + } + + if (listener->ssl) + { + dprintf(file, "ssl=required\n"); + + if (listener->ssl->ssl_cert) + { + dprintf(file, "ssl_cert=%s\n", listener->ssl->ssl_cert); + } + + if (listener->ssl->ssl_key) + { + dprintf(file, "ssl_key=%s\n", listener->ssl->ssl_key); + } + + if (listener->ssl->ssl_ca_cert) + { + dprintf(file, "ssl_ca_cert=%s\n", listener->ssl->ssl_ca_cert); + } + if (listener->ssl->ssl_cert_verify_depth) + { + dprintf(file, "ssl_cert_verify_depth=%d\n", listener->ssl->ssl_cert_verify_depth); + } + + const char *version = NULL; + + switch (listener->ssl->ssl_method_type) + { + case SERVICE_TLS10: + version = "TLSV10"; + break; + +#ifdef OPENSSL_1_0 + case SERVICE_TLS11: + version = "TLSV11"; + break; + + case SERVICE_TLS12: + version = "TLSV12"; + break; +#endif + case SERVICE_SSL_TLS_MAX: + version = "MAX"; + break; + + default: + break; + } + + if (version) + { + dprintf(file, "ssl_version=%s\n", version); + } + } + + close(file); + + return true; +} + +bool listener_serialize(const SERV_LISTENER *listener) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), + listener->name); + + if (unlink(filename) == -1 && errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove temporary listener configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else if (create_listener_config(listener, filename)) + { + char final_filename[PATH_MAX]; + strcpy(final_filename, filename); + + char *dot = strrchr(final_filename, '.'); + ss_dassert(dot); + *dot = '\0'; + + if (rename(filename, final_filename) == 0) + { + rval = true; + } + else + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to rename temporary listener configuration at '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + } + + return rval; +} diff --git a/server/core/service.c b/server/core/service.c index 2c04f8e54..66e3da276 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -508,22 +508,9 @@ serviceStart(SERVICE *service) * @param service The service to start the listener for * @param port The port number */ -bool serviceListen(SERVICE *service, unsigned short port) +bool serviceListen(SERVICE *service, SERV_LISTENER *port) { - bool rval = false; - for (SERV_LISTENER *ptr = service->ports; ptr; ptr = ptr->next) - { - if (ptr->port == port) - { - if (serviceStartPort(service, ptr)) - { - rval = true; - } - break; - } - } - - return rval; + return serviceStartPort(service, port); } /** @@ -678,7 +665,7 @@ service_free(SERVICE *service) } /** - * Add a protocol/port pair to the service + * Create a listener for the service * * @param service The service * @param protocol The name of the protocol module @@ -686,13 +673,13 @@ service_free(SERVICE *service) * @param port The port to listen on * @param authenticator Name of the authenticator to be used * @param ssl SSL configuration - * @return TRUE if the protocol/port could be added + * + * @return Created listener or NULL on error */ -bool serviceAddProtocol(SERVICE *service, const char *name, const char *protocol, +SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const char *protocol, const char *address, unsigned short port, const char *authenticator, const char *options, SSL_LISTENER *ssl) { - bool rval = false; SERV_LISTENER *proto = listener_alloc(service, name, protocol, address, port, authenticator, options, ssl); @@ -702,10 +689,9 @@ bool serviceAddProtocol(SERVICE *service, const char *name, const char *protocol proto->next = service->ports; service->ports = proto; spinlock_release(&service->spin); - rval = true; } - return rval; + return proto; } /** diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index e6aa0bd5b..cef230b21 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -69,8 +69,8 @@ test1() ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name"); ss_dfprintf(stderr, "\t..done\nAdding protocol testprotocol."); set_libdir(MXS_STRDUP_A("../../modules/authenticator/MySQLAuth/")); - ss_info_dassert(0 != serviceAddProtocol(service, "TestProtocol", "testprotocol", - "localhost", 9876, "MySQLAuth", NULL, NULL), + ss_info_dassert(serviceCreateListener(service, "TestProtocol", "testprotocol", + "localhost", 9876, "MySQLAuth", NULL, NULL), "Add Protocol should succeed"); ss_info_dassert(0 != serviceHasProtocol(service, "testprotocol", "localhost", 9876), "Service should have new protocol as requested"); From ff405a4f5f6ee6a25feeed5078dbeae2a02db9f1 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 23 Nov 2016 15:48:33 +0200 Subject: [PATCH 34/48] Cache compiled as C++ Cache compiled as C++ in order to allow the use of standard C++ data structures. --- server/modules/filter/cache/CMakeLists.txt | 2 +- server/modules/filter/cache/{cache.c => cache.cc} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename server/modules/filter/cache/{cache.c => cache.cc} (99%) diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 3977cfbb0..52566a844 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cache.c rules.c storage.c) + add_library(cache SHARED cache.cc rules.c storage.c) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cache.c b/server/modules/filter/cache/cache.cc similarity index 99% rename from server/modules/filter/cache/cache.c rename to server/modules/filter/cache/cache.cc index 69d518944..093939db3 100644 --- a/server/modules/filter/cache/cache.c +++ b/server/modules/filter/cache/cache.cc @@ -51,7 +51,7 @@ MODULE_INFO info = "A caching filter that is capable of caching and returning cached data." }; -char *version() +extern "C" char *version() { return VERSION_STRING; } @@ -60,7 +60,7 @@ char *version() * The module initialization functions, called when the module has * been loaded. */ -void ModuleInit() +extern "C" void ModuleInit() { } @@ -69,7 +69,7 @@ void ModuleInit() * * @return The module object. */ -FILTER_OBJECT *GetModuleObject() +extern "C" FILTER_OBJECT *GetModuleObject() { static FILTER_OBJECT object = { @@ -263,7 +263,7 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER if (rules) { - cinstance = MXS_CALLOC(1, sizeof(CACHE_INSTANCE)); + cinstance = (CACHE_INSTANCE*)MXS_CALLOC(1, sizeof(CACHE_INSTANCE)); HASHTABLE* pending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); if (cinstance && pending) @@ -405,7 +405,7 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - uint8_t *data = GWBUF_DATA(packet); + uint8_t *data = (uint8_t*)GWBUF_DATA(packet); // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING ss_dassert(GWBUF_IS_CONTIGUOUS(packet)); @@ -425,7 +425,7 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) { ss_dassert(!csdata->use_db); size_t len = MYSQL_GET_PACKET_LEN(data) - 1; // Remove the command byte. - csdata->use_db = MXS_MALLOC(len + 1); + csdata->use_db = (char*)MXS_MALLOC(len + 1); if (csdata->use_db) { @@ -1087,7 +1087,7 @@ static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONF const char *datadir = get_datadir(); size_t len = strlen(datadir) + 1 + strlen(param->value) + 1; - char *rules = MXS_MALLOC(len); + char *rules = (char*)MXS_MALLOC(len); if (rules) { From 762da5f2df4587bf85ab585dc61e38457d72e67a Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 09:39:25 +0200 Subject: [PATCH 35/48] Cache: Make everything C++ and rename some files --- server/modules/filter/cache/CMakeLists.txt | 2 +- .../filter/cache/{cache.cc => cachefilter.cc} | 2 +- .../filter/cache/{cache.h => cachefilter.h} | 0 .../modules/filter/cache/{rules.c => rules.cc} | 18 ++++++++++-------- .../filter/cache/{storage.c => storage.cc} | 0 .../modules/filter/cache/test/CMakeLists.txt | 2 +- .../cache/test/{testrules.c => testrules.cc} | 6 +++--- 7 files changed, 16 insertions(+), 14 deletions(-) rename server/modules/filter/cache/{cache.cc => cachefilter.cc} (99%) rename server/modules/filter/cache/{cache.h => cachefilter.h} (100%) rename server/modules/filter/cache/{rules.c => rules.cc} (99%) rename server/modules/filter/cache/{storage.c => storage.cc} (100%) rename server/modules/filter/cache/test/{testrules.c => testrules.cc} (98%) diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 52566a844..2592217bd 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cache.cc rules.c storage.c) + add_library(cache SHARED cachefilter.cc rules.cc storage.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cachefilter.cc similarity index 99% rename from server/modules/filter/cache/cache.cc rename to server/modules/filter/cache/cachefilter.cc index 093939db3..458a36060 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -12,7 +12,7 @@ */ #define MXS_MODULE_NAME "cache" -#include "cache.h" +#include "cachefilter.h" #include #include #include diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cachefilter.h similarity index 100% rename from server/modules/filter/cache/cache.h rename to server/modules/filter/cache/cachefilter.h diff --git a/server/modules/filter/cache/rules.c b/server/modules/filter/cache/rules.cc similarity index 99% rename from server/modules/filter/cache/rules.c rename to server/modules/filter/cache/rules.cc index c4917aaa5..26d6ee95a 100644 --- a/server/modules/filter/cache/rules.c +++ b/server/modules/filter/cache/rules.cc @@ -20,7 +20,7 @@ #include #include #include -#include "cache.h" +#include "cachefilter.h" static const char KEY_ATTRIBUTE[] = "attribute"; static const char KEY_OP[] = "op"; @@ -41,8 +41,8 @@ static const char VALUE_OP_UNLIKE[] = "unlike"; struct cache_attribute_mapping { - const char* name; - int value; + const char* name; + cache_rule_attribute_t value; }; static struct cache_attribute_mapping cache_store_attributes[] = @@ -51,13 +51,13 @@ static struct cache_attribute_mapping cache_store_attributes[] = { VALUE_ATTRIBUTE_DATABASE, CACHE_ATTRIBUTE_DATABASE }, { VALUE_ATTRIBUTE_QUERY, CACHE_ATTRIBUTE_QUERY }, { VALUE_ATTRIBUTE_TABLE, CACHE_ATTRIBUTE_TABLE }, - { NULL, 0 } + { NULL, static_cast(0) } }; static struct cache_attribute_mapping cache_use_attributes[] = { { VALUE_ATTRIBUTE_USER, CACHE_ATTRIBUTE_USER }, - { NULL, 0 } + { NULL, static_cast(0) } }; static bool cache_rule_attribute_get(struct cache_attribute_mapping *mapping, @@ -567,6 +567,7 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut char *at = strchr(value, '@'); char *user = value; char *host; + char any[2]; // sizeof("%") if (at) { @@ -575,7 +576,8 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut } else { - host = "%"; + strcpy(any, "%"); + host = any; } if (dequote_mysql(user)) @@ -610,7 +612,7 @@ static CACHE_RULE *cache_rule_create_simple_user(cache_rule_attribute_t attribut // No wildcard, no need to use regexp. rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); - char *value = MXS_MALLOC(strlen(user) + 1 + strlen(host) + 1); + char *value = (char*)MXS_MALLOC(strlen(user) + 1 + strlen(host) + 1); if (rule && value) { @@ -827,7 +829,7 @@ static CACHE_RULE *cache_rule_create_simple_query(cache_rule_attribute_t attribu ss_dassert(attribute == CACHE_ATTRIBUTE_QUERY); ss_dassert((op == CACHE_OP_EQ) || (op == CACHE_OP_NEQ)); - CACHE_RULE *rule = MXS_CALLOC(1, sizeof(CACHE_RULE)); + CACHE_RULE *rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); char *value = MXS_STRDUP(cvalue); if (rule && value) diff --git a/server/modules/filter/cache/storage.c b/server/modules/filter/cache/storage.cc similarity index 100% rename from server/modules/filter/cache/storage.c rename to server/modules/filter/cache/storage.cc diff --git a/server/modules/filter/cache/test/CMakeLists.txt b/server/modules/filter/cache/test/CMakeLists.txt index 061d2323f..c2d8e3cf7 100644 --- a/server/modules/filter/cache/test/CMakeLists.txt +++ b/server/modules/filter/cache/test/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(testrules testrules.c ../rules.c) +add_executable(testrules testrules.cc ../rules.cc) include_directories(..) target_link_libraries(testrules maxscale-common jansson) diff --git a/server/modules/filter/cache/test/testrules.c b/server/modules/filter/cache/test/testrules.cc similarity index 98% rename from server/modules/filter/cache/test/testrules.c rename to server/modules/filter/cache/test/testrules.cc index 71bee9956..c613305a8 100644 --- a/server/modules/filter/cache/test/testrules.c +++ b/server/modules/filter/cache/test/testrules.cc @@ -80,7 +80,7 @@ int test_user() { int errors = 0; - for (int i = 0; i < n_user_test_cases; ++i) + for (size_t i = 0; i < n_user_test_cases; ++i) { const struct user_test_case *test_case = &user_test_cases[i]; @@ -182,9 +182,9 @@ int test_store() { int errors = 0; - for (int i = 0; i < n_store_test_cases; ++i) + for (size_t i = 0; i < n_store_test_cases; ++i) { - printf("TC : %d\n", i + 1); + printf("TC : %d\n", (int)(i + 1)); const struct store_test_case *test_case = &store_test_cases[i]; CACHE_RULES *rules = cache_rules_parse(test_case->rule, 0); From 10deb9f07b675d27b0e96f26ee8d29110b7fb387 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 11:43:29 +0200 Subject: [PATCH 36/48] Cache: Introduce StorageFactory and Storage The storage module is abstracted with StorageFactory that is capable of creating Storage instances. The latter contains the data and provides the behaviour for using the actual storage implementation, which sits behing a C API, conveniently. --- server/modules/filter/cache/CMakeLists.txt | 2 +- server/modules/filter/cache/cachefilter.cc | 29 ++-- server/modules/filter/cache/storage.cc | 104 ++++-------- server/modules/filter/cache/storage.h | 36 +++- server/modules/filter/cache/storagefactory.cc | 154 ++++++++++++++++++ server/modules/filter/cache/storagefactory.h | 44 +++++ 6 files changed, 267 insertions(+), 102 deletions(-) create mode 100644 server/modules/filter/cache/storagefactory.cc create mode 100644 server/modules/filter/cache/storagefactory.h diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 2592217bd..9934207f5 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cachefilter.cc rules.cc storage.cc) + add_library(cache SHARED cachefilter.cc rules.cc storage.cc storagefactory.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 458a36060..ed97c4fb9 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -25,6 +25,7 @@ #include #include "rules.h" #include "storage.h" +#include "storagefactory.h" static char VERSION_STRING[] = "V1.0.0"; @@ -124,8 +125,8 @@ typedef struct cache_instance const char *name; // The name of the instance; the section name in the config. CACHE_CONFIG config; // The configuration of the cache instance. CACHE_RULES *rules; // The rules of the cache instance. - CACHE_STORAGE_MODULE *module; // The storage module. - CACHE_STORAGE *storage; // The storage API. + StorageFactory *factory; // The storage factory. + Storage *storage; // The storage instance to use. HASHTABLE *pending; // Pending items; being fetched from the backend. SPINLOCK pending_lock; // Lock used for protecting 'pending'. } CACHE_INSTANCE; @@ -154,8 +155,7 @@ static void cache_response_state_reset(CACHE_RESPONSE_STATE *state); typedef struct cache_session_data { CACHE_INSTANCE *instance; /**< The cache instance the session is associated with. */ - CACHE_STORAGE_API *api; /**< The storage API to be used. */ - CACHE_STORAGE *storage; /**< The storage to be used with this session data. */ + Storage *storage; /**< The storage to be used with this session data. */ DOWNSTREAM down; /**< The previous filter or equivalent. */ UPSTREAM up; /**< The next filter or equivalent. */ CACHE_RESPONSE_STATE res; /**< The response state. */ @@ -268,22 +268,22 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER if (cinstance && pending) { - CACHE_STORAGE_MODULE *module = cache_storage_open(config.storage); + StorageFactory *factory = StorageFactory::Open(config.storage); - if (module) + if (factory) { uint32_t ttl = config.ttl; int argc = config.storage_argc; char** argv = config.storage_argv; - CACHE_STORAGE *storage = module->api->createInstance(name, ttl, argc, argv); + Storage *storage = factory->createStorage(name, ttl, argc, argv); if (storage) { cinstance->name = name; cinstance->config = config; cinstance->rules = rules; - cinstance->module = module; + cinstance->factory = factory; cinstance->storage = storage; cinstance->pending = pending; @@ -293,7 +293,7 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER { MXS_ERROR("Could not create storage instance for '%s'.", name); cache_rules_free(rules); - cache_storage_close(module); + delete factory; MXS_FREE(cinstance); hashtable_free(pending); cinstance = NULL; @@ -722,7 +722,6 @@ static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, if ((mysql_session->db[0] == 0) || default_db) { data->instance = instance; - data->api = instance->module->api; data->storage = instance->storage; data->session = session; data->state = CACHE_EXPECTING_NOTHING; @@ -1202,13 +1201,13 @@ static cache_result_t get_cached_response(CACHE_SESSION_DATA *csdata, const GWBUF *query, GWBUF **value) { - cache_result_t result = csdata->api->getKey(csdata->storage, csdata->default_db, query, csdata->key); + cache_result_t result = csdata->storage->getKey(csdata->default_db, query, csdata->key); if (result == CACHE_RESULT_OK) { uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; - result = csdata->api->getValue(csdata->storage, csdata->key, flags, value); + result = csdata->storage->getValue(csdata->key, flags, value); } else { @@ -1250,15 +1249,13 @@ static void store_result(CACHE_SESSION_DATA *csdata) { csdata->res.data = data; - cache_result_t result = csdata->api->putValue(csdata->storage, - csdata->key, - csdata->res.data); + cache_result_t result = csdata->storage->putValue(csdata->key, csdata->res.data); if (result != CACHE_RESULT_OK) { MXS_ERROR("Could not store cache item, deleting it."); - result = csdata->api->delValue(csdata->storage, csdata->key); + result = csdata->storage->delValue(csdata->key); if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) { diff --git a/server/modules/filter/cache/storage.cc b/server/modules/filter/cache/storage.cc index 165905dd1..2d724866a 100644 --- a/server/modules/filter/cache/storage.cc +++ b/server/modules/filter/cache/storage.cc @@ -12,85 +12,37 @@ */ #include "storage.h" -#include -#include -#include -#include -#include -CACHE_STORAGE_MODULE* cache_storage_open(const char *name) + +Storage::Storage(CACHE_STORAGE_API* pApi, CACHE_STORAGE* pStorage) + : m_pApi(pApi) + , m_pStorage(pStorage) { - CACHE_STORAGE_MODULE* module = (CACHE_STORAGE_MODULE*)MXS_CALLOC(1, sizeof(CACHE_STORAGE_MODULE)); - - if (module) - { - char path[MAXPATHLEN + 1]; - sprintf(path, "%s/lib%s.so", get_libdir(), name); - - void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); - - if (handle) - { - module->handle = handle; - - void *f = dlsym(module->handle, CACHE_STORAGE_ENTRY_POINT); - - if (f) - { - module->api = ((CacheGetStorageAPIFN)f)(); - - if (module->api) - { - if (!(module->api->initialize)()) - { - MXS_ERROR("Initialization of %s failed.", path); - - (void)dlclose(module->handle); - MXS_FREE(module); - module = NULL; - } - } - else - { - MXS_ERROR("Could not obtain API object from %s.", name); - - (void)dlclose(module->handle); - MXS_FREE(module); - module = NULL; - } - } - else - { - const char* s = dlerror(); - MXS_ERROR("Could not look up symbol %s from %s: %s", - name, CACHE_STORAGE_ENTRY_POINT, s ? s : ""); - MXS_FREE(module); - module = NULL; - } - } - else - { - const char* s = dlerror(); - MXS_ERROR("Could not load %s: %s", name, s ? s : ""); - MXS_FREE(module); - module = NULL; - } - } - - return module; + ss_dassert(m_pApi); + ss_dassert(m_pStorage); } - -void cache_storage_close(CACHE_STORAGE_MODULE *module) +cache_result_t Storage::getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey) { - if (module) - { - if (dlclose(module->handle) != 0) - { - const char *s = dlerror(); - MXS_ERROR("Could not close module %s: ", s ? s : ""); - } - - MXS_FREE(module); - } + return m_pApi->getKey(m_pStorage, zDefaultDb, pQuery, pKey); +} + +cache_result_t Storage::getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue) +{ + return m_pApi->getValue(m_pStorage, pKey, flags, ppValue); +} + +cache_result_t Storage::putValue(const char* pKey, + const GWBUF* pValue) +{ + return m_pApi->putValue(m_pStorage, pKey, pValue); +} + +cache_result_t Storage::delValue(const char* pKey) +{ + return m_pApi->delValue(m_pStorage, pKey); } diff --git a/server/modules/filter/cache/storage.h b/server/modules/filter/cache/storage.h index f0d2f8bef..a2eadfaaf 100644 --- a/server/modules/filter/cache/storage.h +++ b/server/modules/filter/cache/storage.h @@ -17,17 +17,35 @@ #include #include "cache_storage_api.h" -MXS_BEGIN_DECLS - -typedef struct cache_storage_module_t +class Storage { - void* handle; - CACHE_STORAGE_API* api; -} CACHE_STORAGE_MODULE; +public: + ~Storage(); -CACHE_STORAGE_MODULE* cache_storage_open(const char *name); -void cache_storage_close(CACHE_STORAGE_MODULE *module); + cache_result_t getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey); -MXS_END_DECLS + cache_result_t getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue); + + cache_result_t putValue(const char* pKey, + const GWBUF* pValue); + + cache_result_t delValue(const char* pKey); + +private: + friend class StorageFactory; + + Storage(CACHE_STORAGE_API* pApi, CACHE_STORAGE* pStorage); + + Storage(const Storage&); + Storage& operator = (const Storage&); + +private: + CACHE_STORAGE_API* m_pApi; + CACHE_STORAGE* m_pStorage; +}; #endif diff --git a/server/modules/filter/cache/storagefactory.cc b/server/modules/filter/cache/storagefactory.cc new file mode 100644 index 000000000..9b088da76 --- /dev/null +++ b/server/modules/filter/cache/storagefactory.cc @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "storagefactory.h" +#include +#include +#include +#include +#include +#include +#include "storage.h" + + +namespace +{ + +bool open_cache_storage(const char* zName, void** pHandle, CACHE_STORAGE_API** ppApi) +{ + bool rv = false; + + char path[MAXPATHLEN + 1]; + sprintf(path, "%s/lib%s.so", get_libdir(), zName); + + void* handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + + if (handle) + { + void* f = dlsym(handle, CACHE_STORAGE_ENTRY_POINT); + + if (f) + { + CACHE_STORAGE_API* pApi = ((CacheGetStorageAPIFN)f)(); + + if (pApi) + { + if ((pApi->initialize)()) + { + *pHandle = handle; + *ppApi = pApi; + + rv = true; + } + else + { + MXS_ERROR("Initialization of %s failed.", path); + + (void)dlclose(handle); + } + } + else + { + MXS_ERROR("Could not obtain API object from %s.", zName); + + (void)dlclose(handle); + } + } + else + { + const char* s = dlerror(); + MXS_ERROR("Could not look up symbol %s from %s: %s", + zName, CACHE_STORAGE_ENTRY_POINT, s ? s : ""); + } + } + else + { + const char* s = dlerror(); + MXS_ERROR("Could not load %s: %s", zName, s ? s : ""); + } + + return rv; +} + + +void close_cache_storage(void* handle, CACHE_STORAGE_API* pApi) +{ + // TODO: pApi->finalize(); + + if (dlclose(handle) != 0) + { + const char *s = dlerror(); + MXS_ERROR("Could not close module %s: ", s ? s : ""); + } +} + +} + +StorageFactory::StorageFactory(void* handle, CACHE_STORAGE_API* pApi) + : m_handle(handle) + , m_pApi(pApi) +{ + ss_dassert(handle); + ss_dassert(pApi); +} + +StorageFactory::~StorageFactory() +{ + close_cache_storage(m_handle, m_pApi); + m_handle = 0; + m_pApi = 0; +} + +//static +StorageFactory* StorageFactory::Open(const char* zName) +{ + StorageFactory* pFactory = 0; + + void* handle; + CACHE_STORAGE_API* pApi; + + if (open_cache_storage(zName, &handle, &pApi)) + { + pFactory = new (std::nothrow) StorageFactory(handle, pApi); + + if (!pFactory) + { + close_cache_storage(handle, pApi); + } + } + + return pFactory; +} + +Storage* StorageFactory::createStorage(const char* zName, + uint32_t ttl, + int argc, char* argv[]) +{ + ss_dassert(m_handle); + ss_dassert(m_pApi); + + Storage* pStorage = 0; + CACHE_STORAGE* pRawStorage = m_pApi->createInstance(zName, ttl, argc, argv); + + if (pRawStorage) + { + pStorage = new (std::nothrow) Storage(m_pApi, pRawStorage); + + if (!pStorage) + { + m_pApi->freeInstance(pRawStorage); + } + } + + return pStorage; +} diff --git a/server/modules/filter/cache/storagefactory.h b/server/modules/filter/cache/storagefactory.h new file mode 100644 index 000000000..e87f57742 --- /dev/null +++ b/server/modules/filter/cache/storagefactory.h @@ -0,0 +1,44 @@ +#pragma once +#ifndef _MAXSCALE_FILTER_CACHE_STORAGEFACTORY_H +#define _MAXSCALE_FILTER_CACHE_STORAGEFACTORY_H +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include "cache_storage_api.h" + +class Storage; + +class StorageFactory +{ +public: + ~StorageFactory(); + + static StorageFactory* Open(const char* zName); + + Storage* createStorage(const char* zName, + uint32_t ttl, + int argc, char* argv[]); + +private: + StorageFactory(void* handle, CACHE_STORAGE_API* pApi); + + StorageFactory(const StorageFactory&); + StorageFactory& operator = (const StorageFactory&); + +private: + void* m_handle; + CACHE_STORAGE_API* m_pApi; +}; + +#endif From b5022e1deb8080aaf87662dde54e33b171c3dabe Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 14:42:56 +0200 Subject: [PATCH 37/48] Add SessionCache SessionCache abstracts the cache for a specific session. It then uses the actual Cache instance for storing data. It is not used yet. --- server/modules/filter/cache/CMakeLists.txt | 2 +- server/modules/filter/cache/cachefilter.cc | 26 +- server/modules/filter/cache/cachefilter.h | 31 +- server/modules/filter/cache/sessioncache.cc | 684 ++++++++++++++++++++ server/modules/filter/cache/sessioncache.h | 140 ++++ 5 files changed, 855 insertions(+), 28 deletions(-) create mode 100644 server/modules/filter/cache/sessioncache.cc create mode 100644 server/modules/filter/cache/sessioncache.h diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 9934207f5..ff340d9e8 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cachefilter.cc rules.cc storage.cc storagefactory.cc) + add_library(cache SHARED cachefilter.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index ed97c4fb9..185e66c2a 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -94,19 +94,6 @@ extern "C" FILTER_OBJECT *GetModuleObject() // Implementation // -typedef struct cache_config -{ - uint32_t max_resultset_rows; - uint32_t max_resultset_size; - const char *rules; - const char *storage; - char *storage_options; - char **storage_argv; - int storage_argc; - uint32_t ttl; - uint32_t debug; -} CACHE_CONFIG; - static const CACHE_CONFIG DEFAULT_CONFIG = { CACHE_DEFAULT_MAX_RESULTSET_ROWS, @@ -120,17 +107,6 @@ static const CACHE_CONFIG DEFAULT_CONFIG = CACHE_DEFAULT_DEBUG }; -typedef struct cache_instance -{ - const char *name; // The name of the instance; the section name in the config. - CACHE_CONFIG config; // The configuration of the cache instance. - CACHE_RULES *rules; // The rules of the cache instance. - StorageFactory *factory; // The storage factory. - Storage *storage; // The storage instance to use. - HASHTABLE *pending; // Pending items; being fetched from the backend. - SPINLOCK pending_lock; // Lock used for protecting 'pending'. -} CACHE_INSTANCE; - typedef enum cache_session_state { CACHE_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. @@ -190,7 +166,7 @@ static void store_result(CACHE_SESSION_DATA *csdata); * * @returns Corresponding integer hash. */ -static int hash_of_key(const void* key) +int hash_of_key(const void* key) { int hash = 0; diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index 0b03cdeb5..e76a63ccc 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -16,8 +16,11 @@ #include #include +#include +#include "rules.h" -MXS_BEGIN_DECLS +class Storage; +class StorageFactory; #define CACHE_DEBUG_NONE 0 /* 0b00000 */ #define CACHE_DEBUG_MATCHING 1 /* 0b00001 */ @@ -40,6 +43,30 @@ MXS_BEGIN_DECLS // Integer value #define CACHE_DEFAULT_DEBUG 0 -MXS_END_DECLS +typedef struct cache_config +{ + uint32_t max_resultset_rows; + uint32_t max_resultset_size; + const char *rules; + const char *storage; + char *storage_options; + char **storage_argv; + int storage_argc; + uint32_t ttl; + uint32_t debug; +} CACHE_CONFIG; + +typedef struct cache_instance +{ + const char *name; // The name of the instance; the section name in the config. + CACHE_CONFIG config; // The configuration of the cache instance. + CACHE_RULES *rules; // The rules of the cache instance. + StorageFactory *factory; // The storage factory. + Storage *storage; // The storage instance to use. + HASHTABLE *pending; // Pending items; being fetched from the backend. + SPINLOCK pending_lock; // Lock used for protecting 'pending'. +} CACHE_INSTANCE; + +int hash_of_key(const void* key); #endif diff --git a/server/modules/filter/cache/sessioncache.cc b/server/modules/filter/cache/sessioncache.cc new file mode 100644 index 000000000..fcb35c4b5 --- /dev/null +++ b/server/modules/filter/cache/sessioncache.cc @@ -0,0 +1,684 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "sessioncache.h" +#include +#include +#include +#include +#include "storage.h" + +#define DUMMY_VALUE (void*)0xdeadbeef + +SessionCache::SessionCache(CACHE_INSTANCE* pInstance, SESSION* pSession, char* zDefaultDb) + : m_state(CACHE_EXPECTING_NOTHING) + , m_pInstance(pInstance) + , m_pSession(pSession) + , m_pStorage(pInstance->storage) + , m_zDefaultDb(zDefaultDb) + , m_zUseDb(NULL) + , m_refreshing(false) +{ + memset(&m_down, 0, sizeof(m_down)); + memset(&m_up, 0, sizeof(m_up)); + memset(m_key, 0, CACHE_KEY_MAXLEN); + + reset_response_state(); +} + +SessionCache::~SessionCache() +{ + MXS_FREE(m_zUseDb); + MXS_FREE(m_zDefaultDb); +} + +//static +SessionCache* SessionCache::Create(CACHE_INSTANCE* pInstance, SESSION* pSession) +{ + SessionCache* pSessionCache = NULL; + + ss_dassert(pSession->client_dcb); + ss_dassert(pSession->client_dcb->data); + + MYSQL_session *pMysqlSession = (MYSQL_session*)pSession->client_dcb->data; + char* zDefaultDb = NULL; + + if (pMysqlSession->db[0] != 0) + { + zDefaultDb = MXS_STRDUP(pMysqlSession->db); + } + + if ((pMysqlSession->db[0] == 0) || zDefaultDb) + { + pSessionCache = new (std::nothrow) SessionCache(pInstance, pSession, zDefaultDb); + + if (!pSessionCache) + { + MXS_FREE(zDefaultDb); + } + } + + return pSessionCache; +} + +void SessionCache::close() +{ +} + +void SessionCache::setDownstream(DOWNSTREAM* pDown) +{ + m_down = *pDown; +} + +void SessionCache::setUpstream(UPSTREAM* pUp) +{ + m_up = *pUp; +} + +int SessionCache::routeQuery(GWBUF* pPacket) +{ + uint8_t* pData = static_cast(GWBUF_DATA(pPacket)); + + // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING + ss_dassert(GWBUF_IS_CONTIGUOUS(pPacket)); + ss_dassert(GWBUF_LENGTH(pPacket) >= MYSQL_HEADER_LEN + 1); + ss_dassert(MYSQL_GET_PACKET_LEN(pData) + MYSQL_HEADER_LEN == GWBUF_LENGTH(pPacket)); + + bool fetch_from_server = true; + + reset_response_state(); + m_state = CACHE_IGNORING_RESPONSE; + + int rv; + + switch ((int)MYSQL_GET_COMMAND(pData)) + { + case MYSQL_COM_INIT_DB: + { + ss_dassert(!m_zUseDb); + size_t len = MYSQL_GET_PACKET_LEN(pData) - 1; // Remove the command byte. + m_zUseDb = (char*)MXS_MALLOC(len + 1); + + if (m_zUseDb) + { + memcpy(m_zUseDb, (char*)(pData + MYSQL_HEADER_LEN + 1), len); + m_zUseDb[len] = 0; + m_state = CACHE_EXPECTING_USE_RESPONSE; + } + else + { + // Memory allocation failed. We need to remove the default database to + // prevent incorrect cache entries, since we won't know what the + // default db is. But we only need to do that if "USE " really + // succeeds. The right thing will happen by itself in + // handle_expecting_use_response(); if OK is returned, default_db will + // become NULL, if ERR, default_db will not be changed. + } + } + break; + + case MYSQL_COM_QUERY: + { + // We do not care whether the query was fully parsed or not. + // If a query cannot be fully parsed, the worst thing that can + // happen is that caching is not used, even though it would be + // possible. + if (qc_get_operation(pPacket) == QUERY_OP_SELECT) + { + SESSION *session = m_pSession; + + if ((session_is_autocommit(session) && !session_trx_is_active(session)) || + session_trx_is_read_only(session)) + { + if (cache_rules_should_store(m_pInstance->rules, m_zDefaultDb, pPacket)) + { + if (cache_rules_should_use(m_pInstance->rules, m_pSession)) + { + GWBUF* pResponse; + cache_result_t result = get_cached_response(pPacket, &pResponse); + + switch (result) + { + case CACHE_RESULT_STALE: + { + // The value was found, but it was stale. Now we need to + // figure out whether somebody else is already fetching it. + + long key = hash_of_key(m_key); + + spinlock_acquire(&m_pInstance->pending_lock); + // TODO: Remove the internal locking of hashtable. The internal + // TODO: locking is no good if you need transactional behaviour. + // TODO: Now we lock twice. + void *value = hashtable_fetch(m_pInstance->pending, (void*)key); + if (!value) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(m_pInstance->pending, (void*)key, DUMMY_VALUE); + } + spinlock_release(&m_pInstance->pending_lock); + + if (!value) + { + // We were the first ones who hit the stale item. It's + // our responsibility now to fetch it. + if (log_decisions()) + { + MXS_NOTICE("Cache data is stale, fetching fresh from server."); + } + m_refreshing = true; + fetch_from_server = true; + break; + } + else + { + // Somebody is already fetching the new value. So, let's + // use the stale value. No point in hitting the server twice. + if (log_decisions()) + { + MXS_NOTICE("Cache data is stale but returning it, fresh " + "data is being fetched already."); + } + fetch_from_server = false; + } + } + break; + + case CACHE_RESULT_OK: + if (log_decisions()) + { + MXS_NOTICE("Using fresh data from cache."); + } + fetch_from_server = false; + break; + + default: + fetch_from_server = true; + } + + if (fetch_from_server) + { + m_state = CACHE_EXPECTING_RESPONSE; + } + else + { + m_state = CACHE_EXPECTING_NOTHING; + gwbuf_free(pPacket); + DCB *dcb = m_pSession->client_dcb; + + // TODO: This is not ok. Any filters before this filter, will not + // TODO: see this data. + rv = dcb->func.write(dcb, pResponse); + } + } + } + else + { + m_state = CACHE_IGNORING_RESPONSE; + } + } + else + { + if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + { + MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " + "storing to cache.", + session_is_autocommit(m_pSession) ? "ON" : "OFF", + session_trx_state_to_string(session_get_trx_state(m_pSession))); + } + } + } + break; + + default: + break; + } + } + + if (fetch_from_server) + { + rv = m_down.routeQuery(m_down.instance, m_down.session, pPacket); + } + + return rv; +} + +int SessionCache::clientReply(GWBUF* pData) +{ + int rv; + + if (m_res.pData) + { + gwbuf_append(m_res.pData, pData); + } + else + { + m_res.pData = pData; + } + + if (m_state != CACHE_IGNORING_RESPONSE) + { + if (gwbuf_length(m_res.pData) > m_pInstance->config.max_resultset_size) + { + if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + { + MXS_NOTICE("Current size %uB of resultset, at least as much " + "as maximum allowed size %uKiB. Not caching.", + gwbuf_length(m_res.pData), + m_pInstance->config.max_resultset_size / 1024); + } + + m_state = CACHE_IGNORING_RESPONSE; + } + } + + switch (m_state) + { + case CACHE_EXPECTING_FIELDS: + rv = handle_expecting_fields(); + break; + + case CACHE_EXPECTING_NOTHING: + rv = handle_expecting_nothing(); + break; + + case CACHE_EXPECTING_RESPONSE: + rv = handle_expecting_response(); + break; + + case CACHE_EXPECTING_ROWS: + rv = handle_expecting_rows(); + break; + + case CACHE_EXPECTING_USE_RESPONSE: + rv = handle_expecting_use_response(); + break; + + case CACHE_IGNORING_RESPONSE: + rv = handle_ignoring_response(); + break; + + default: + MXS_ERROR("Internal cache logic broken, unexpected state: %d", m_state); + ss_dassert(!true); + rv = send_upstream(); + reset_response_state(); + m_state = CACHE_IGNORING_RESPONSE; + } + + return rv; +} + +void SessionCache::diagnostics(DCB* pDcb) +{ + dcb_printf(pDcb, "Hello World from Cache!\n"); +} + +/** + * Called when resultset field information is handled. + */ +int SessionCache::handle_expecting_fields() +{ + ss_dassert(m_state == CACHE_EXPECTING_FIELDS); + ss_dassert(m_res.pData); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(m_res.pData); + + while (!insufficient && (buflen - m_res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MYSQL_HEADER_LEN + 1]; + gwbuf_copy_data(m_res.pData, m_res.offset, MYSQL_HEADER_LEN + 1, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (m_res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xfe: // EOF, the one after the fields. + m_res.offset += packetlen; + m_state = CACHE_EXPECTING_ROWS; + rv = handle_expecting_rows(); + break; + + default: // Field information. + m_res.offset += packetlen; + ++m_res.nFields; + ss_dassert(m_res.nFields <= m_res.nTotalFields); + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when data is received (even if nothing is expected) from the server. + */ +int SessionCache::handle_expecting_nothing() +{ + ss_dassert(m_state == CACHE_EXPECTING_NOTHING); + ss_dassert(m_res.pData); + MXS_ERROR("Received data from the backend althoug we were expecting nothing."); + ss_dassert(!true); + + return send_upstream(); +} + +/** + * Called when a response is received from the server. + */ +int SessionCache::handle_expecting_response() +{ + ss_dassert(m_state == CACHE_EXPECTING_RESPONSE); + ss_dassert(m_res.pData); + + int rv = 1; + + size_t buflen = gwbuf_length(m_res.pData); + + if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. + { + // Reserve enough space to accomodate for the largest length encoded integer, + // which is type field + 8 bytes. + uint8_t header[MYSQL_HEADER_LEN + 1 + 8]; + gwbuf_copy_data(m_res.pData, 0, MYSQL_HEADER_LEN + 1, header); + + switch ((int)MYSQL_GET_COMMAND(header)) + { + case 0x00: // OK + case 0xff: // ERR + store_result(); + + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + break; + + case 0xfb: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + break; + + default: + if (m_res.nTotalFields != 0) + { + // We've seen the header and have figured out how many fields there are. + m_state = CACHE_EXPECTING_FIELDS; + rv = handle_expecting_fields(); + } + else + { + // leint_bytes() returns the length of the int type field + the size of the + // integer. + size_t n_bytes = leint_bytes(&header[4]); + + if (MYSQL_HEADER_LEN + n_bytes <= buflen) + { + // Now we can figure out how many fields there are, but first we + // need to copy some more data. + gwbuf_copy_data(m_res.pData, + MYSQL_HEADER_LEN + 1, n_bytes - 1, &header[MYSQL_HEADER_LEN + 1]); + + m_res.nTotalFields = leint_value(&header[4]); + m_res.offset = MYSQL_HEADER_LEN + n_bytes; + + m_state = CACHE_EXPECTING_FIELDS; + rv = handle_expecting_fields(); + } + else + { + // We need more data. We will be called again, when data is available. + } + } + break; + } + } + + return rv; +} + +/** + * Called when resultset rows are handled. + */ +int SessionCache::handle_expecting_rows() +{ + ss_dassert(m_state == CACHE_EXPECTING_ROWS); + ss_dassert(m_res.pData); + + int rv = 1; + + bool insufficient = false; + + size_t buflen = gwbuf_length(m_res.pData); + + while (!insufficient && (buflen - m_res.offset >= MYSQL_HEADER_LEN)) + { + uint8_t header[MYSQL_HEADER_LEN + 1]; + gwbuf_copy_data(m_res.pData, m_res.offset, MYSQL_HEADER_LEN + 1, header); + + size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); + + if (m_res.offset + packetlen <= buflen) + { + // We have at least one complete packet. + int command = (int)MYSQL_GET_COMMAND(header); + + switch (command) + { + case 0xfe: // EOF, the one after the rows. + m_res.offset += packetlen; + ss_dassert(m_res.offset == buflen); + + store_result(); + + rv = send_upstream(); + m_state = CACHE_EXPECTING_NOTHING; + break; + + case 0xfb: // NULL + default: // length-encoded-string + m_res.offset += packetlen; + ++m_res.nRows; + + if (m_res.nRows > m_pInstance->config.max_resultset_rows) + { + if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + { + MXS_NOTICE("Max rows %lu reached, not caching result.", m_res.nRows); + } + rv = send_upstream(); + m_res.offset = buflen; // To abort the loop. + m_state = CACHE_IGNORING_RESPONSE; + } + break; + } + } + else + { + // We need more data + insufficient = true; + } + } + + return rv; +} + +/** + * Called when a response to a "USE db" is received from the server. + */ +int SessionCache::handle_expecting_use_response() +{ + ss_dassert(m_state == CACHE_EXPECTING_USE_RESPONSE); + ss_dassert(m_res.pData); + + int rv = 1; + + size_t buflen = gwbuf_length(m_res.pData); + + if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. + { + uint8_t command; + + gwbuf_copy_data(m_res.pData, MYSQL_HEADER_LEN, 1, &command); + + switch (command) + { + case 0x00: // OK + // In case m_zUseDb could not be allocated in routeQuery(), we will + // in fact reset the default db here. That's ok as it will prevent broken + // entries in the cache. + MXS_FREE(m_zDefaultDb); + m_zDefaultDb = m_zUseDb; + m_zUseDb = NULL; + break; + + case 0xff: // ERR + MXS_FREE(m_zUseDb); + m_zUseDb = NULL; + break; + + default: + MXS_ERROR("\"USE %s\" received unexpected server response %d.", + m_zUseDb ? m_zUseDb : "", command); + MXS_FREE(m_zDefaultDb); + MXS_FREE(m_zUseDb); + m_zDefaultDb = NULL; + m_zUseDb = NULL; + } + + rv = send_upstream(); + m_state = CACHE_IGNORING_RESPONSE; + } + + return rv; +} + +/** + * Called when all data from the server is ignored. + */ +int SessionCache::handle_ignoring_response() +{ + ss_dassert(m_state == CACHE_IGNORING_RESPONSE); + ss_dassert(m_res.pData); + + return send_upstream(); +} + +/** + * Send data upstream. + * + * @return Whatever the upstream returns. + */ +int SessionCache::send_upstream() +{ + ss_dassert(m_res.pData != NULL); + + int rv = m_up.clientReply(m_up.instance, m_up.session, m_res.pData); + m_res.pData = NULL; + + return rv; +} + +/** + * Reset cache response state + */ +void SessionCache::reset_response_state() +{ + m_res.pData = NULL; + m_res.nTotalFields = 0; + m_res.nFields = 0; + m_res.nRows = 0; + m_res.offset = 0; +} + +/** + * Route a query via the cache. + * + * @param key A SELECT packet. + * @param value The result. + * @return True if the query was satisfied from the query. + */ +cache_result_t SessionCache::get_cached_response(const GWBUF *pQuery, GWBUF **ppResponse) +{ + cache_result_t result = m_pStorage->getKey(m_zDefaultDb, pQuery, m_key); + + if (result == CACHE_RESULT_OK) + { + uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; + + result = m_pStorage->getValue(m_key, flags, ppResponse); + } + else + { + MXS_ERROR("Could not create cache key."); + } + + return result; +} + +/** + * Store the data. + * + * @param csdata Session data + */ +void SessionCache::store_result() +{ + ss_dassert(m_res.pData); + + GWBUF *pData = gwbuf_make_contiguous(m_res.pData); + + if (pData) + { + m_res.pData = pData; + + cache_result_t result = m_pStorage->putValue(m_key, m_res.pData); + + if (result != CACHE_RESULT_OK) + { + MXS_ERROR("Could not store cache item, deleting it."); + + result = m_pStorage->delValue(m_key); + + if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) + { + MXS_ERROR("Could not delete cache item."); + } + } + } + + if (m_refreshing) + { + long key = hash_of_key(m_key); + + spinlock_acquire(&m_pInstance->pending_lock); + ss_dassert(hashtable_fetch(m_pInstance->pending, (void*)key) == DUMMY_VALUE); + ss_debug(int n =) hashtable_delete(m_pInstance->pending, (void*)key); + ss_dassert(n == 1); + spinlock_release(&m_pInstance->pending_lock); + + m_refreshing = false; + } +} diff --git a/server/modules/filter/cache/sessioncache.h b/server/modules/filter/cache/sessioncache.h new file mode 100644 index 000000000..67e785736 --- /dev/null +++ b/server/modules/filter/cache/sessioncache.h @@ -0,0 +1,140 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include +#include +#include "cachefilter.h" +#include "cache_storage_api.h" + +class SessionCache +{ +public: + enum cache_session_state_t + { + CACHE_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. + CACHE_EXPECTING_FIELDS, // A select has been sent, and we want more fields. + CACHE_EXPECTING_ROWS, // A select has been sent, and we want more rows. + CACHE_EXPECTING_NOTHING, // We are not expecting anything from the server. + CACHE_EXPECTING_USE_RESPONSE, // A "USE DB" was issued. + CACHE_IGNORING_RESPONSE, // We are not interested in the data received from the server. + }; + + struct CACHE_RESPONSE_STATE + { + GWBUF* pData; /**< Response data, possibly incomplete. */ + size_t nTotalFields; /**< The number of fields a resultset contains. */ + size_t nFields; /**< How many fields we have received, <= n_totalfields. */ + size_t nRows; /**< How many rows we have received. */ + size_t offset; /**< Where we are in the response buffer. */ + }; + + /** + * Releases all resources held by the session cache. + */ + ~SessionCache(); + + /** + * Creates a SessionCache instance. + * + * @param pInstance Pointer to the cache instance to which this session cache + * belongs. Must remain valid for the lifetime of the SessionCache + * instance being created. + * @param pSession Pointer to the session this session cache instance is + * specific for. Must remain valid for the lifetime of the SessionCache + * instance being created. + * + * @return A new instance or NULL if memory allocation fails. + */ + static SessionCache* Create(CACHE_INSTANCE* pInstance, SESSION* pSession); + + /** + * The session has been closed. + */ + void close(); + + /** + * Set the downstream component for this session. + * + * @param pDown The downstream filter or router + */ + void setDownstream(DOWNSTREAM* pDownstream); + + /** + * Set the upstream component for this session. + * + * @param pUp The upstream filter or router + */ + void setUpstream(UPSTREAM* pUpstream); + + /** + * A request on its way to a backend is delivered to this function. + * + * @param pPacket Buffer containing an MySQL protocol packet. + */ + int routeQuery(GWBUF* pPacket); + + /** + * A response on its way to the client is delivered to this function. + * + * @param pData Response data. + */ + int clientReply(GWBUF* pPacket); + + /** + * Print diagnostics of the session cache. + */ + void diagnostics(DCB *dcb); + +private: + int handle_expecting_fields(); + int handle_expecting_nothing(); + int handle_expecting_response(); + int handle_expecting_rows(); + int handle_expecting_use_response(); + int handle_ignoring_response(); + + int send_upstream(); + + void reset_response_state(); + + cache_result_t get_cached_response(const GWBUF *pQuery, GWBUF **ppResponse); + + bool log_decisions() const + { + return m_pInstance->config.debug & CACHE_DEBUG_DECISIONS ? true : false; + } + + void store_result(); + +private: + SessionCache(CACHE_INSTANCE* pInstance, SESSION* pSession, char* zDefaultDb); + + SessionCache(const SessionCache&); + SessionCache& operator = (const SessionCache&); + +private: + cache_session_state_t m_state; /**< What state is the session in, what data is expected. */ + CACHE_INSTANCE* m_pInstance; /**< The cache instance the session is associated with. */ + SESSION* m_pSession; /**< The session this data is associated with. */ + Storage* m_pStorage; /**< The storage to be used with this session data. */ + DOWNSTREAM m_down; /**< The previous filter or equivalent. */ + UPSTREAM m_up; /**< The next filter or equivalent. */ + CACHE_RESPONSE_STATE m_res; /**< The response state. */ + char m_key[CACHE_KEY_MAXLEN]; /**< Key storage. */ + char* m_zDefaultDb; /**< The default database. */ + char* m_zUseDb; /**< Pending default database. Needs server response. */ + bool m_refreshing; /**< Whether the session is updating a stale cache entry. */ +}; + From 5a6c4b5970e6a8e093e7dfaa97a98862e3aa7650 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 15:13:54 +0200 Subject: [PATCH 38/48] Cache: Take SessionCache into use --- server/modules/filter/cache/cachefilter.cc | 765 +----------------- server/modules/filter/cache/cachefilter.h | 1 + server/modules/filter/cache/storage.cc | 1 + server/modules/filter/cache/storagefactory.cc | 1 + 4 files changed, 27 insertions(+), 741 deletions(-) diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 185e66c2a..356186c79 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -16,14 +16,9 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include #include "rules.h" +#include "sessioncache.h" #include "storage.h" #include "storagefactory.h" @@ -107,57 +102,7 @@ static const CACHE_CONFIG DEFAULT_CONFIG = CACHE_DEFAULT_DEBUG }; -typedef enum cache_session_state -{ - CACHE_EXPECTING_RESPONSE, // A select has been sent, and we are waiting for the response. - CACHE_EXPECTING_FIELDS, // A select has been sent, and we want more fields. - CACHE_EXPECTING_ROWS, // A select has been sent, and we want more rows. - CACHE_EXPECTING_NOTHING, // We are not expecting anything from the server. - CACHE_EXPECTING_USE_RESPONSE, // A "USE DB" was issued. - CACHE_IGNORING_RESPONSE, // We are not interested in the data received from the server. -} cache_session_state_t; - -typedef struct cache_response_state -{ - GWBUF* data; /**< Response data, possibly incomplete. */ - size_t n_totalfields; /**< The number of fields a resultset contains. */ - size_t n_fields; /**< How many fields we have received, <= n_totalfields. */ - size_t n_rows; /**< How many rows we have received. */ - size_t offset; /**< Where we are in the response buffer. */ -} CACHE_RESPONSE_STATE; - -static void cache_response_state_reset(CACHE_RESPONSE_STATE *state); - -typedef struct cache_session_data -{ - CACHE_INSTANCE *instance; /**< The cache instance the session is associated with. */ - Storage *storage; /**< The storage to be used with this session data. */ - DOWNSTREAM down; /**< The previous filter or equivalent. */ - UPSTREAM up; /**< The next filter or equivalent. */ - CACHE_RESPONSE_STATE res; /**< The response state. */ - SESSION *session; /**< The session this data is associated with. */ - char key[CACHE_KEY_MAXLEN]; /**< Key storage. */ - char *default_db; /**< The default database. */ - char *use_db; /**< Pending default database. Needs server response. */ - cache_session_state_t state; /**< What state is the session in, what data is expected. */ - bool refreshing; /**< Whether the session is updating a stale cache entry. */ -} CACHE_SESSION_DATA; - -static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, SESSION *session); -static void cache_session_data_free(CACHE_SESSION_DATA *data); - -static int handle_expecting_fields(CACHE_SESSION_DATA *csdata); -static int handle_expecting_nothing(CACHE_SESSION_DATA *csdata); -static int handle_expecting_response(CACHE_SESSION_DATA *csdata); -static int handle_expecting_rows(CACHE_SESSION_DATA *csdata); -static int handle_expecting_use_response(CACHE_SESSION_DATA *csdata); -static int handle_ignoring_response(CACHE_SESSION_DATA *csdata); static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); -static cache_result_t get_cached_response(CACHE_SESSION_DATA *sdata, const GWBUF *key, GWBUF **value); - -static int send_upstream(CACHE_SESSION_DATA *csdata); - -static void store_result(CACHE_SESSION_DATA *csdata); /** * Hashes a cache key to an integer. @@ -194,17 +139,10 @@ static int hashcmp(const void* address1, const void* address2) return (long)address2 - (long)address1; } -#define DUMMY_VALUE (void*)0xdeadbeef - // Initial size of hashtable used for storing keys of queries that // are being fetches. #define CACHE_PENDING_ITEMS 50 -static inline bool log_decisions(const CACHE_SESSION_DATA* csdata) -{ - return csdata->instance->config.debug & CACHE_DEBUG_DECISIONS ? true : false; -} - // // API BEGIN // @@ -308,9 +246,10 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER static void *newSession(FILTER *instance, SESSION *session) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = cache_session_data_create(cinstance, session); - return csdata; + SessionCache *pSessionCache = SessionCache::Create(cinstance, session); + + return pSessionCache; } /** @@ -322,7 +261,10 @@ static void *newSession(FILTER *instance, SESSION *session) static void closeSession(FILTER *instance, void *sdata) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; + + SessionCache* pSessionCache = static_cast(sdata); + + pSessionCache->close(); } /** @@ -334,9 +276,10 @@ static void closeSession(FILTER *instance, void *sdata) static void freeSession(FILTER *instance, void *sdata) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - cache_session_data_free(csdata); + SessionCache* pSessionCache = static_cast(sdata); + + delete pSessionCache; } /** @@ -349,9 +292,10 @@ static void freeSession(FILTER *instance, void *sdata) static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *down) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - csdata->down = *down; + SessionCache* pSessionCache = static_cast(sdata); + + pSessionCache->setDownstream(down); } /** @@ -364,9 +308,10 @@ static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *down) static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *up) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - csdata->up = *up; + SessionCache* pSessionCache = static_cast(sdata); + + pSessionCache->setUpstream(up); } /** @@ -379,172 +324,10 @@ static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *up) static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - uint8_t *data = (uint8_t*)GWBUF_DATA(packet); + SessionCache* pSessionCache = static_cast(sdata); - // All of these should be guaranteed by RCAP_TYPE_TRANSACTION_TRACKING - ss_dassert(GWBUF_IS_CONTIGUOUS(packet)); - ss_dassert(GWBUF_LENGTH(packet) >= MYSQL_HEADER_LEN + 1); - ss_dassert(MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN == GWBUF_LENGTH(packet)); - - bool fetch_from_server = true; - - cache_response_state_reset(&csdata->res); - csdata->state = CACHE_IGNORING_RESPONSE; - - int rv; - - switch ((int)MYSQL_GET_COMMAND(data)) - { - case MYSQL_COM_INIT_DB: - { - ss_dassert(!csdata->use_db); - size_t len = MYSQL_GET_PACKET_LEN(data) - 1; // Remove the command byte. - csdata->use_db = (char*)MXS_MALLOC(len + 1); - - if (csdata->use_db) - { - memcpy(csdata->use_db, data + MYSQL_HEADER_LEN + 1, len); - csdata->use_db[len] = 0; - csdata->state = CACHE_EXPECTING_USE_RESPONSE; - } - else - { - // Memory allocation failed. We need to remove the default database to - // prevent incorrect cache entries, since we won't know what the - // default db is. But we only need to do that if "USE " really - // succeeds. The right thing will happen by itself in - // handle_expecting_use_response(); if OK is returned, default_db will - // become NULL, if ERR, default_db will not be changed. - } - } - break; - - case MYSQL_COM_QUERY: - { - // We do not care whether the query was fully parsed or not. - // If a query cannot be fully parsed, the worst thing that can - // happen is that caching is not used, even though it would be - // possible. - if (qc_get_operation(packet) == QUERY_OP_SELECT) - { - SESSION *session = csdata->session; - - if ((session_is_autocommit(session) && !session_trx_is_active(session)) || - session_trx_is_read_only(session)) - { - if (cache_rules_should_store(cinstance->rules, csdata->default_db, packet)) - { - if (cache_rules_should_use(cinstance->rules, csdata->session)) - { - GWBUF *response; - cache_result_t result = get_cached_response(csdata, packet, &response); - - switch (result) - { - case CACHE_RESULT_STALE: - { - // The value was found, but it was stale. Now we need to - // figure out whether somebody else is already fetching it. - - long key = hash_of_key(csdata->key); - - spinlock_acquire(&cinstance->pending_lock); - // TODO: Remove the internal locking of hashtable. The internal - // TODO: locking is no good if you need transactional behaviour. - // TODO: Now we lock twice. - void *value = hashtable_fetch(cinstance->pending, (void*)key); - if (!value) - { - // It's not being fetched, so we make a note that we are. - hashtable_add(cinstance->pending, (void*)key, DUMMY_VALUE); - } - spinlock_release(&cinstance->pending_lock); - - if (!value) - { - // We were the first ones who hit the stale item. It's - // our responsibility now to fetch it. - if (log_decisions(csdata)) - { - MXS_NOTICE("Cache data is stale, fetching fresh from server."); - } - csdata->refreshing = true; - fetch_from_server = true; - break; - } - else - { - // Somebody is already fetching the new value. So, let's - // use the stale value. No point in hitting the server twice. - if (log_decisions(csdata)) - { - MXS_NOTICE("Cache data is stale but returning it, fresh " - "data is being fetched already."); - } - fetch_from_server = false; - } - } - break; - - case CACHE_RESULT_OK: - if (log_decisions(csdata)) - { - MXS_NOTICE("Using fresh data from cache."); - } - fetch_from_server = false; - break; - - default: - fetch_from_server = true; - } - - if (fetch_from_server) - { - csdata->state = CACHE_EXPECTING_RESPONSE; - } - else - { - csdata->state = CACHE_EXPECTING_NOTHING; - gwbuf_free(packet); - DCB *dcb = csdata->session->client_dcb; - - // TODO: This is not ok. Any filters before this filter, will not - // TODO: see this data. - rv = dcb->func.write(dcb, response); - } - } - } - else - { - csdata->state = CACHE_IGNORING_RESPONSE; - } - } - else - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " - "storing to cache.", - session_is_autocommit(csdata->session) ? "ON" : "OFF", - session_trx_state_to_string(session_get_trx_state(csdata->session))); - } - } - } - break; - - default: - break; - } - } - - if (fetch_from_server) - { - rv = csdata->down.routeQuery(csdata->down.instance, csdata->down.session, packet); - } - - return rv; + return pSessionCache->routeQuery(packet); } /** @@ -557,70 +340,10 @@ static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) static int clientReply(FILTER *instance, void *sdata, GWBUF *data) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - int rv; + SessionCache* pSessionCache = static_cast(sdata); - if (csdata->res.data) - { - gwbuf_append(csdata->res.data, data); - } - else - { - csdata->res.data = data; - } - - if (csdata->state != CACHE_IGNORING_RESPONSE) - { - if (gwbuf_length(csdata->res.data) > csdata->instance->config.max_resultset_size) - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Current size %uB of resultset, at least as much " - "as maximum allowed size %uKiB. Not caching.", - gwbuf_length(csdata->res.data), - csdata->instance->config.max_resultset_size / 1024); - } - - csdata->state = CACHE_IGNORING_RESPONSE; - } - } - - switch (csdata->state) - { - case CACHE_EXPECTING_FIELDS: - rv = handle_expecting_fields(csdata); - break; - - case CACHE_EXPECTING_NOTHING: - rv = handle_expecting_nothing(csdata); - break; - - case CACHE_EXPECTING_RESPONSE: - rv = handle_expecting_response(csdata); - break; - - case CACHE_EXPECTING_ROWS: - rv = handle_expecting_rows(csdata); - break; - - case CACHE_EXPECTING_USE_RESPONSE: - rv = handle_expecting_use_response(csdata); - break; - - case CACHE_IGNORING_RESPONSE: - rv = handle_ignoring_response(csdata); - break; - - default: - MXS_ERROR("Internal cache logic broken, unexpected state: %d", csdata->state); - ss_dassert(!true); - rv = send_upstream(csdata); - cache_response_state_reset(&csdata->res); - csdata->state = CACHE_IGNORING_RESPONSE; - } - - return rv; + return pSessionCache->clientReply(data); } /** @@ -636,9 +359,10 @@ static int clientReply(FILTER *instance, void *sdata, GWBUF *data) static void diagnostics(FILTER *instance, void *sdata, DCB *dcb) { CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - CACHE_SESSION_DATA *csdata = (CACHE_SESSION_DATA*)sdata; - dcb_printf(dcb, "Hello World from Cache!\n"); + SessionCache* pSessionCache = static_cast(sdata); + + pSessionCache->diagnostics(dcb); } @@ -656,356 +380,6 @@ static uint64_t getCapabilities(void) // API END // -/** - * Reset cache response state - * - * @param state Pointer to object. - */ -static void cache_response_state_reset(CACHE_RESPONSE_STATE *state) -{ - state->data = NULL; - state->n_totalfields = 0; - state->n_fields = 0; - state->n_rows = 0; - state->offset = 0; -} - -/** - * Create cache session data - * - * @param instance The cache instance this data is associated with. - * - * @return Session data or NULL if creation fails. - */ -static CACHE_SESSION_DATA *cache_session_data_create(CACHE_INSTANCE *instance, - SESSION* session) -{ - CACHE_SESSION_DATA *data = (CACHE_SESSION_DATA*)MXS_CALLOC(1, sizeof(CACHE_SESSION_DATA)); - - if (data) - { - char *default_db = NULL; - - ss_dassert(session->client_dcb); - ss_dassert(session->client_dcb->data); - MYSQL_session *mysql_session = (MYSQL_session*)session->client_dcb->data; - - if (mysql_session->db[0] != 0) - { - default_db = MXS_STRDUP(mysql_session->db); - } - - if ((mysql_session->db[0] == 0) || default_db) - { - data->instance = instance; - data->storage = instance->storage; - data->session = session; - data->state = CACHE_EXPECTING_NOTHING; - data->default_db = default_db; - } - else - { - MXS_FREE(data); - data = NULL; - } - } - - return data; -} - -/** - * Free cache session data. - * - * @param A cache session data previously allocated using session_data_create(). - */ -static void cache_session_data_free(CACHE_SESSION_DATA* data) -{ - if (data) - { - // In normal circumstances, only data->default_db may be non-NULL at - // this point. However, if the authentication with the backend fails - // and the session is closed, data->use_db may be non-NULL. - MXS_FREE(data->use_db); - MXS_FREE(data->default_db); - MXS_FREE(data); - } -} - -/** - * Called when resultset field information is handled. - * - * @param csdata The cache session data. - */ -static int handle_expecting_fields(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_FIELDS); - ss_dassert(csdata->res.data); - - int rv = 1; - - bool insufficient = false; - - size_t buflen = gwbuf_length(csdata->res.data); - - while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) - { - uint8_t header[MYSQL_HEADER_LEN + 1]; - gwbuf_copy_data(csdata->res.data, csdata->res.offset, MYSQL_HEADER_LEN + 1, header); - - size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); - - if (csdata->res.offset + packetlen <= buflen) - { - // We have at least one complete packet. - int command = (int)MYSQL_GET_COMMAND(header); - - switch (command) - { - case 0xfe: // EOF, the one after the fields. - csdata->res.offset += packetlen; - csdata->state = CACHE_EXPECTING_ROWS; - rv = handle_expecting_rows(csdata); - break; - - default: // Field information. - csdata->res.offset += packetlen; - ++csdata->res.n_fields; - ss_dassert(csdata->res.n_fields <= csdata->res.n_totalfields); - break; - } - } - else - { - // We need more data - insufficient = true; - } - } - - return rv; -} - -/** - * Called when data is received (even if nothing is expected) from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_nothing(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_NOTHING); - ss_dassert(csdata->res.data); - MXS_ERROR("Received data from the backend althoug we were expecting nothing."); - ss_dassert(!true); - - return send_upstream(csdata); -} - -/** - * Called when a response is received from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_RESPONSE); - ss_dassert(csdata->res.data); - - int rv = 1; - - size_t buflen = gwbuf_length(csdata->res.data); - - if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. - { - // Reserve enough space to accomodate for the largest length encoded integer, - // which is type field + 8 bytes. - uint8_t header[MYSQL_HEADER_LEN + 1 + 8]; - gwbuf_copy_data(csdata->res.data, 0, MYSQL_HEADER_LEN + 1, header); - - switch ((int)MYSQL_GET_COMMAND(header)) - { - case 0x00: // OK - case 0xff: // ERR - store_result(csdata); - - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - break; - - case 0xfb: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - break; - - default: - if (csdata->res.n_totalfields != 0) - { - // We've seen the header and have figured out how many fields there are. - csdata->state = CACHE_EXPECTING_FIELDS; - rv = handle_expecting_fields(csdata); - } - else - { - // leint_bytes() returns the length of the int type field + the size of the - // integer. - size_t n_bytes = leint_bytes(&header[4]); - - if (MYSQL_HEADER_LEN + n_bytes <= buflen) - { - // Now we can figure out how many fields there are, but first we - // need to copy some more data. - gwbuf_copy_data(csdata->res.data, - MYSQL_HEADER_LEN + 1, n_bytes - 1, &header[MYSQL_HEADER_LEN + 1]); - - csdata->res.n_totalfields = leint_value(&header[4]); - csdata->res.offset = MYSQL_HEADER_LEN + n_bytes; - - csdata->state = CACHE_EXPECTING_FIELDS; - rv = handle_expecting_fields(csdata); - } - else - { - // We need more data. We will be called again, when data is available. - } - } - break; - } - } - - return rv; -} - -/** - * Called when resultset rows are handled. - * - * @param csdata The cache session data. - */ -static int handle_expecting_rows(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_ROWS); - ss_dassert(csdata->res.data); - - int rv = 1; - - bool insufficient = false; - - size_t buflen = gwbuf_length(csdata->res.data); - - while (!insufficient && (buflen - csdata->res.offset >= MYSQL_HEADER_LEN)) - { - uint8_t header[MYSQL_HEADER_LEN + 1]; - gwbuf_copy_data(csdata->res.data, csdata->res.offset, MYSQL_HEADER_LEN + 1, header); - - size_t packetlen = MYSQL_HEADER_LEN + MYSQL_GET_PACKET_LEN(header); - - if (csdata->res.offset + packetlen <= buflen) - { - // We have at least one complete packet. - int command = (int)MYSQL_GET_COMMAND(header); - - switch (command) - { - case 0xfe: // EOF, the one after the rows. - csdata->res.offset += packetlen; - ss_dassert(csdata->res.offset == buflen); - - store_result(csdata); - - rv = send_upstream(csdata); - csdata->state = CACHE_EXPECTING_NOTHING; - break; - - case 0xfb: // NULL - default: // length-encoded-string - csdata->res.offset += packetlen; - ++csdata->res.n_rows; - - if (csdata->res.n_rows > csdata->instance->config.max_resultset_rows) - { - if (csdata->instance->config.debug & CACHE_DEBUG_DECISIONS) - { - MXS_NOTICE("Max rows %lu reached, not caching result.", csdata->res.n_rows); - } - rv = send_upstream(csdata); - csdata->res.offset = buflen; // To abort the loop. - csdata->state = CACHE_IGNORING_RESPONSE; - } - break; - } - } - else - { - // We need more data - insufficient = true; - } - } - - return rv; -} - -/** - * Called when a response to a "USE db" is received from the server. - * - * @param csdata The cache session data. - */ -static int handle_expecting_use_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_EXPECTING_USE_RESPONSE); - ss_dassert(csdata->res.data); - - int rv = 1; - - size_t buflen = gwbuf_length(csdata->res.data); - - if (buflen >= MYSQL_HEADER_LEN + 1) // We need the command byte. - { - uint8_t command; - - gwbuf_copy_data(csdata->res.data, MYSQL_HEADER_LEN, 1, &command); - - switch (command) - { - case 0x00: // OK - // In case csdata->use_db could not be allocated in routeQuery(), we will - // in fact reset the default db here. That's ok as it will prevent broken - // entries in the cache. - MXS_FREE(csdata->default_db); - csdata->default_db = csdata->use_db; - csdata->use_db = NULL; - break; - - case 0xff: // ERR - MXS_FREE(csdata->use_db); - csdata->use_db = NULL; - break; - - default: - MXS_ERROR("\"USE %s\" received unexpected server response %d.", - csdata->use_db ? csdata->use_db : "", command); - MXS_FREE(csdata->default_db); - MXS_FREE(csdata->use_db); - csdata->default_db = NULL; - csdata->use_db = NULL; - } - - rv = send_upstream(csdata); - csdata->state = CACHE_IGNORING_RESPONSE; - } - - return rv; -} - -/** - * Called when all data from the server is ignored. - * - * @param csdata The cache session data. - */ -static int handle_ignoring_response(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->state == CACHE_IGNORING_RESPONSE); - ss_dassert(csdata->res.data); - - return send_upstream(csdata); -} - /** * Processes the cache params * @@ -1164,94 +538,3 @@ static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONF return !error; } - -/** - * Route a query via the cache. - * - * @param csdata Session data - * @param key A SELECT packet. - * @param value The result. - * @return True if the query was satisfied from the query. - */ -static cache_result_t get_cached_response(CACHE_SESSION_DATA *csdata, - const GWBUF *query, - GWBUF **value) -{ - cache_result_t result = csdata->storage->getKey(csdata->default_db, query, csdata->key); - - if (result == CACHE_RESULT_OK) - { - uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; - - result = csdata->storage->getValue(csdata->key, flags, value); - } - else - { - MXS_ERROR("Could not create cache key."); - } - - return result; -} - -/** - * Send data upstream. - * - * @param csdata Session data - * - * @return Whatever the upstream returns. - */ -static int send_upstream(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->res.data != NULL); - - int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, csdata->res.data); - csdata->res.data = NULL; - - return rv; -} - -/** - * Store the data. - * - * @param csdata Session data - */ -static void store_result(CACHE_SESSION_DATA *csdata) -{ - ss_dassert(csdata->res.data); - - GWBUF *data = gwbuf_make_contiguous(csdata->res.data); - - if (data) - { - csdata->res.data = data; - - cache_result_t result = csdata->storage->putValue(csdata->key, csdata->res.data); - - if (result != CACHE_RESULT_OK) - { - MXS_ERROR("Could not store cache item, deleting it."); - - result = csdata->storage->delValue(csdata->key); - - if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) - { - MXS_ERROR("Could not delete cache item."); - } - } - } - - if (csdata->refreshing) - { - long key = hash_of_key(csdata->key); - - CACHE_INSTANCE *instance = csdata->instance; - - spinlock_acquire(&instance->pending_lock); - ss_dassert(hashtable_fetch(instance->pending, (void*)key) == DUMMY_VALUE); - ss_debug(int n =) hashtable_delete(instance->pending, (void*)key); - ss_dassert(n == 1); - spinlock_release(&instance->pending_lock); - - csdata->refreshing = false; - } -} diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index e76a63ccc..d03cdd8ed 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "rules.h" class Storage; diff --git a/server/modules/filter/cache/storage.cc b/server/modules/filter/cache/storage.cc index 2d724866a..c50084fed 100644 --- a/server/modules/filter/cache/storage.cc +++ b/server/modules/filter/cache/storage.cc @@ -11,6 +11,7 @@ * Public License. */ +#define MXS_MODULE_NAME "cache" #include "storage.h" diff --git a/server/modules/filter/cache/storagefactory.cc b/server/modules/filter/cache/storagefactory.cc index 9b088da76..09e2f984a 100644 --- a/server/modules/filter/cache/storagefactory.cc +++ b/server/modules/filter/cache/storagefactory.cc @@ -11,6 +11,7 @@ * Public License. */ +#define MXS_MODULE_NAME "cache" #include "storagefactory.h" #include #include From 719cffd596be47ac33abaf31f1f5dfb0619d79ff Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 16:12:53 +0200 Subject: [PATCH 39/48] Cache: Add Cache class. This class provides the cache function used by the session caches. Not taken into use yet. --- server/modules/filter/cache/CMakeLists.txt | 2 +- server/modules/filter/cache/cache.cc | 359 +++++++++++++++++++++ server/modules/filter/cache/cache.h | 98 ++++++ 3 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 server/modules/filter/cache/cache.cc create mode 100644 server/modules/filter/cache/cache.h diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index ff340d9e8..2385faf8e 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cachefilter.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) + add_library(cache SHARED cache.cc cachefilter.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc new file mode 100644 index 000000000..16bc27a94 --- /dev/null +++ b/server/modules/filter/cache/cache.cc @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#define MXS_MODULE_NAME "cache" +#include "cache.h" +#include +#include +#include +#include "storagefactory.h" +#include "storage.h" + +// Initial size of hashtable used for storing keys of queries that +// are being fetches. +#define CACHE_PENDING_ITEMS 50 + + +static int hashfn(const void* address) +{ + // TODO: Hash the address; pointers are not evenly distributed. + return (long)address; +} + +static int hashcmp(const void* address1, const void* address2) +{ + return (long)address2 - (long)address1; +} + + +Cache::Cache(const char* zName, + const CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending) + : m_zName(zName) + , m_config(config) + , m_pRules(pRules) + , m_pFactory(pFactory) + , m_pStorage(pStorage) + , m_pPending(pPending) +{ +} + +Cache::~Cache() +{ + // TODO: Free everything. + ss_dassert(false); +} + +//static +Cache* Cache::Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) +{ + Cache* pCache = NULL; + + CACHE_CONFIG config; + memset(&config, 0, sizeof(config)); + + if (process_params(pzOptions, ppParams, &config)) + { + CACHE_RULES* pRules = NULL; + + if (config.rules) + { + pRules = cache_rules_load(config.rules, config.debug); + } + else + { + pRules = cache_rules_create(config.debug); + } + + if (pRules) + { + HASHTABLE* pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); + + if (pPending) + { + StorageFactory *pFactory = StorageFactory::Open(config.storage); + + if (pFactory) + { + uint32_t ttl = config.ttl; + int argc = config.storage_argc; + char** argv = config.storage_argv; + + Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); + + if (pStorage) + { + pCache = new (std::nothrow) Cache(zName, + config, + pRules, + pFactory, + pStorage, + pPending); + } + else + { + MXS_ERROR("Could not create storage instance for '%s'.", zName); + } + } + else + { + MXS_ERROR("Could not open storage factory '%s'.", config.storage); + } + + if (!pCache) + { + delete pFactory; + } + } + + if (!pCache) + { + hashtable_free(pPending); + } + } + + if (!pCache) + { + cache_rules_free(pRules); + } + } + + return pCache; +} + +bool Cache::shouldStore(const char* zDefaultDb, const GWBUF* pQuery) +{ + return cache_rules_should_store(m_pRules, zDefaultDb, pQuery); +} + +bool Cache::shouldUse(const SESSION* pSession) +{ + return cache_rules_should_use(m_pRules, pSession); +} + +bool Cache::mustRefresh(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hash_of_key(pKey); + + spinlock_acquire(&m_lockPending); + // TODO: Remove the internal locking of hashtable. The internal + // TODO: locking is no good if you need transactional behaviour. + // TODO: Now we lock twice. + void *pValue = hashtable_fetch(m_pPending, (void*)pKey); + if (!pValue) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(m_pPending, (void*)pKey, (void*)pSessionCache); + } + spinlock_release(&m_lockPending); + + return pValue == NULL; +} + +void Cache::refreshed(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hash_of_key(pKey); + + spinlock_acquire(&m_lockPending); + ss_dassert(hashtable_fetch(m_pPending, (void*)pKey) == pSessionCache); + ss_debug(int n =) hashtable_delete(m_pPending, (void*)pKey); + ss_dassert(n == 1); + spinlock_release(&m_lockPending); +} + +cache_result_t Cache::getKey(const char* zDefaultDb, + const GWBUF* pQuery, + char* pKey) +{ + return m_pStorage->getKey(zDefaultDb, pQuery, pKey); +} + +cache_result_t Cache::getValue(const char* pKey, + uint32_t flags, + GWBUF** ppValue) +{ + return m_pStorage->getValue(pKey, flags, ppValue); +} + +cache_result_t Cache::putValue(const char* pKey, + const GWBUF* pValue) +{ + return m_pStorage->putValue(pKey, pValue); +} + +cache_result_t Cache::delValue(const char* pKey) +{ + return m_pStorage->delValue(pKey); +} + +/** + * Processes the cache params + * + * @param options Options as passed to the filter. + * @param params Parameters as passed to the filter. + * @param config Pointer to config instance where params will be stored. + * + * @return True if all parameters could be processed, false otherwise. + */ +bool Cache::process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG* pConfig) +{ + bool error = false; + + for (int i = 0; ppParams[i]; ++i) + { + const FILTER_PARAMETER *pParam = ppParams[i]; + + if (strcmp(pParam->name, "max_resultset_rows") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + pConfig->max_resultset_rows = v; + } + else + { + pConfig->max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; + } + } + else if (strcmp(pParam->name, "max_resultset_size") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + pConfig->max_resultset_size = v * 1024; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "rules") == 0) + { + if (*pParam->value == '/') + { + pConfig->rules = MXS_STRDUP(pParam->value); + } + else + { + const char *datadir = get_datadir(); + size_t len = strlen(datadir) + 1 + strlen(pParam->value) + 1; + + char *rules = (char*)MXS_MALLOC(len); + + if (rules) + { + sprintf(rules, "%s/%s", datadir, pParam->value); + pConfig->rules = rules; + } + } + + if (!pConfig->rules) + { + error = true; + } + } + else if (strcmp(pParam->name, "storage_options") == 0) + { + pConfig->storage_options = MXS_STRDUP(pParam->value); + + if (pConfig->storage_options) + { + int argc = 1; + char *arg = pConfig->storage_options; + + while ((arg = strchr(pConfig->storage_options, ','))) + { + ++argc; + } + + pConfig->storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); + + if (pConfig->storage_argv) + { + pConfig->storage_argc = argc; + + int i = 0; + arg = pConfig->storage_options; + pConfig->storage_argv[i++] = arg; + + while ((arg = strchr(pConfig->storage_options, ','))) + { + *arg = 0; + ++arg; + pConfig->storage_argv[i++] = arg; + } + + pConfig->storage_argv[i] = NULL; + } + else + { + MXS_FREE(pConfig->storage_options); + pConfig->storage_options = NULL; + } + } + else + { + error = true; + } + } + else if (strcmp(pParam->name, "storage") == 0) + { + pConfig->storage = pParam->value; + } + else if (strcmp(pParam->name, "ttl") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + pConfig->ttl = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "debug") == 0) + { + int v = atoi(pParam->value); + + if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) + { + pConfig->debug = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be between %d and %d, inclusive.", + pParam->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); + error = true; + } + } + else if (!filter_standard_parameter(pParam->name)) + { + MXS_ERROR("Unknown configuration entry '%s'.", pParam->name); + error = true; + } + } + + return !error; +} diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h new file mode 100644 index 000000000..191436b06 --- /dev/null +++ b/server/modules/filter/cache/cache.h @@ -0,0 +1,98 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include +#include +#include +#include "cachefilter.h" +#include "cache_storage_api.h" + +class SessionCache; + +class Cache +{ +public: + ~Cache(); + + static Cache* Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParms); + + /** + * Returns whether the results of a particular query should be stored. + * + * @param zDefaultDb The current default database. + * @param pQuery Buffer containing a SELECT. + * + * @return True of the result should be cached. + */ + bool shouldStore(const char* zDefaultDb, const GWBUF* pQuery); + + /** + * Returns whether cached results should be used. + * + * @param pSession The session in question. + * + * @return True of cached results should be used. + */ + bool shouldUse(const SESSION* pSession); + + /** + * Specifies whether a particular SessioCache should refresh the data. + * + * @param pKey The hashed key for a query. + * @param pSessionCache The session cache asking. + * + * @return True, if the session cache should refresh the data. + */ + bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); + + /** + * To inform the cache that a particular item has been updated upon request. + * + * @param pKey The hashed key for a query. + * @param pSessionCache The session cache informing. + */ + void refreshed(const char* pKey, const SessionCache* pSessionCache); + + cache_result_t getKey(const char* zDefaultDb, const GWBUF* pQuery, char* pKey); + + cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppValue); + + cache_result_t putValue(const char* pKey, const GWBUF* pValue); + + cache_result_t delValue(const char* pKey); + +private: + Cache(const char* zName, + const CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending); + + static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); + +private: + Cache(const Cache&); + Cache& operator = (const Cache&); + +private: + const char* m_zName; // The name of the instance; the section name in the config. + CACHE_CONFIG m_config; // The configuration of the cache instance. + CACHE_RULES* m_pRules; // The rules of the cache instance. + StorageFactory* m_pFactory; // The storage factory. + Storage* m_pStorage; // The storage instance to use. + HASHTABLE* m_pPending; // Pending items; being fetched from the backend. + SPINLOCK m_lockPending; // Lock used for protecting 'pending'. +}; From 14c8a5bb5f8c1e06e7123c600cec4dc83fa266a8 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 16:48:49 +0200 Subject: [PATCH 40/48] Cache: Take Cache class into use Now the basic structure is in place: - cachefilter.cc is the MaxScale filter interface. - Cache is the actual cache class that will also handle LRU issues. - SessionCache (sessioncache.cc) is the session specific cache class that using Cache acts as the cache for a particular session. If an item is stale, then one SessionCache will update it. - StorageFactory is the component that is capable of loading a module providing storage facilities. - Storage is the actual key/value store. --- server/modules/filter/cache/cache.cc | 39 +- server/modules/filter/cache/cache.h | 2 + server/modules/filter/cache/cachefilter.cc | 435 +++----------------- server/modules/filter/cache/cachefilter.h | 13 - server/modules/filter/cache/sessioncache.cc | 61 +-- server/modules/filter/cache/sessioncache.h | 14 +- 6 files changed, 127 insertions(+), 437 deletions(-) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc index 16bc27a94..b6e20f4ab 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cache.cc @@ -23,6 +23,42 @@ // are being fetches. #define CACHE_PENDING_ITEMS 50 +static const CACHE_CONFIG DEFAULT_CONFIG = +{ + CACHE_DEFAULT_MAX_RESULTSET_ROWS, + CACHE_DEFAULT_MAX_RESULTSET_SIZE, + NULL, + NULL, + NULL, + NULL, + 0, + CACHE_DEFAULT_TTL, + CACHE_DEFAULT_DEBUG +}; + +/** + * Hashes a cache key to an integer. + * + * @param key Pointer to cache key. + * + * @returns Corresponding integer hash. + */ +static int hash_of_key(const void* key) +{ + int hash = 0; + + const char* i = (const char*)key; + const char* end = i + CACHE_KEY_MAXLEN; + + while (i < end) + { + int c = *i; + hash = c + (hash << 6) + (hash << 16) - hash; + ++i; + } + + return hash; +} static int hashfn(const void* address) { @@ -62,8 +98,7 @@ Cache* Cache::Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppP { Cache* pCache = NULL; - CACHE_CONFIG config; - memset(&config, 0, sizeof(config)); + CACHE_CONFIG config = DEFAULT_CONFIG; if (process_params(pzOptions, ppParams, &config)) { diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index 191436b06..f274ffec6 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -65,6 +65,8 @@ public: */ void refreshed(const char* pKey, const SessionCache* pSessionCache); + const CACHE_CONFIG& config() const { return m_config; } + cache_result_t getKey(const char* zDefaultDb, const GWBUF* pQuery, char* pKey); cache_result_t getValue(const char* pKey, uint32_t flags, GWBUF** ppValue); diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 356186c79..8c4112750 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -12,27 +12,21 @@ */ #define MXS_MODULE_NAME "cache" -#include "cachefilter.h" -#include #include -#include -#include -#include "rules.h" +#include "cache.h" #include "sessioncache.h" -#include "storage.h" -#include "storagefactory.h" static char VERSION_STRING[] = "V1.0.0"; -static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **); -static void *newSession(FILTER *instance, SESSION *session); -static void closeSession(FILTER *instance, void *sdata); -static void freeSession(FILTER *instance, void *sdata); -static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *downstream); -static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *upstream); -static int routeQuery(FILTER *instance, void *sdata, GWBUF *queue); -static int clientReply(FILTER *instance, void *sdata, GWBUF *queue); -static void diagnostics(FILTER *instance, void *sdata, DCB *dcb); +static FILTER* createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams); +static void* newSession(FILTER* pInstance, SESSION* pSession); +static void closeSession(FILTER* pInstance, void* pSessionData); +static void freeSession(FILTER* pInstance, void* pSessionData); +static void setDownstream(FILTER* pInstance, void* pSessionData, DOWNSTREAM* pDownstream); +static void setUpstream(FILTER* pInstance, void* pSessionData, UPSTREAM* pUpstream); +static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket); +static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket); +static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb); static uint64_t getCapabilities(void); // @@ -86,168 +80,38 @@ extern "C" FILTER_OBJECT *GetModuleObject() }; // -// Implementation -// - -static const CACHE_CONFIG DEFAULT_CONFIG = -{ - CACHE_DEFAULT_MAX_RESULTSET_ROWS, - CACHE_DEFAULT_MAX_RESULTSET_SIZE, - NULL, - NULL, - NULL, - NULL, - 0, - CACHE_DEFAULT_TTL, - CACHE_DEFAULT_DEBUG -}; - -static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); - -/** - * Hashes a cache key to an integer. - * - * @param key Pointer to cache key. - * - * @returns Corresponding integer hash. - */ -int hash_of_key(const void* key) -{ - int hash = 0; - - const char* i = (const char*)key; - const char* end = i + CACHE_KEY_MAXLEN; - - while (i < end) - { - int c = *i; - hash = c + (hash << 6) + (hash << 16) - hash; - ++i; - } - - return hash; -} - -static int hashfn(const void* address) -{ - // TODO: Hash the address; pointers are not evenly distributed. - return (long)address; -} - -static int hashcmp(const void* address1, const void* address2) -{ - return (long)address2 - (long)address1; -} - -// Initial size of hashtable used for storing keys of queries that -// are being fetches. -#define CACHE_PENDING_ITEMS 50 - -// -// API BEGIN +// API Implementation // /** * Create an instance of the cache filter for a particular service * within MaxScale. * - * @param name The name of the instance (as defined in the config file). - * @param options The options for this filter - * @param params The array of name/value pair parameters for the filter + * @param zName The name of the instance (as defined in the config file). + * @param pzOptions The options for this filter + * @param ppparams The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ -static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **params) +static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) { - CACHE_INSTANCE *cinstance = NULL; - CACHE_CONFIG config = DEFAULT_CONFIG; + Cache* pCache = Cache::Create(zName, pzOptions, ppParams); - if (process_params(options, params, &config)) - { - CACHE_RULES *rules = NULL; - - if (config.rules) - { - rules = cache_rules_load(config.rules, config.debug); - } - else - { - rules = cache_rules_create(config.debug); - } - - if (rules) - { - cinstance = (CACHE_INSTANCE*)MXS_CALLOC(1, sizeof(CACHE_INSTANCE)); - HASHTABLE* pending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); - - if (cinstance && pending) - { - StorageFactory *factory = StorageFactory::Open(config.storage); - - if (factory) - { - uint32_t ttl = config.ttl; - int argc = config.storage_argc; - char** argv = config.storage_argv; - - Storage *storage = factory->createStorage(name, ttl, argc, argv); - - if (storage) - { - cinstance->name = name; - cinstance->config = config; - cinstance->rules = rules; - cinstance->factory = factory; - cinstance->storage = storage; - cinstance->pending = pending; - - MXS_NOTICE("Cache storage %s opened and initialized.", config.storage); - } - else - { - MXS_ERROR("Could not create storage instance for '%s'.", name); - cache_rules_free(rules); - delete factory; - MXS_FREE(cinstance); - hashtable_free(pending); - cinstance = NULL; - } - } - else - { - MXS_ERROR("Could not load cache storage module '%s'.", name); - cache_rules_free(rules); - MXS_FREE(cinstance); - cinstance = NULL; - } - } - else - { - MXS_FREE(cinstance); - if (pending) - { - hashtable_free(pending); - } - } - } - } - - return (FILTER*)cinstance; + return reinterpret_cast(pCache); } /** * Associate a new session with this instance of the filter. * - * @param instance The cache instance data - * @param session The session itself + * @param pInstance The cache instance data + * @param pSession The session itself * * @return Session specific data for this session */ -static void *newSession(FILTER *instance, SESSION *session) +static void *newSession(FILTER* pInstance, SESSION* pSession) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - - SessionCache *pSessionCache = SessionCache::Create(cinstance, session); + Cache* pCache = reinterpret_cast(pInstance); + SessionCache* pSessionCache = SessionCache::Create(pCache, pSession); return pSessionCache; } @@ -255,14 +119,12 @@ static void *newSession(FILTER *instance, SESSION *session) /** * A session has been closed. * - * @param instance The cache instance data - * @param sdata The session data of the session being closed + * @param pInstance The cache instance data + * @param pSessionData The session data of the session being closed */ -static void closeSession(FILTER *instance, void *sdata) +static void closeSession(FILTER* pInstance, void* pSessionData) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - - SessionCache* pSessionCache = static_cast(sdata); + SessionCache* pSessionCache = static_cast(pSessionData); pSessionCache->close(); } @@ -270,14 +132,12 @@ static void closeSession(FILTER *instance, void *sdata) /** * Free the session data. * - * @param instance The cache instance data - * @param sdata The session data of the session being closed + * @param pInstance The cache instance data + * @param pSessionData The session data of the session being closed */ -static void freeSession(FILTER *instance, void *sdata) +static void freeSession(FILTER* pInstance, void* pSessionData) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; - - SessionCache* pSessionCache = static_cast(sdata); + SessionCache* pSessionCache = static_cast(pSessionData); delete pSessionCache; } @@ -285,84 +145,74 @@ static void freeSession(FILTER *instance, void *sdata) /** * Set the downstream component for this filter. * - * @param instance The cache instance data - * @param sdata The session data of the session - * @param down The downstream filter or router + * @param pInstance The cache instance data + * @param pSessionData The session data of the session + * @param pDownstream The downstream filter or router */ -static void setDownstream(FILTER *instance, void *sdata, DOWNSTREAM *down) +static void setDownstream(FILTER* pInstance, void* pSessionData, DOWNSTREAM* pDownstream) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; + SessionCache* pSessionCache = static_cast(pSessionData); - SessionCache* pSessionCache = static_cast(sdata); - - pSessionCache->setDownstream(down); + pSessionCache->setDownstream(pDownstream); } /** * Set the upstream component for this filter. * - * @param instance The cache instance data - * @param sdata The session data of the session - * @param up The upstream filter or router + * @param pInstance The cache instance data + * @param pSessionData The session data of the session + * @param pUpstream The upstream filter or router */ -static void setUpstream(FILTER *instance, void *sdata, UPSTREAM *up) +static void setUpstream(FILTER* pInstance, void* pSessionData, UPSTREAM* pUpstream) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; + SessionCache* pSessionCache = static_cast(pSessionData); - SessionCache* pSessionCache = static_cast(sdata); - - pSessionCache->setUpstream(up); + pSessionCache->setUpstream(pUpstream); } /** * A request on its way to a backend is delivered to this function. * - * @param instance The filter instance data - * @param sdata The filter session data - * @param buffer Buffer containing an MySQL protocol packet. + * @param pInstance The filter instance data + * @param pSessionData The filter session data + * @param pPacket Buffer containing an MySQL protocol packet. */ -static int routeQuery(FILTER *instance, void *sdata, GWBUF *packet) +static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; + SessionCache* pSessionCache = static_cast(pSessionData); - SessionCache* pSessionCache = static_cast(sdata); - - return pSessionCache->routeQuery(packet); + return pSessionCache->routeQuery(pPacket); } /** * A response on its way to the client is delivered to this function. * - * @param instance The filter instance data - * @param sdata The filter session data - * @param queue The query data + * @param pInstance The filter instance data + * @param pSessionData The filter session data + * @param pPacket The response data */ -static int clientReply(FILTER *instance, void *sdata, GWBUF *data) +static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; + SessionCache* pSessionCache = static_cast(pSessionData); - SessionCache* pSessionCache = static_cast(sdata); - - return pSessionCache->clientReply(data); + return pSessionCache->clientReply(pPacket); } /** * Diagnostics routine * - * If csdata is NULL then print diagnostics on the instance as a whole, + * If cpSessionData is NULL then print diagnostics on the instance as a whole, * otherwise print diagnostics for the particular session. * - * @param instance The filter instance - * @param fsession Filter session, may be NULL - * @param dcb The DCB for diagnostic output + * @param pInstance The filter instance + * @param pSessionData Filter session, may be NULL + * @param pDcb The DCB for diagnostic output */ -static void diagnostics(FILTER *instance, void *sdata, DCB *dcb) +static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb) { - CACHE_INSTANCE *cinstance = (CACHE_INSTANCE*)instance; + SessionCache* pSessionCache = static_cast(pSessionData); - SessionCache* pSessionCache = static_cast(sdata); - - pSessionCache->diagnostics(dcb); + pSessionCache->diagnostics(pDcb); } @@ -375,166 +225,3 @@ static uint64_t getCapabilities(void) { return RCAP_TYPE_TRANSACTION_TRACKING; } - -// -// API END -// - -/** - * Processes the cache params - * - * @param options Options as passed to the filter. - * @param params Parameters as passed to the filter. - * @param config Pointer to config instance where params will be stored. - * - * @return True if all parameters could be processed, false otherwise. - */ -static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config) -{ - bool error = false; - - for (int i = 0; params[i]; ++i) - { - const FILTER_PARAMETER *param = params[i]; - - if (strcmp(param->name, "max_resultset_rows") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->max_resultset_rows = v; - } - else - { - config->max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; - } - } - else if (strcmp(param->name, "max_resultset_size") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->max_resultset_size = v * 1024; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", param->name); - error = true; - } - } - else if (strcmp(param->name, "rules") == 0) - { - if (*param->value == '/') - { - config->rules = MXS_STRDUP(param->value); - } - else - { - const char *datadir = get_datadir(); - size_t len = strlen(datadir) + 1 + strlen(param->value) + 1; - - char *rules = (char*)MXS_MALLOC(len); - - if (rules) - { - sprintf(rules, "%s/%s", datadir, param->value); - config->rules = rules; - } - } - - if (!config->rules) - { - error = true; - } - } - else if (strcmp(param->name, "storage_options") == 0) - { - config->storage_options = MXS_STRDUP(param->value); - - if (config->storage_options) - { - int argc = 1; - char *arg = config->storage_options; - - while ((arg = strchr(config->storage_options, ','))) - { - ++argc; - } - - config->storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); - - if (config->storage_argv) - { - config->storage_argc = argc; - - int i = 0; - arg = config->storage_options; - config->storage_argv[i++] = arg; - - while ((arg = strchr(config->storage_options, ','))) - { - *arg = 0; - ++arg; - config->storage_argv[i++] = arg; - } - - config->storage_argv[i] = NULL; - } - else - { - MXS_FREE(config->storage_options); - config->storage_options = NULL; - } - } - else - { - error = true; - } - } - else if (strcmp(param->name, "storage") == 0) - { - config->storage = param->value; - } - else if (strcmp(param->name, "ttl") == 0) - { - int v = atoi(param->value); - - if (v > 0) - { - config->ttl = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", param->name); - error = true; - } - } - else if (strcmp(param->name, "debug") == 0) - { - int v = atoi(param->value); - - if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) - { - config->debug = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be between %d and %d, inclusive.", - param->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); - error = true; - } - } - else if (!filter_standard_parameter(params[i]->name)) - { - MXS_ERROR("Unknown configuration entry '%s'.", param->name); - error = true; - } - } - - return !error; -} diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index d03cdd8ed..d31af252d 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -57,17 +57,4 @@ typedef struct cache_config uint32_t debug; } CACHE_CONFIG; -typedef struct cache_instance -{ - const char *name; // The name of the instance; the section name in the config. - CACHE_CONFIG config; // The configuration of the cache instance. - CACHE_RULES *rules; // The rules of the cache instance. - StorageFactory *factory; // The storage factory. - Storage *storage; // The storage instance to use. - HASHTABLE *pending; // Pending items; being fetched from the backend. - SPINLOCK pending_lock; // Lock used for protecting 'pending'. -} CACHE_INSTANCE; - -int hash_of_key(const void* key); - #endif diff --git a/server/modules/filter/cache/sessioncache.cc b/server/modules/filter/cache/sessioncache.cc index fcb35c4b5..c1c91f666 100644 --- a/server/modules/filter/cache/sessioncache.cc +++ b/server/modules/filter/cache/sessioncache.cc @@ -11,6 +11,7 @@ * Public License. */ +#define MXS_MODULE_NAME "cache" #include "sessioncache.h" #include #include @@ -18,13 +19,10 @@ #include #include "storage.h" -#define DUMMY_VALUE (void*)0xdeadbeef - -SessionCache::SessionCache(CACHE_INSTANCE* pInstance, SESSION* pSession, char* zDefaultDb) +SessionCache::SessionCache(Cache* pCache, SESSION* pSession, char* zDefaultDb) : m_state(CACHE_EXPECTING_NOTHING) - , m_pInstance(pInstance) + , m_pCache(pCache) , m_pSession(pSession) - , m_pStorage(pInstance->storage) , m_zDefaultDb(zDefaultDb) , m_zUseDb(NULL) , m_refreshing(false) @@ -43,7 +41,7 @@ SessionCache::~SessionCache() } //static -SessionCache* SessionCache::Create(CACHE_INSTANCE* pInstance, SESSION* pSession) +SessionCache* SessionCache::Create(Cache* pCache, SESSION* pSession) { SessionCache* pSessionCache = NULL; @@ -60,7 +58,7 @@ SessionCache* SessionCache::Create(CACHE_INSTANCE* pInstance, SESSION* pSession) if ((pMysqlSession->db[0] == 0) || zDefaultDb) { - pSessionCache = new (std::nothrow) SessionCache(pInstance, pSession, zDefaultDb); + pSessionCache = new (std::nothrow) SessionCache(pCache, pSession, zDefaultDb); if (!pSessionCache) { @@ -140,9 +138,9 @@ int SessionCache::routeQuery(GWBUF* pPacket) if ((session_is_autocommit(session) && !session_trx_is_active(session)) || session_trx_is_read_only(session)) { - if (cache_rules_should_store(m_pInstance->rules, m_zDefaultDb, pPacket)) + if (m_pCache->shouldStore(m_zDefaultDb, pPacket)) { - if (cache_rules_should_use(m_pInstance->rules, m_pSession)) + if (m_pCache->shouldUse(m_pSession)) { GWBUF* pResponse; cache_result_t result = get_cached_response(pPacket, &pResponse); @@ -154,21 +152,7 @@ int SessionCache::routeQuery(GWBUF* pPacket) // The value was found, but it was stale. Now we need to // figure out whether somebody else is already fetching it. - long key = hash_of_key(m_key); - - spinlock_acquire(&m_pInstance->pending_lock); - // TODO: Remove the internal locking of hashtable. The internal - // TODO: locking is no good if you need transactional behaviour. - // TODO: Now we lock twice. - void *value = hashtable_fetch(m_pInstance->pending, (void*)key); - if (!value) - { - // It's not being fetched, so we make a note that we are. - hashtable_add(m_pInstance->pending, (void*)key, DUMMY_VALUE); - } - spinlock_release(&m_pInstance->pending_lock); - - if (!value) + if (m_pCache->mustRefresh(m_key, this)) { // We were the first ones who hit the stale item. It's // our responsibility now to fetch it. @@ -229,7 +213,7 @@ int SessionCache::routeQuery(GWBUF* pPacket) } else { - if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + if (log_decisions()) { MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " "storing to cache.", @@ -268,14 +252,14 @@ int SessionCache::clientReply(GWBUF* pData) if (m_state != CACHE_IGNORING_RESPONSE) { - if (gwbuf_length(m_res.pData) > m_pInstance->config.max_resultset_size) + if (gwbuf_length(m_res.pData) > m_pCache->config().max_resultset_size) { - if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + if (log_decisions()) { MXS_NOTICE("Current size %uB of resultset, at least as much " "as maximum allowed size %uKiB. Not caching.", gwbuf_length(m_res.pData), - m_pInstance->config.max_resultset_size / 1024); + m_pCache->config().max_resultset_size / 1024); } m_state = CACHE_IGNORING_RESPONSE; @@ -503,9 +487,9 @@ int SessionCache::handle_expecting_rows() m_res.offset += packetlen; ++m_res.nRows; - if (m_res.nRows > m_pInstance->config.max_resultset_rows) + if (m_res.nRows > m_pCache->config().max_resultset_rows) { - if (m_pInstance->config.debug & CACHE_DEBUG_DECISIONS) + if (log_decisions()) { MXS_NOTICE("Max rows %lu reached, not caching result.", m_res.nRows); } @@ -623,13 +607,13 @@ void SessionCache::reset_response_state() */ cache_result_t SessionCache::get_cached_response(const GWBUF *pQuery, GWBUF **ppResponse) { - cache_result_t result = m_pStorage->getKey(m_zDefaultDb, pQuery, m_key); + cache_result_t result = m_pCache->getKey(m_zDefaultDb, pQuery, m_key); if (result == CACHE_RESULT_OK) { uint32_t flags = CACHE_FLAGS_INCLUDE_STALE; - result = m_pStorage->getValue(m_key, flags, ppResponse); + result = m_pCache->getValue(m_key, flags, ppResponse); } else { @@ -654,13 +638,13 @@ void SessionCache::store_result() { m_res.pData = pData; - cache_result_t result = m_pStorage->putValue(m_key, m_res.pData); + cache_result_t result = m_pCache->putValue(m_key, m_res.pData); if (result != CACHE_RESULT_OK) { MXS_ERROR("Could not store cache item, deleting it."); - result = m_pStorage->delValue(m_key); + result = m_pCache->delValue(m_key); if ((result != CACHE_RESULT_OK) || (result != CACHE_RESULT_NOT_FOUND)) { @@ -671,14 +655,7 @@ void SessionCache::store_result() if (m_refreshing) { - long key = hash_of_key(m_key); - - spinlock_acquire(&m_pInstance->pending_lock); - ss_dassert(hashtable_fetch(m_pInstance->pending, (void*)key) == DUMMY_VALUE); - ss_debug(int n =) hashtable_delete(m_pInstance->pending, (void*)key); - ss_dassert(n == 1); - spinlock_release(&m_pInstance->pending_lock); - + m_pCache->refreshed(m_key, this); m_refreshing = false; } } diff --git a/server/modules/filter/cache/sessioncache.h b/server/modules/filter/cache/sessioncache.h index 67e785736..6606e975b 100644 --- a/server/modules/filter/cache/sessioncache.h +++ b/server/modules/filter/cache/sessioncache.h @@ -15,9 +15,12 @@ #include #include #include +#include "cache.h" #include "cachefilter.h" #include "cache_storage_api.h" +class Cache; + class SessionCache { public: @@ -48,7 +51,7 @@ public: /** * Creates a SessionCache instance. * - * @param pInstance Pointer to the cache instance to which this session cache + * @param pCache Pointer to the cache instance to which this session cache * belongs. Must remain valid for the lifetime of the SessionCache * instance being created. * @param pSession Pointer to the session this session cache instance is @@ -57,7 +60,7 @@ public: * * @return A new instance or NULL if memory allocation fails. */ - static SessionCache* Create(CACHE_INSTANCE* pInstance, SESSION* pSession); + static SessionCache* Create(Cache* pCache, SESSION* pSession); /** * The session has been closed. @@ -113,22 +116,21 @@ private: bool log_decisions() const { - return m_pInstance->config.debug & CACHE_DEBUG_DECISIONS ? true : false; + return m_pCache->config().debug & CACHE_DEBUG_DECISIONS ? true : false; } void store_result(); private: - SessionCache(CACHE_INSTANCE* pInstance, SESSION* pSession, char* zDefaultDb); + SessionCache(Cache* pCache, SESSION* pSession, char* zDefaultDb); SessionCache(const SessionCache&); SessionCache& operator = (const SessionCache&); private: cache_session_state_t m_state; /**< What state is the session in, what data is expected. */ - CACHE_INSTANCE* m_pInstance; /**< The cache instance the session is associated with. */ + Cache* m_pCache; /**< The cache instance the session is associated with. */ SESSION* m_pSession; /**< The session this data is associated with. */ - Storage* m_pStorage; /**< The storage to be used with this session data. */ DOWNSTREAM m_down; /**< The previous filter or equivalent. */ UPSTREAM m_up; /**< The next filter or equivalent. */ CACHE_RESPONSE_STATE m_res; /**< The response state. */ From f8f0ef8d34ed7d91aebfd872b3ddeaa990b339d8 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 24 Nov 2016 22:01:24 +0200 Subject: [PATCH 41/48] Cache: Ensure no C++ exceptions can escape --- server/modules/filter/cache/cache.h | 2 +- server/modules/filter/cache/cachefilter.cc | 32 ++++++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index f274ffec6..12def4232 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -26,7 +26,7 @@ class Cache public: ~Cache(); - static Cache* Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParms); + static Cache* Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams); /** * Returns whether the results of a particular query should be stored. diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 8c4112750..e991bb390 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -12,6 +12,8 @@ */ #define MXS_MODULE_NAME "cache" +#include "cachefilter.h" +#include #include #include "cache.h" #include "sessioncache.h" @@ -29,6 +31,11 @@ static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacke static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb); static uint64_t getCapabilities(void); +#define CPP_GUARD(statement)\ + do { try { statement; } \ + catch (const std::exception& x) { MXS_ERROR("Caught standard exception: %s", x.what()); }\ + catch (...) { MXS_ERROR("Caught unknown exception."); } } while (false) + // // Global symbols of the Module // @@ -95,7 +102,8 @@ extern "C" FILTER_OBJECT *GetModuleObject() */ static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) { - Cache* pCache = Cache::Create(zName, pzOptions, ppParams); + Cache* pCache = NULL; + CPP_GUARD(pCache = Cache::Create(zName, pzOptions, ppParams)); return reinterpret_cast(pCache); } @@ -111,7 +119,9 @@ static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAME static void *newSession(FILTER* pInstance, SESSION* pSession) { Cache* pCache = reinterpret_cast(pInstance); - SessionCache* pSessionCache = SessionCache::Create(pCache, pSession); + + SessionCache* pSessionCache = NULL; + CPP_GUARD(pSessionCache = SessionCache::Create(pCache, pSession)); return pSessionCache; } @@ -126,7 +136,7 @@ static void closeSession(FILTER* pInstance, void* pSessionData) { SessionCache* pSessionCache = static_cast(pSessionData); - pSessionCache->close(); + CPP_GUARD(pSessionCache->close()); } /** @@ -153,7 +163,7 @@ static void setDownstream(FILTER* pInstance, void* pSessionData, DOWNSTREAM* pDo { SessionCache* pSessionCache = static_cast(pSessionData); - pSessionCache->setDownstream(pDownstream); + CPP_GUARD(pSessionCache->setDownstream(pDownstream)); } /** @@ -167,7 +177,7 @@ static void setUpstream(FILTER* pInstance, void* pSessionData, UPSTREAM* pUpstre { SessionCache* pSessionCache = static_cast(pSessionData); - pSessionCache->setUpstream(pUpstream); + CPP_GUARD(pSessionCache->setUpstream(pUpstream)); } /** @@ -181,7 +191,10 @@ static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) { SessionCache* pSessionCache = static_cast(pSessionData); - return pSessionCache->routeQuery(pPacket); + int rv = 0; + CPP_GUARD(rv = pSessionCache->routeQuery(pPacket)); + + return rv; } /** @@ -195,7 +208,10 @@ static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket) { SessionCache* pSessionCache = static_cast(pSessionData); - return pSessionCache->clientReply(pPacket); + int rv = 0; + CPP_GUARD(rv = pSessionCache->clientReply(pPacket)); + + return rv; } /** @@ -212,7 +228,7 @@ static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb) { SessionCache* pSessionCache = static_cast(pSessionData); - pSessionCache->diagnostics(pDcb); + CPP_GUARD(pSessionCache->diagnostics(pDcb)); } From a71d1f08778641a8a6646c2b4f6df98ce9eaf410 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 25 Nov 2016 11:08:36 +0200 Subject: [PATCH 42/48] Cache: Param processing moved to cachefilter.cc This in antecipation of different Cache instances being created depending on passed arguments. --- server/modules/filter/cache/cache.cc | 258 ++++----------------- server/modules/filter/cache/cache.h | 6 +- server/modules/filter/cache/cachefilter.cc | 255 +++++++++++++++++++- server/modules/filter/cache/cachefilter.h | 12 +- 4 files changed, 304 insertions(+), 227 deletions(-) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc index b6e20f4ab..971322b64 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cache.cc @@ -23,19 +23,6 @@ // are being fetches. #define CACHE_PENDING_ITEMS 50 -static const CACHE_CONFIG DEFAULT_CONFIG = -{ - CACHE_DEFAULT_MAX_RESULTSET_ROWS, - CACHE_DEFAULT_MAX_RESULTSET_SIZE, - NULL, - NULL, - NULL, - NULL, - 0, - CACHE_DEFAULT_TTL, - CACHE_DEFAULT_DEBUG -}; - /** * Hashes a cache key to an integer. * @@ -73,7 +60,7 @@ static int hashcmp(const void* address1, const void* address2) Cache::Cache(const char* zName, - const CACHE_CONFIG& config, + CACHE_CONFIG& config, CACHE_RULES* pRules, StorageFactory* pFactory, Storage* pStorage, @@ -85,6 +72,7 @@ Cache::Cache(const char* zName, , m_pStorage(pStorage) , m_pPending(pPending) { + cache_config_reset(config); } Cache::~Cache() @@ -94,78 +82,73 @@ Cache::~Cache() } //static -Cache* Cache::Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) +Cache* Cache::Create(const char* zName, CACHE_CONFIG& config) { Cache* pCache = NULL; - CACHE_CONFIG config = DEFAULT_CONFIG; + CACHE_RULES* pRules = NULL; - if (process_params(pzOptions, ppParams, &config)) + if (config.rules) { - CACHE_RULES* pRules = NULL; + pRules = cache_rules_load(config.rules, config.debug); + } + else + { + pRules = cache_rules_create(config.debug); + } - if (config.rules) - { - pRules = cache_rules_load(config.rules, config.debug); - } - else - { - pRules = cache_rules_create(config.debug); - } + if (pRules) + { + HASHTABLE* pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); - if (pRules) + if (pPending) { - HASHTABLE* pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); + StorageFactory *pFactory = StorageFactory::Open(config.storage); - if (pPending) + if (pFactory) { - StorageFactory *pFactory = StorageFactory::Open(config.storage); + uint32_t ttl = config.ttl; + int argc = config.storage_argc; + char** argv = config.storage_argv; - if (pFactory) + Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); + + if (pStorage) { - uint32_t ttl = config.ttl; - int argc = config.storage_argc; - char** argv = config.storage_argv; - - Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); - - if (pStorage) - { - pCache = new (std::nothrow) Cache(zName, - config, - pRules, - pFactory, - pStorage, - pPending); - } - else - { - MXS_ERROR("Could not create storage instance for '%s'.", zName); - } + pCache = new (std::nothrow) Cache(zName, + config, + pRules, + pFactory, + pStorage, + pPending); } else { - MXS_ERROR("Could not open storage factory '%s'.", config.storage); - } - - if (!pCache) - { - delete pFactory; + MXS_ERROR("Could not create storage instance for '%s'.", zName); } } + else + { + MXS_ERROR("Could not open storage factory '%s'.", config.storage); + } if (!pCache) { - hashtable_free(pPending); + delete pFactory; } } if (!pCache) { - cache_rules_free(pRules); + hashtable_free(pPending); } } + if (!pCache) + { + cache_rules_free(pRules); + } + return pCache; } @@ -233,162 +216,3 @@ cache_result_t Cache::delValue(const char* pKey) { return m_pStorage->delValue(pKey); } - -/** - * Processes the cache params - * - * @param options Options as passed to the filter. - * @param params Parameters as passed to the filter. - * @param config Pointer to config instance where params will be stored. - * - * @return True if all parameters could be processed, false otherwise. - */ -bool Cache::process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG* pConfig) -{ - bool error = false; - - for (int i = 0; ppParams[i]; ++i) - { - const FILTER_PARAMETER *pParam = ppParams[i]; - - if (strcmp(pParam->name, "max_resultset_rows") == 0) - { - int v = atoi(pParam->value); - - if (v > 0) - { - pConfig->max_resultset_rows = v; - } - else - { - pConfig->max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; - } - } - else if (strcmp(pParam->name, "max_resultset_size") == 0) - { - int v = atoi(pParam->value); - - if (v > 0) - { - pConfig->max_resultset_size = v * 1024; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", pParam->name); - error = true; - } - } - else if (strcmp(pParam->name, "rules") == 0) - { - if (*pParam->value == '/') - { - pConfig->rules = MXS_STRDUP(pParam->value); - } - else - { - const char *datadir = get_datadir(); - size_t len = strlen(datadir) + 1 + strlen(pParam->value) + 1; - - char *rules = (char*)MXS_MALLOC(len); - - if (rules) - { - sprintf(rules, "%s/%s", datadir, pParam->value); - pConfig->rules = rules; - } - } - - if (!pConfig->rules) - { - error = true; - } - } - else if (strcmp(pParam->name, "storage_options") == 0) - { - pConfig->storage_options = MXS_STRDUP(pParam->value); - - if (pConfig->storage_options) - { - int argc = 1; - char *arg = pConfig->storage_options; - - while ((arg = strchr(pConfig->storage_options, ','))) - { - ++argc; - } - - pConfig->storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); - - if (pConfig->storage_argv) - { - pConfig->storage_argc = argc; - - int i = 0; - arg = pConfig->storage_options; - pConfig->storage_argv[i++] = arg; - - while ((arg = strchr(pConfig->storage_options, ','))) - { - *arg = 0; - ++arg; - pConfig->storage_argv[i++] = arg; - } - - pConfig->storage_argv[i] = NULL; - } - else - { - MXS_FREE(pConfig->storage_options); - pConfig->storage_options = NULL; - } - } - else - { - error = true; - } - } - else if (strcmp(pParam->name, "storage") == 0) - { - pConfig->storage = pParam->value; - } - else if (strcmp(pParam->name, "ttl") == 0) - { - int v = atoi(pParam->value); - - if (v > 0) - { - pConfig->ttl = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be an integer larger than 0.", pParam->name); - error = true; - } - } - else if (strcmp(pParam->name, "debug") == 0) - { - int v = atoi(pParam->value); - - if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) - { - pConfig->debug = v; - } - else - { - MXS_ERROR("The value of the configuration entry '%s' must " - "be between %d and %d, inclusive.", - pParam->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); - error = true; - } - } - else if (!filter_standard_parameter(pParam->name)) - { - MXS_ERROR("Unknown configuration entry '%s'.", pParam->name); - error = true; - } - } - - return !error; -} diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index 12def4232..105b5f39b 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -26,7 +26,7 @@ class Cache public: ~Cache(); - static Cache* Create(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams); + static Cache* Create(const char* zName, CACHE_CONFIG& config); /** * Returns whether the results of a particular query should be stored. @@ -77,14 +77,12 @@ public: private: Cache(const char* zName, - const CACHE_CONFIG& config, + CACHE_CONFIG& config, CACHE_RULES* pRules, StorageFactory* pFactory, Storage* pStorage, HASHTABLE* pPending); - static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONFIG* config); - private: Cache(const Cache&); Cache& operator = (const Cache&); diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index e991bb390..d1b621869 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -14,12 +14,27 @@ #define MXS_MODULE_NAME "cache" #include "cachefilter.h" #include +#include #include +#include #include "cache.h" #include "sessioncache.h" static char VERSION_STRING[] = "V1.0.0"; +static const CACHE_CONFIG DEFAULT_CONFIG = +{ + CACHE_DEFAULT_MAX_RESULTSET_ROWS, + CACHE_DEFAULT_MAX_RESULTSET_SIZE, + NULL, + NULL, + NULL, + NULL, + 0, + CACHE_DEFAULT_TTL, + CACHE_DEFAULT_DEBUG +}; + static FILTER* createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams); static void* newSession(FILTER* pInstance, SESSION* pSession); static void closeSession(FILTER* pInstance, void* pSessionData); @@ -31,6 +46,8 @@ static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacke static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb); static uint64_t getCapabilities(void); +static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config); + #define CPP_GUARD(statement)\ do { try { statement; } \ catch (const std::exception& x) { MXS_ERROR("Caught standard exception: %s", x.what()); }\ @@ -87,7 +104,7 @@ extern "C" FILTER_OBJECT *GetModuleObject() }; // -// API Implementation +// API Implementation BEGIN // /** @@ -103,7 +120,17 @@ extern "C" FILTER_OBJECT *GetModuleObject() static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAMETER** ppParams) { Cache* pCache = NULL; - CPP_GUARD(pCache = Cache::Create(zName, pzOptions, ppParams)); + CACHE_CONFIG config = DEFAULT_CONFIG; + + if (process_params(pzOptions, ppParams, config)) + { + CPP_GUARD(pCache = Cache::Create(zName, config)); + + if (!pCache) + { + cache_config_finish(config); + } + } return reinterpret_cast(pCache); } @@ -241,3 +268,227 @@ static uint64_t getCapabilities(void) { return RCAP_TYPE_TRANSACTION_TRACKING; } + +// +// API Implementation END +// + +/** + * Processes the cache params + * + * @param options Options as passed to the filter. + * @param params Parameters as passed to the filter. + * @param config Reference to config instance where params will be stored. + * + * @return True if all parameters could be processed, false otherwise. + */ +static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config) +{ + bool error = false; + + for (int i = 0; ppParams[i]; ++i) + { + const FILTER_PARAMETER *pParam = ppParams[i]; + + if (strcmp(pParam->name, "max_resultset_rows") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.max_resultset_rows = v; + } + else + { + config.max_resultset_rows = CACHE_DEFAULT_MAX_RESULTSET_ROWS; + } + } + else if (strcmp(pParam->name, "max_resultset_size") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.max_resultset_size = v * 1024; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "rules") == 0) + { + if (*pParam->value == '/') + { + config.rules = MXS_STRDUP(pParam->value); + } + else + { + const char* datadir = get_datadir(); + size_t len = strlen(datadir) + 1 + strlen(pParam->value) + 1; + + char *rules = (char*)MXS_MALLOC(len); + + if (rules) + { + sprintf(rules, "%s/%s", datadir, pParam->value); + config.rules = rules; + } + } + + if (!config.rules) + { + error = true; + } + } + else if (strcmp(pParam->name, "storage_options") == 0) + { + config.storage_options = MXS_STRDUP(pParam->value); + + if (config.storage_options) + { + int argc = 1; + char *arg = config.storage_options; + + while ((arg = strchr(config.storage_options, ','))) + { + ++argc; + } + + config.storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*)); + + if (config.storage_argv) + { + config.storage_argc = argc; + + int i = 0; + arg = config.storage_options; + config.storage_argv[i++] = arg; + + while ((arg = strchr(config.storage_options, ','))) + { + *arg = 0; + ++arg; + config.storage_argv[i++] = arg; + } + + config.storage_argv[i] = NULL; + } + else + { + MXS_FREE(config.storage_options); + config.storage_options = NULL; + } + } + else + { + error = true; + } + } + else if (strcmp(pParam->name, "storage") == 0) + { + config.storage = MXS_STRDUP(pParam->value); + + if (!config.storage) + { + error = true; + } + } + else if (strcmp(pParam->name, "ttl") == 0) + { + int v = atoi(pParam->value); + + if (v > 0) + { + config.ttl = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be an integer larger than 0.", pParam->name); + error = true; + } + } + else if (strcmp(pParam->name, "debug") == 0) + { + int v = atoi(pParam->value); + + if ((v >= CACHE_DEBUG_MIN) && (v <= CACHE_DEBUG_MAX)) + { + config.debug = v; + } + else + { + MXS_ERROR("The value of the configuration entry '%s' must " + "be between %d and %d, inclusive.", + pParam->name, CACHE_DEBUG_MIN, CACHE_DEBUG_MAX); + error = true; + } + } + else if (!filter_standard_parameter(pParam->name)) + { + MXS_ERROR("Unknown configuration entry '%s'.", pParam->name); + error = true; + } + } + + if (error) + { + cache_config_finish(config); + } + + return !error; +} + +/** + * Frees all data of a config object, but not the object itself + * + * @param pConfig Pointer to a config object. + */ +void cache_config_finish(CACHE_CONFIG& config) +{ + MXS_FREE(config.rules); + MXS_FREE(config.storage); + MXS_FREE(config.storage_options); + + for (int i = 0; i < config.storage_argc; ++i) + { + MXS_FREE(config.storage_argv[i]); + } + + config.max_resultset_rows = 0; + config.max_resultset_size = 0; + config.rules = NULL; + config.storage = NULL; + config.storage_options = NULL; + config.storage_argc = 0; + config.storage_argv = NULL; + config.ttl = 0; + config.debug = 0; +} + +/** + * Frees all data of a config object, and the object itself + * + * @param pConfig Pointer to a config object. + */ +void cache_config_free(CACHE_CONFIG* pConfig) +{ + if (pConfig) + { + cache_config_finish(*pConfig); + MXS_FREE(pConfig); + } +} + +/** + * Resets the data without freeing anything. + * + * @param config Reference to a config object. + */ +void cache_config_reset(CACHE_CONFIG& config) +{ + memset(&config, 0, sizeof(config)); +} diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index d31af252d..4f9fc4365 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -48,13 +48,17 @@ typedef struct cache_config { uint32_t max_resultset_rows; uint32_t max_resultset_size; - const char *rules; - const char *storage; - char *storage_options; - char **storage_argv; + char* rules; + char* storage; + char* storage_options; + char** storage_argv; int storage_argc; uint32_t ttl; uint32_t debug; } CACHE_CONFIG; +void cache_config_finish(CACHE_CONFIG& config); +void cache_config_free(CACHE_CONFIG* pConfig); +void cache_config_reset(CACHE_CONFIG& config); + #endif From 68f70ee5b2e25dcf8c1a3b20c4eaba160ae04acd Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 25 Nov 2016 13:03:48 +0200 Subject: [PATCH 43/48] Cache: Prepare for ST, MT, and PT caches With a small cost it is possible to prepare for a single-thread, multi-thread and cache-per-thread specific cases. --- server/modules/filter/cache/CMakeLists.txt | 2 +- server/modules/filter/cache/cache.cc | 81 +++++++++++++++++++ server/modules/filter/cache/cache.h | 19 ++++- server/modules/filter/cache/cachefilter.cc | 9 +-- server/modules/filter/cache/cachefilter.h | 5 ++ server/modules/filter/cache/cachemt.cc | 91 ++++++++++++++++++++++ server/modules/filter/cache/cachemt.h | 43 ++++++++++ server/modules/filter/cache/storage.cc | 4 + 8 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 server/modules/filter/cache/cachemt.cc create mode 100644 server/modules/filter/cache/cachemt.h diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 2385faf8e..69ea3e697 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -1,5 +1,5 @@ if (JANSSON_FOUND) - add_library(cache SHARED cache.cc cachefilter.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) + add_library(cache SHARED cache.cc cachefilter.cc cachemt.cc rules.cc sessioncache.cc storage.cc storagefactory.cc) target_link_libraries(cache maxscale-common jansson) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc index 971322b64..138be8d27 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cache.cc @@ -152,6 +152,58 @@ Cache* Cache::Create(const char* zName, CACHE_CONFIG& config) return pCache; } +//static +bool Cache::Create(const CACHE_CONFIG& config, + CACHE_RULES** ppRules, + StorageFactory** ppFactory, + HASHTABLE** ppPending) +{ + CACHE_RULES* pRules = NULL; + HASHTABLE* pPending = NULL; + StorageFactory* pFactory = NULL; + + if (config.rules) + { + pRules = cache_rules_load(config.rules, config.debug); + } + else + { + pRules = cache_rules_create(config.debug); + } + + if (pRules) + { + pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); + + if (pPending) + { + pFactory = StorageFactory::Open(config.storage); + + if (!pFactory) + { + MXS_ERROR("Could not open storage factory '%s'.", config.storage); + } + } + } + + bool rv = (pRules && pPending && pFactory); + + if (rv) + { + *ppRules = pRules; + *ppPending = pPending; + *ppFactory = pFactory; + } + else + { + cache_rules_free(pRules); + hashtable_free(pPending); + delete pFactory; + } + + return rv; +} + bool Cache::shouldStore(const char* zDefaultDb, const GWBUF* pQuery) { return cache_rules_should_store(m_pRules, zDefaultDb, pQuery); @@ -216,3 +268,32 @@ cache_result_t Cache::delValue(const char* pKey) { return m_pStorage->delValue(pKey); } + +// protected +long Cache::hashOfKey(const char* pKey) +{ + return hash_of_key(pKey); +} + +// protected +bool Cache::mustRefresh(long key, const SessionCache* pSessionCache) +{ + void *pValue = hashtable_fetch(m_pPending, (void*)key); + if (!pValue) + { + // It's not being fetched, so we make a note that we are. + hashtable_add(m_pPending, (void*)key, (void*)pSessionCache); + } + + return !pValue; +} + +// protected +void Cache::refreshed(long key, const SessionCache* pSessionCache) +{ + ss_dassert(hashtable_fetch(m_pPending, (void*)key) == pSessionCache); + ss_debug(int n =) hashtable_delete(m_pPending, (void*)key); + ss_dassert(n == 1); +} + + diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index 105b5f39b..eb002f4de 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -55,7 +55,7 @@ public: * * @return True, if the session cache should refresh the data. */ - bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); + virtual bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); /** * To inform the cache that a particular item has been updated upon request. @@ -63,7 +63,7 @@ public: * @param pKey The hashed key for a query. * @param pSessionCache The session cache informing. */ - void refreshed(const char* pKey, const SessionCache* pSessionCache); + virtual void refreshed(const char* pKey, const SessionCache* pSessionCache); const CACHE_CONFIG& config() const { return m_config; } @@ -75,7 +75,7 @@ public: cache_result_t delValue(const char* pKey); -private: +protected: Cache(const char* zName, CACHE_CONFIG& config, CACHE_RULES* pRules, @@ -83,11 +83,22 @@ private: Storage* pStorage, HASHTABLE* pPending); + static bool Create(const CACHE_CONFIG& config, + CACHE_RULES** ppRules, + StorageFactory** ppFactory, + HASHTABLE** ppPending); + + long hashOfKey(const char* pKey); + + bool mustRefresh(long key, const SessionCache* pSessionCache); + + void refreshed(long key, const SessionCache* pSessionCache); + private: Cache(const Cache&); Cache& operator = (const Cache&); -private: +protected: const char* m_zName; // The name of the instance; the section name in the config. CACHE_CONFIG m_config; // The configuration of the cache instance. CACHE_RULES* m_pRules; // The rules of the cache instance. diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index d1b621869..ac03179a7 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -17,7 +17,7 @@ #include #include #include -#include "cache.h" +#include "cachemt.h" #include "sessioncache.h" static char VERSION_STRING[] = "V1.0.0"; @@ -48,11 +48,6 @@ static uint64_t getCapabilities(void); static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config); -#define CPP_GUARD(statement)\ - do { try { statement; } \ - catch (const std::exception& x) { MXS_ERROR("Caught standard exception: %s", x.what()); }\ - catch (...) { MXS_ERROR("Caught unknown exception."); } } while (false) - // // Global symbols of the Module // @@ -124,7 +119,7 @@ static FILTER *createInstance(const char* zName, char** pzOptions, FILTER_PARAME if (process_params(pzOptions, ppParams, config)) { - CPP_GUARD(pCache = Cache::Create(zName, config)); + CPP_GUARD(pCache = CacheMT::Create(zName, config)); if (!pCache) { diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index 4f9fc4365..6fcdca1d1 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -61,4 +61,9 @@ void cache_config_finish(CACHE_CONFIG& config); void cache_config_free(CACHE_CONFIG* pConfig); void cache_config_reset(CACHE_CONFIG& config); +#define CPP_GUARD(statement)\ + do { try { statement; } \ + catch (const std::exception& x) { MXS_ERROR("Caught standard exception: %s", x.what()); }\ + catch (...) { MXS_ERROR("Caught unknown exception."); } } while (false) + #endif diff --git a/server/modules/filter/cache/cachemt.cc b/server/modules/filter/cache/cachemt.cc new file mode 100644 index 000000000..6fcc0e5cf --- /dev/null +++ b/server/modules/filter/cache/cachemt.cc @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "cachemt.h" +#include +#include +#include "storage.h" +#include "storagefactory.h" + +CacheMT::CacheMT(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending) + : Cache(zName, config, pRules, pFactory, pStorage, pPending) +{ + spinlock_init(&m_lockPending); +} + +CacheMT::~CacheMT() +{ +} + +CacheMT* CacheMT::Create(const char* zName, CACHE_CONFIG& config) +{ + CacheMT* pCache = NULL; + + CACHE_RULES* pRules = NULL; + HASHTABLE* pPending = NULL; + StorageFactory* pFactory = NULL; + + if (Cache::Create(config, &pRules, &pFactory, &pPending)) + { + uint32_t ttl = config.ttl; + int argc = config.storage_argc; + char** argv = config.storage_argv; + + Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); + + if (pStorage) + { + CPP_GUARD(pCache = new CacheMT(zName, + config, + pRules, + pFactory, + pStorage, + pPending)); + + if (!pCache) + { + cache_rules_free(pRules); + hashtable_free(pPending); + delete pStorage; + delete pFactory; + } + } + } + + return pCache; +} + +bool CacheMT::mustRefresh(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hashOfKey(pKey); + + spinlock_acquire(&m_lockPending); + bool rv = Cache::mustRefresh(key, pSessionCache); + spinlock_release(&m_lockPending); + + return rv; +} + +void CacheMT::refreshed(const char* pKey, const SessionCache* pSessionCache) +{ + long key = hashOfKey(pKey); + + spinlock_acquire(&m_lockPending); + Cache::refreshed(key, pSessionCache); + spinlock_release(&m_lockPending); +} diff --git a/server/modules/filter/cache/cachemt.h b/server/modules/filter/cache/cachemt.h new file mode 100644 index 000000000..315224a9e --- /dev/null +++ b/server/modules/filter/cache/cachemt.h @@ -0,0 +1,43 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include "cache.h" + +class CacheMT : public Cache +{ +public: + ~CacheMT(); + + static CacheMT* Create(const char* zName, CACHE_CONFIG& config); + + bool mustRefresh(const char* pKey, const SessionCache* pSessionCache); + + void refreshed(const char* pKey, const SessionCache* pSessionCache); + +private: + CacheMT(const char* zName, + CACHE_CONFIG& config, + CACHE_RULES* pRules, + StorageFactory* pFactory, + Storage* pStorage, + HASHTABLE* pPending); + +private: + CacheMT(const CacheMT&); + CacheMT& operator = (const CacheMT&); + +private: + SPINLOCK m_lockPending; // Lock used for protecting 'pending'. +}; diff --git a/server/modules/filter/cache/storage.cc b/server/modules/filter/cache/storage.cc index c50084fed..626dff108 100644 --- a/server/modules/filter/cache/storage.cc +++ b/server/modules/filter/cache/storage.cc @@ -23,6 +23,10 @@ Storage::Storage(CACHE_STORAGE_API* pApi, CACHE_STORAGE* pStorage) ss_dassert(m_pStorage); } +Storage::~Storage() +{ +} + cache_result_t Storage::getKey(const char* zDefaultDb, const GWBUF* pQuery, char* pKey) From ff3173d58807b96debe358fa06e36aa2eb566dcd Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 25 Nov 2016 13:44:41 +0200 Subject: [PATCH 44/48] Cache: Remove obsolete method --- server/modules/filter/cache/cache.cc | 71 ---------------------------- server/modules/filter/cache/cache.h | 2 - 2 files changed, 73 deletions(-) diff --git a/server/modules/filter/cache/cache.cc b/server/modules/filter/cache/cache.cc index 138be8d27..cefdde9a7 100644 --- a/server/modules/filter/cache/cache.cc +++ b/server/modules/filter/cache/cache.cc @@ -81,77 +81,6 @@ Cache::~Cache() ss_dassert(false); } -//static -Cache* Cache::Create(const char* zName, CACHE_CONFIG& config) -{ - Cache* pCache = NULL; - - CACHE_RULES* pRules = NULL; - - if (config.rules) - { - pRules = cache_rules_load(config.rules, config.debug); - } - else - { - pRules = cache_rules_create(config.debug); - } - - if (pRules) - { - HASHTABLE* pPending = hashtable_alloc(CACHE_PENDING_ITEMS, hashfn, hashcmp); - - if (pPending) - { - StorageFactory *pFactory = StorageFactory::Open(config.storage); - - if (pFactory) - { - uint32_t ttl = config.ttl; - int argc = config.storage_argc; - char** argv = config.storage_argv; - - Storage* pStorage = pFactory->createStorage(zName, ttl, argc, argv); - - if (pStorage) - { - pCache = new (std::nothrow) Cache(zName, - config, - pRules, - pFactory, - pStorage, - pPending); - } - else - { - MXS_ERROR("Could not create storage instance for '%s'.", zName); - } - } - else - { - MXS_ERROR("Could not open storage factory '%s'.", config.storage); - } - - if (!pCache) - { - delete pFactory; - } - } - - if (!pCache) - { - hashtable_free(pPending); - } - } - - if (!pCache) - { - cache_rules_free(pRules); - } - - return pCache; -} - //static bool Cache::Create(const CACHE_CONFIG& config, CACHE_RULES** ppRules, diff --git a/server/modules/filter/cache/cache.h b/server/modules/filter/cache/cache.h index eb002f4de..8de701fa9 100644 --- a/server/modules/filter/cache/cache.h +++ b/server/modules/filter/cache/cache.h @@ -26,8 +26,6 @@ class Cache public: ~Cache(); - static Cache* Create(const char* zName, CACHE_CONFIG& config); - /** * Returns whether the results of a particular query should be stored. * From 6ea4e50f2cf6731f22a2ce2ea6aff643a6b12352 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 25 Nov 2016 12:15:52 +0200 Subject: [PATCH 45/48] Clean up service.h Most of the service header functions now contain the relevant documentation. Cleaned up small parts of it and renamed functions to be more consistent. --- include/maxscale/service.h | 231 ++++++++++++------ server/core/config_runtime.c | 2 +- server/core/gateway.cc | 2 +- server/core/service.c | 113 ++++----- server/modules/routing/debugcli/debugcmd.c | 2 +- server/modules/routing/maxinfo/maxinfo_exec.c | 2 +- 6 files changed, 208 insertions(+), 144 deletions(-) diff --git a/include/maxscale/service.h b/include/maxscale/service.h index fab8f3128..723d1259d 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -98,10 +98,10 @@ typedef struct typedef struct server_ref_t { struct server_ref_t *next; /**< Next server reference */ - SERVER* server; /**< The actual server */ - int weight; /**< Weight of this server */ - int connections; /**< Number of connections created through this reference */ - bool active; /**< Whether this reference is valid and in use*/ + SERVER* server; /**< The actual server */ + int weight; /**< Weight of this server */ + int connections; /**< Number of connections created through this reference */ + bool active; /**< Whether this reference is valid and in use*/ } SERVER_REF; /** Macro to check whether a SERVER_REF is active */ @@ -119,8 +119,8 @@ typedef struct server_ref_t #define SERVICE_PARAM_UNINIT -1 /* Refresh rate limits for load users from database */ -#define USERS_REFRESH_TIME 30 /* Allowed time interval (in seconds) after last update*/ -#define USERS_REFRESH_MAX_PER_TIME 4 /* Max number of load calls within the time interval */ +#define USERS_REFRESH_TIME 30 /* Allowed time interval (in seconds) after last update*/ +#define USERS_REFRESH_MAX_PER_TIME 4 /* Max number of load calls within the time interval */ /** Default timeout values used by the connections which fetch user authentication data */ #define DEFAULT_AUTH_CONNECT_TIMEOUT 3 @@ -142,25 +142,24 @@ typedef struct service int max_connections; /**< Maximum client connections */ QUEUE_CONFIG *queued_connections; /**< Queued connections, if set */ SERV_LISTENER *ports; /**< Linked list of ports and protocols - * that this service will listen on. - */ + * that this service will listen on */ char *routerModule; /**< Name of router module to use */ char **routerOptions; /**< Router specific option strings */ struct router_object *router; /**< The router we are using */ void *router_instance; /**< The router instance for this service */ - char *version_string; /** version string for this service listeners */ - SERVER_REF *dbref; /** server references */ - int n_dbref; /** Number of server references */ + char *version_string; /**< version string for this service listeners */ + SERVER_REF *dbref; /**< server references */ + int n_dbref; /**< Number of server references */ SERVICE_USER credentials; /**< The cedentials of the service user */ SPINLOCK spin; /**< The service spinlock */ SERVICE_STATS stats; /**< The service statistics */ int enable_root; /**< Allow root user access */ int localhost_match_wildcard_host; /**< Match localhost against wildcard */ - CONFIG_PARAMETER* svc_config_param;/*< list of config params and values */ - int svc_config_version; /*< Version number of configuration */ - bool svc_do_shutdown; /*< tells the service to exit loops etc. */ - bool users_from_all; /*< Load users from one server or all of them */ - bool strip_db_esc; /*< Remove the '\' characters from database names + CONFIG_PARAMETER* svc_config_param;/**< list of config params and values */ + int svc_config_version; /**< Version number of configuration */ + bool svc_do_shutdown; /**< tells the service to exit loops etc. */ + bool users_from_all; /**< Load users from one server or all of them */ + bool strip_db_esc; /**< Remove the '\' characters from database names * when querying them from the server. MySQL Workbench seems * to escape at least the underscore character. */ SPINLOCK users_table_spin; /**< The spinlock for users data refresh */ @@ -168,11 +167,11 @@ typedef struct service FILTER_DEF **filters; /**< Ordered list of filters */ int n_filters; /**< Number of filters */ long conn_idle_timeout; /**< Session timeout in seconds */ - char *weightby; + char *weightby; /**< Service weighting parameter name */ struct service *next; /**< The next service in the linked list */ - bool retry_start; /*< If starting of the service should be retried later */ - bool log_auth_warnings; /*< Log authentication failures and warnings */ - uint64_t capabilities; /*< The capabilities of the service. */ + bool retry_start; /**< If starting of the service should be retried later */ + bool log_auth_warnings; /**< Log authentication failures and warnings */ + uint64_t capabilities; /**< The capabilities of the service. */ } SERVICE; typedef enum count_spec_t @@ -188,60 +187,144 @@ typedef enum count_spec_t #define SERVICE_STATE_FAILED 3 /**< The service failed to start */ #define SERVICE_STATE_STOPPED 4 /**< The service has been stopped */ -extern SERVICE *service_alloc(const char *, const char *); -extern int service_free(SERVICE *); -extern SERVICE *service_find(const char *); -extern int service_isvalid(SERVICE *); -extern SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const char *protocol, - const char *address, unsigned short port, const char *authenticator, - const char *options, SSL_LISTENER *ssl); -extern int serviceHasProtocol(SERVICE *service, const char *protocol, - const char* address, unsigned short port); -extern void serviceAddBackend(SERVICE *, SERVER *); -extern void serviceRemoveBackend(SERVICE *, const SERVER *); -extern bool serviceHasBackend(SERVICE *, SERVER *); -extern void serviceAddRouterOption(SERVICE *, char *); -extern void serviceClearRouterOptions(SERVICE *); -extern int serviceStart(SERVICE *); -extern int serviceStartAll(); -extern bool serviceListen(SERVICE *service, SERV_LISTENER *port); -extern int serviceStop(SERVICE *); -extern int serviceRestart(SERVICE *); -extern int serviceSetUser(SERVICE *, char *, char *); -extern int serviceGetUser(SERVICE *, char **, char **); -extern bool serviceSetFilters(SERVICE *, char *); -extern int serviceSetSSL(SERVICE *service, char* action); -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 int serviceSetConnectionLimits(SERVICE *, int, int, int); -extern void serviceSetRetryOnFailure(SERVICE *service, char* value); -extern void serviceWeightBy(SERVICE *, char *); -extern char *serviceGetWeightingParameter(SERVICE *); -extern int serviceEnableLocalhostMatchWildcardHost(SERVICE *, int); -extern int serviceStripDbEsc(SERVICE* service, int action); -extern int serviceAuthAllServers(SERVICE *service, int action); -extern void service_update(SERVICE *, char *, char *, char *); -extern int service_refresh_users(SERVICE *); -extern void printService(SERVICE *); -extern void printAllServices(); -extern void dprintAllServices(DCB *); -extern bool service_set_param_value(SERVICE* service, - CONFIG_PARAMETER* param, - char* valstr, - count_spec_t count_spec, - config_param_type_t type); -extern void dprintService(DCB *, SERVICE *); -extern void dListServices(DCB *); -extern void dListListeners(DCB *); -extern char* service_get_name(SERVICE* svc); -extern void service_shutdown(); -extern int serviceSessionCountAll(); -extern RESULTSET *serviceGetList(); -extern RESULTSET *serviceGetListenerList(); -extern bool service_all_services_have_listeners(); +/** + * Service life cycle management + * + * These functions should only be called by the MaxScale core. + */ + +/** + * @brief Allocate a new service + * + * @param name The service name + * @param router The router module this service uses + * + * @return The newly created service or NULL if an error occurred + */ +SERVICE* service_alloc(const char *name, const char *router); + +/** + * @brief Free the specified service + * + * @param service The service to free + */ +void service_free(SERVICE *service); + +/** + * @brief Shut all services down + * + * Stops all services and calls the destroyInstance entry points for all routers + * and filter. This should only be called once by the main shutdown code. + */ +void service_shutdown(void); + +/** + * @brief Launch all services + * + * Initialize and start all services. This should only be called once by the + * main initialization code. + * + * @return Number of successfully started services + */ +int service_launch_all(void); + +/** + * Creating and adding new components to services + */ +SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, + const char *protocol, const char *address, + unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl); +int serviceHasProtocol(SERVICE *service, const char *protocol, + const char* address, unsigned short port); +void serviceAddBackend(SERVICE *service, SERVER *server); +void serviceRemoveBackend(SERVICE *service, const SERVER *server); +bool serviceHasBackend(SERVICE *service, SERVER *server); + +/** + * Starting and stopping services + */ + +/** + * @brief Stop a service + * + * @param service Service to stop + * @return True if service was stopped + */ +bool serviceStop(SERVICE *service); + +/** + * @brief Restart a stopped service + * + * @param service Service to restart + * @return True if service was restarted + */ +bool serviceStart(SERVICE *service); + +/** + * @brief Start a listener for a service + * + * @param service Service where the listener is linked + * @param port Listener to start + * @return True if listener was started + */ +bool serviceStartListener(SERVICE *service, SERV_LISTENER *port); + +/** + * @brief Stop a listener for a service + * + * @param service Service where the listener is linked + * @param name Name of the listener + * @return True if listener was stopped + */ +bool serviceStopListener(SERVICE *service, const char *name); + +/** + * Utility functions + */ +char* service_get_name(SERVICE* service); +bool service_all_services_have_listeners(void); +SERVICE* service_find(const char *name); +int service_isvalid(SERVICE *service); + +/** + * Alteration of the service configuration + */ +void serviceAddRouterOption(SERVICE *service, char *option); +void serviceClearRouterOptions(SERVICE *service); +int serviceSetUser(SERVICE *service, char *user, char *auth); +int serviceGetUser(SERVICE *service, char **user, char **auth); +bool serviceSetFilters(SERVICE *service, char *filters); +int serviceSetSSL(SERVICE *service, char* action); +int serviceSetSSLVersion(SERVICE *service, char* version); +int serviceSetSSLVerifyDepth(SERVICE* service, int depth); +void serviceSetCertificates(SERVICE *service, char* cert, char* key, char* ca_cert); +int serviceEnableRootUser(SERVICE *service, int action); +int serviceSetTimeout(SERVICE *service, int val); +int serviceSetConnectionLimits(SERVICE *service, int max, int queued, int timeout); +void serviceSetRetryOnFailure(SERVICE *service, char* value); +void serviceWeightBy(SERVICE *service, char *weightby); +char* serviceGetWeightingParameter(SERVICE *service); +int serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action); +int serviceStripDbEsc(SERVICE* service, int action); +int serviceAuthAllServers(SERVICE *service, int action); +void service_update(SERVICE *service, char *router, char *user, char *auth); +int service_refresh_users(SERVICE *service); +bool service_set_param_value(SERVICE* service, CONFIG_PARAMETER* param, char* valstr, + count_spec_t count_spec, config_param_type_t type); + +/** + * Diagnostics + */ +void printService(SERVICE *service); +void printAllServices(void); +void dprintAllServices(DCB *dcb); +void dprintService(DCB *dcb, SERVICE *service); +void dListServices(DCB *dcb); +void dListListeners(DCB *dcb); +int serviceSessionCountAll(void); +RESULTSET* serviceGetList(void); +RESULTSET* serviceGetListenerList(void); /** * Get the capabilities of the servive. diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index 54c60550d..f7d56628d 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -414,7 +414,7 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr, u_port, auth, auth_opt, ssl); - if (listener && listener_serialize(listener) && serviceListen(service, listener)) + if (listener && listener_serialize(listener) && serviceStartListener(service, listener)) { MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created", name, print_addr, port, service->name); diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 9d6a2e28d..f67f8c36c 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -1951,7 +1951,7 @@ int main(int argc, char **argv) monitorStartAll(); /** Start the services that were created above */ - n_services = serviceStartAll(); + n_services = service_launch_all(); if (n_services == 0) { diff --git a/server/core/service.c b/server/core/service.c index 66e3da276..128f018f8 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -103,32 +103,21 @@ static void service_internal_restart(void *data); static void service_queue_check(void *data); static void service_calculate_weights(SERVICE *service); -/** - * Allocate a new service for the gateway to support - * - * - * @param servname The service name - * @param router Name of the router module this service uses - * - * @return The newly created service or NULL if an error occurred - */ -SERVICE * -service_alloc(const char *servname, const char *router) +SERVICE* service_alloc(const char *name, const char *router) { - servname = MXS_STRDUP(servname); - router = MXS_STRDUP(router); - + char *my_name = MXS_STRDUP(name); + char *my_router = MXS_STRDUP(router); SERVICE *service = (SERVICE *)MXS_CALLOC(1, sizeof(*service)); - if (!servname || !router || !service) + if (!my_name || !my_router || !service) { - MXS_FREE((void*)servname); - MXS_FREE((void*)router); + MXS_FREE(my_name); + MXS_FREE(my_router); MXS_FREE(service); return NULL; } - if ((service->router = load_module(router, MODULE_ROUTER)) == NULL) + if ((service->router = load_module(my_router, MODULE_ROUTER)) == NULL) { char* home = get_libdir(); char* ldpath = getenv("LD_LIBRARY_PATH"); @@ -138,13 +127,13 @@ service_alloc(const char *servname, const char *router) "following directories :\n\t\t\t " "- %s\n%s%s", MODULE_ROUTER, - router, - router, + my_router, + my_router, home, ldpath ? "\t\t\t - " : "", ldpath ? ldpath : ""); - MXS_FREE((void*)servname); - MXS_FREE((void*)router); + MXS_FREE(my_name); + MXS_FREE(my_router); MXS_FREE(service); return NULL; } @@ -152,8 +141,8 @@ service_alloc(const char *servname, const char *router) service->capabilities = service->router->getCapabilities(); service->client_count = 0; service->n_dbref = 0; - service->name = (char*)servname; - service->routerModule = (char*)router; + service->name = my_name; + service->routerModule = my_router; service->users_from_all = false; service->queued_connections = NULL; service->localhost_match_wildcard_host = SERVICE_PARAM_UNINIT; @@ -478,8 +467,7 @@ static void free_string_array(char** array) * @param service The Service that should be started * @return Returns the number of listeners created */ -int -serviceStart(SERVICE *service) +int serviceInitialize(SERVICE *service) { /** Calculate the server weights */ service_calculate_weights(service); @@ -502,24 +490,36 @@ serviceStart(SERVICE *service) return listeners; } -/** - * Start an individual listener - * - * @param service The service to start the listener for - * @param port The port number - */ -bool serviceListen(SERVICE *service, SERV_LISTENER *port) +bool serviceStartListener(SERVICE *service, SERV_LISTENER *port) { return serviceStartPort(service, port); } -/** - * Start all the services - * - * @return Return the number of services started - */ -int -serviceStartAll() +bool serviceStopListener(SERVICE *service, const char *name) +{ + bool rval = false; + + spinlock_acquire(&service->spin); + + for (SERV_LISTENER *port = service->ports; port; port = port->next) + { + if (strcmp(port->name, name) == 0) + { + if (poll_remove_dcb(port->listener) == 0) + { + port->listener->session->state = SESSION_STATE_LISTENER_STOPPED; + rval = true; + } + break; + } + } + + spinlock_release(&service->spin); + + return rval; +} + +int service_launch_all() { SERVICE *ptr; int n = 0, i; @@ -530,7 +530,7 @@ serviceStartAll() ptr = allServices; while (ptr && !ptr->svc_do_shutdown) { - n += (i = serviceStart(ptr)); + n += (i = serviceInitialize(ptr)); if (i == 0) { @@ -543,16 +543,7 @@ serviceStartAll() return error ? 0 : n; } -/** - * Stop a service - * - * This function stops the listener for the service - * - * @param service The Service that should be stopped - * @return Returns the number of listeners restarted - */ -int -serviceStop(SERVICE *service) +bool serviceStop(SERVICE *service) { SERV_LISTENER *port; int listeners = 0; @@ -572,7 +563,7 @@ serviceStop(SERVICE *service) } service->state = SERVICE_STATE_STOPPED; - return listeners; + return listeners > 0; } /** @@ -583,8 +574,7 @@ serviceStop(SERVICE *service) * @param service The Service that should be restarted * @return Returns the number of listeners restarted */ -int -serviceRestart(SERVICE *service) +bool serviceStart(SERVICE *service) { SERV_LISTENER *port; int listeners = 0; @@ -603,24 +593,16 @@ serviceRestart(SERVICE *service) port = port->next; } service->state = SERVICE_STATE_STARTED; - return listeners; + return listeners > 0; } - -/** - * Deallocate the specified service - * - * @param service The service to deallocate - * @return Returns true if the service was freed - */ -int -service_free(SERVICE *service) +void service_free(SERVICE *service) { SERVICE *ptr; SERVER_REF *srv; if (service->stats.n_current) { - return 0; + return; } /* First of all remove from the linked list */ spinlock_acquire(&service_spin); @@ -661,7 +643,6 @@ service_free(SERVICE *service) serviceClearRouterOptions(service); MXS_FREE(service); - return 1; } /** diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index a63b81603..34907326c 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1776,7 +1776,7 @@ shutdown_service(DCB *dcb, SERVICE *service) static void restart_service(DCB *dcb, SERVICE *service) { - serviceRestart(service); + serviceStart(service); } /** diff --git a/server/modules/routing/maxinfo/maxinfo_exec.c b/server/modules/routing/maxinfo/maxinfo_exec.c index d8a990c3d..a82433b1b 100644 --- a/server/modules/routing/maxinfo/maxinfo_exec.c +++ b/server/modules/routing/maxinfo/maxinfo_exec.c @@ -694,7 +694,7 @@ void exec_restart_service(DCB *dcb, MAXINFO_TREE *tree) SERVICE* service = service_find(tree->value); if (service) { - serviceRestart(service); + serviceStart(service); maxinfo_send_ok(dcb); } else From 88677946f851886ed7d4592e72060a1764492235 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 25 Nov 2016 14:48:11 +0200 Subject: [PATCH 46/48] Add destruction of listeners The listeners aren't really destroyed and are only stopped. Further changes are required so that they won't be started again once they have been destroyed. --- include/maxscale/config_runtime.h | 13 ++++++ include/maxscale/service.h | 4 +- server/core/config_runtime.c | 53 +++++++++++++++++++++- server/core/service.c | 2 +- server/modules/routing/debugcli/debugcmd.c | 18 ++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/include/maxscale/config_runtime.h b/include/maxscale/config_runtime.h index 8384ebbb9..06ae263a8 100644 --- a/include/maxscale/config_runtime.h +++ b/include/maxscale/config_runtime.h @@ -144,3 +144,16 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add const char *auth_opt, const char *ssl_key, const char *ssl_cert, const char *ssl_ca, const char *ssl_version, const char *ssl_depth); + +/** + * @brief Destroy a listener + * + * This disables the listener by removing it from the polling system. It also + * removes any generated configurations for this listener. + * + * @param service Service where the listener exists + * @param name Name of the listener + * + * @return True if the listener was successfully destroyed + */ +bool runtime_destroy_listener(SERVICE *service, const char *name); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 723d1259d..caa773625 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -262,13 +262,13 @@ bool serviceStop(SERVICE *service); bool serviceStart(SERVICE *service); /** - * @brief Start a listener for a service + * @brief Start new a listener for a service * * @param service Service where the listener is linked * @param port Listener to start * @return True if listener was started */ -bool serviceStartListener(SERVICE *service, SERV_LISTENER *port); +bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port); /** * @brief Stop a listener for a service diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index f7d56628d..c5acb8fdd 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -412,9 +412,9 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add { const char *print_addr = addr ? addr : "0.0.0.0"; SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr, - u_port, auth, auth_opt, ssl); + u_port, auth, auth_opt, ssl); - if (listener && listener_serialize(listener) && serviceStartListener(service, listener)) + if (listener && listener_serialize(listener) && serviceLaunchListener(service, listener)) { MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created", name, print_addr, port, service->name); @@ -429,3 +429,52 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add spinlock_release(&crt_lock); return rval; } + +bool runtime_destroy_listener(SERVICE *service, const char *name) +{ + bool rval = false; + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), name); + + spinlock_acquire(&crt_lock); + + if (unlink(filename) == -1) + { + if (errno != ENOENT) + { + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to remove persisted listener configuration '%s': %d, %s", + filename, errno, strerror_r(errno, err, sizeof(err))); + } + else + { + rval = false; + MXS_WARNING("Listener '%s' was not created at runtime. Remove the " + "listener manually from the correct configuration file.", + name); + } + } + else + { + rval = true; + } + + if (rval) + { + rval = serviceStopListener(service, name); + + if (rval) + { + MXS_NOTICE("Destroyed listener '%s' for service '%s'. The listener " + "will be removed after the next restart of MaxScale.", + name, service->name); + } + else + { + MXS_ERROR("Failed to destroy listener '%s' for service '%s'", name, service->name); + } + } + + spinlock_release(&crt_lock); + return rval; +} diff --git a/server/core/service.c b/server/core/service.c index 128f018f8..3df2e19af 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -490,7 +490,7 @@ int serviceInitialize(SERVICE *service) return listeners; } -bool serviceStartListener(SERVICE *service, SERV_LISTENER *port) +bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port) { return serviceStartPort(service, port); } diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 34907326c..fb584b268 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -1111,6 +1111,18 @@ static void destroyServer(DCB *dcb, SERVER *server) } } +static void destroyListener(DCB *dcb, SERVICE *service, const char *name) +{ + if (runtime_destroy_listener(service, name)) + { + dcb_printf(dcb, "Destroyed listener '%s'\n", name); + } + else + { + dcb_printf(dcb, "Failed to destroy listener '%s', see log file for more details\n", name); + } +} + struct subcommand destroyoptions[] = { { @@ -1119,6 +1131,12 @@ struct subcommand destroyoptions[] = "Usage: destroy server NAME", {ARG_TYPE_SERVER} }, + { + "listener", 2, 2, destroyListener, + "Destroy a listener", + "Usage: destroy listener SERVICE NAME", + {ARG_TYPE_SERVICE, ARG_TYPE_STRING} + }, { EMPTY_OPTION } From 691989ff04ae607417ea2b49886c150605a071ba Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 25 Nov 2016 15:10:19 +0200 Subject: [PATCH 47/48] Split the service header into internal and external parts The service header in include/maxscale/ contains the public part of the service API. These functions can be safely used by the modules. The internal header located in service/core/maxscale/ is used by the core to initialize MaxScale at startup or to provide other services in a more controlled way (the config_runtime, for example). --- include/maxscale/service.h | 99 +------------- server/core/config.c | 3 +- server/core/config_runtime.c | 2 + server/core/gateway.cc | 4 +- server/core/maxscale/service.h | 121 ++++++++++++++++++ server/core/service.c | 3 +- server/core/test/testservice.c | 2 +- .../modules/routing/binlog/test/testbinlog.c | 4 +- 8 files changed, 137 insertions(+), 101 deletions(-) create mode 100644 server/core/maxscale/service.h diff --git a/include/maxscale/service.h b/include/maxscale/service.h index caa773625..9beb1b717 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -187,60 +187,6 @@ typedef enum count_spec_t #define SERVICE_STATE_FAILED 3 /**< The service failed to start */ #define SERVICE_STATE_STOPPED 4 /**< The service has been stopped */ -/** - * Service life cycle management - * - * These functions should only be called by the MaxScale core. - */ - -/** - * @brief Allocate a new service - * - * @param name The service name - * @param router The router module this service uses - * - * @return The newly created service or NULL if an error occurred - */ -SERVICE* service_alloc(const char *name, const char *router); - -/** - * @brief Free the specified service - * - * @param service The service to free - */ -void service_free(SERVICE *service); - -/** - * @brief Shut all services down - * - * Stops all services and calls the destroyInstance entry points for all routers - * and filter. This should only be called once by the main shutdown code. - */ -void service_shutdown(void); - -/** - * @brief Launch all services - * - * Initialize and start all services. This should only be called once by the - * main initialization code. - * - * @return Number of successfully started services - */ -int service_launch_all(void); - -/** - * Creating and adding new components to services - */ -SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, - const char *protocol, const char *address, - unsigned short port, const char *authenticator, - const char *options, SSL_LISTENER *ssl); -int serviceHasProtocol(SERVICE *service, const char *protocol, - const char* address, unsigned short port); -void serviceAddBackend(SERVICE *service, SERVER *server); -void serviceRemoveBackend(SERVICE *service, const SERVER *server); -bool serviceHasBackend(SERVICE *service, SERVER *server); - /** * Starting and stopping services */ @@ -282,23 +228,14 @@ bool serviceStopListener(SERVICE *service, const char *name); /** * Utility functions */ -char* service_get_name(SERVICE* service); -bool service_all_services_have_listeners(void); SERVICE* service_find(const char *name); -int service_isvalid(SERVICE *service); -/** - * Alteration of the service configuration - */ -void serviceAddRouterOption(SERVICE *service, char *option); -void serviceClearRouterOptions(SERVICE *service); -int serviceSetUser(SERVICE *service, char *user, char *auth); +// TODO: Change binlogrouter to use the functions in config_runtime.h +void serviceAddBackend(SERVICE *service, SERVER *server); + int serviceGetUser(SERVICE *service, char **user, char **auth); +int serviceSetUser(SERVICE *service, char *user, char *auth); bool serviceSetFilters(SERVICE *service, char *filters); -int serviceSetSSL(SERVICE *service, char* action); -int serviceSetSSLVersion(SERVICE *service, char* version); -int serviceSetSSLVerifyDepth(SERVICE* service, int depth); -void serviceSetCertificates(SERVICE *service, char* cert, char* key, char* ca_cert); int serviceEnableRootUser(SERVICE *service, int action); int serviceSetTimeout(SERVICE *service, int val); int serviceSetConnectionLimits(SERVICE *service, int max, int queued, int timeout); @@ -308,16 +245,11 @@ char* serviceGetWeightingParameter(SERVICE *service); int serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action); int serviceStripDbEsc(SERVICE* service, int action); int serviceAuthAllServers(SERVICE *service, int action); -void service_update(SERVICE *service, char *router, char *user, char *auth); int service_refresh_users(SERVICE *service); -bool service_set_param_value(SERVICE* service, CONFIG_PARAMETER* param, char* valstr, - count_spec_t count_spec, config_param_type_t type); /** * Diagnostics */ -void printService(SERVICE *service); -void printAllServices(void); void dprintAllServices(DCB *dcb); void dprintService(DCB *dcb, SERVICE *service); void dListServices(DCB *dcb); @@ -339,27 +271,4 @@ static inline uint64_t service_get_capabilities(const SERVICE *service) return service->capabilities; } -/** - * Check if a service uses @c servers - * @param server Server that is queried - * @return True if server is used by at least one service - */ -bool service_server_in_use(const SERVER *server); - -/** - * @brief Serialize a service to a file - * - * This partially converts @c service into an INI format file. Only the servers - * of the service are serialized. This allows the service to keep using the servers - * added at runtime even after a restart. - * - * NOTE: This does not persist the complete service configuration and requires - * that an existing service configuration is in the main configuration file. - * Changes to service parameters are not persisted. - * - * @param service Service to serialize - * @return False if the serialization of the service fails, true if it was successful - */ -bool service_serialize_servers(const SERVICE *service); - MXS_END_DECLS diff --git a/server/core/config.c b/server/core/config.c index 58d01c8cb..3c60038ae 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -62,11 +62,12 @@ #include #include #include -#include #include #include #include +#include "maxscale/service.h" + typedef struct duplicate_context { HASHTABLE *hash; diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index c5acb8fdd..7e2619646 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -17,6 +17,8 @@ #include #include +#include "maxscale/service.h" + static SPINLOCK crt_lock = SPINLOCK_INIT; bool runtime_link_server(SERVER *server, const char *target) diff --git a/server/core/gateway.cc b/server/core/gateway.cc index f67f8c36c..7f7a9b10e 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -66,14 +66,14 @@ #include #include #include -#include -#include #include #include #include #include #include +#include "maxscale/service.h" + #define STRING_BUFFER_SIZE 1024 #define PIDFD_CLOSED -1 diff --git a/server/core/maxscale/service.h b/server/core/maxscale/service.h new file mode 100644 index 000000000..aa17a1e22 --- /dev/null +++ b/server/core/maxscale/service.h @@ -0,0 +1,121 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include + +MXS_BEGIN_DECLS + +/** + * @file service.h - MaxScale internal service functions + */ + +/** + * Service life cycle management + * + * These functions should only be called by the MaxScale core. + */ + +/** + * @brief Allocate a new service + * + * @param name The service name + * @param router The router module this service uses + * + * @return The newly created service or NULL if an error occurred + */ +SERVICE* service_alloc(const char *name, const char *router); + +/** + * @brief Free the specified service + * + * @param service The service to free + */ +void service_free(SERVICE *service); + +/** + * @brief Shut all services down + * + * Stops all services and calls the destroyInstance entry points for all routers + * and filter. This should only be called once by the main shutdown code. + */ +void service_shutdown(void); + +/** + * @brief Launch all services + * + * Initialize and start all services. This should only be called once by the + * main initialization code. + * + * @return Number of successfully started services + */ +int service_launch_all(void); + +/** + * Creating and adding new components to services + */ +SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, + const char *protocol, const char *address, + unsigned short port, const char *authenticator, + const char *options, SSL_LISTENER *ssl); +int serviceHasProtocol(SERVICE *service, const char *protocol, + const char* address, unsigned short port); +void serviceRemoveBackend(SERVICE *service, const SERVER *server); +bool serviceHasBackend(SERVICE *service, SERVER *server); + +/** + * @brief Serialize a service to a file + * + * This partially converts @c service into an INI format file. Only the servers + * of the service are serialized. This allows the service to keep using the servers + * added at runtime even after a restart. + * + * NOTE: This does not persist the complete service configuration and requires + * that an existing service configuration is in the main configuration file. + * Changes to service parameters are not persisted. + * + * @param service Service to serialize + * @return False if the serialization of the service fails, true if it was successful + */ +bool service_serialize_servers(const SERVICE *service); + +/** + * Internal utility functions + */ +char* service_get_name(SERVICE* service); +bool service_all_services_have_listeners(void); +int service_isvalid(SERVICE *service); + +/** + * Check if a service uses @c servers + * @param server Server that is queried + * @return True if server is used by at least one service + */ +bool service_server_in_use(const SERVER *server); + +/** + * Alteration of the service configuration + */ +void serviceAddRouterOption(SERVICE *service, char *option); +void serviceClearRouterOptions(SERVICE *service); +void service_update(SERVICE *service, char *router, char *user, char *auth); +bool service_set_param_value(SERVICE* service, CONFIG_PARAMETER* param, char* valstr, + count_spec_t count_spec, config_param_type_t type); + +/** + * Internal debugging diagnostics + */ +void printService(SERVICE *service); +void printAllServices(void); + +MXS_END_DECLS \ No newline at end of file diff --git a/server/core/service.c b/server/core/service.c index 3df2e19af..f3eaca089 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -48,7 +48,6 @@ #include #include #include -#include #include #include #include @@ -68,6 +67,8 @@ #include #include +#include "maxscale/service.h" + /** Base value for server weights */ #define SERVICE_BASE_SERVER_WEIGHT 1000 diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index cef230b21..22eac3876 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -33,7 +33,7 @@ #include #include #include -#include +#include "../maxscale/service.h" #include #include #include "test_utils.h" diff --git a/server/modules/routing/binlog/test/testbinlog.c b/server/modules/routing/binlog/test/testbinlog.c index 2eda6e461..24078b42d 100644 --- a/server/modules/routing/binlog/test/testbinlog.c +++ b/server/modules/routing/binlog/test/testbinlog.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -47,6 +46,9 @@ #include +// This isn't really a clean way of testing +#include "../../../../core/maxscale/service.h" + static void printVersion(const char *progname); static void printUsage(const char *progname); extern int blr_test_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config); From 3ba8525063f81a4065e5a7fd7d03dd25e72be96d Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 25 Nov 2016 15:21:11 +0200 Subject: [PATCH 48/48] Remove old .gitignore files The files were used to deal with old Makefile builds. --- client/.gitignore | 2 -- query_classifier/test/.gitignore | 2 -- rabbitmq_consumer/inih/.gitignore | 3 --- server/.gitignore | 1 - server/core/.gitignore | 5 ----- server/core/test/.gitignore | 2 -- server/include/.gitignore | 1 - server/inih/.gitignore | 3 --- server/test/.gitignore | 8 -------- 9 files changed, 27 deletions(-) delete mode 100644 client/.gitignore delete mode 100644 query_classifier/test/.gitignore delete mode 100644 rabbitmq_consumer/inih/.gitignore delete mode 100644 server/.gitignore delete mode 100644 server/core/.gitignore delete mode 100644 server/core/test/.gitignore delete mode 100644 server/include/.gitignore delete mode 100644 server/inih/.gitignore delete mode 100644 server/test/.gitignore diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 0e7ae0f51..000000000 --- a/client/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# binaries generated here -maxadmin diff --git a/query_classifier/test/.gitignore b/query_classifier/test/.gitignore deleted file mode 100644 index 670ee3e79..000000000 --- a/query_classifier/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# binaries generated here -testmain diff --git a/rabbitmq_consumer/inih/.gitignore b/rabbitmq_consumer/inih/.gitignore deleted file mode 100644 index 2a5429025..000000000 --- a/rabbitmq_consumer/inih/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -*.a -make.depend diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 5509140f2..000000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.DS_Store diff --git a/server/core/.gitignore b/server/core/.gitignore deleted file mode 100644 index d0c1894ec..000000000 --- a/server/core/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# binaries generated here -maxscale -maxkeys -maxpasswd -*.DS_Store diff --git a/server/core/test/.gitignore b/server/core/test/.gitignore deleted file mode 100644 index ebadda641..000000000 --- a/server/core/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -#binaries generated here -testhash diff --git a/server/include/.gitignore b/server/include/.gitignore deleted file mode 100644 index 67020331b..000000000 --- a/server/include/.gitignore +++ /dev/null @@ -1 +0,0 @@ -version.h diff --git a/server/inih/.gitignore b/server/inih/.gitignore deleted file mode 100644 index 2a5429025..000000000 --- a/server/inih/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -*.a -make.depend diff --git a/server/test/.gitignore b/server/test/.gitignore deleted file mode 100644 index 184d7f57f..000000000 --- a/server/test/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# directories generated/filled by "make testall" -bin/ -Documentation/ -etc/ -lib/ -log/ -modules/ -mysql/