diff --git a/CMakeLists.txt b/CMakeLists.txt index eef89a85f..5ba32df44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,13 @@ find_package(MySQLClient) find_package(MySQL) find_package(Pandoc) +# You can find the variables set by this in the FindCURL.cmake file +# which is a default module in CMake. +find_package(CURL) +if(NOT CURL_FOUND) + message(FATAL_ERROR "Failed to locate dependency: libcurl") +endif() + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/modules) # Make sure the release notes for this release are present if it is a stable one @@ -114,6 +121,7 @@ include_directories(server/include) include_directories(server/inih) include_directories(server/modules/include) include_directories(${CMAKE_BINARY_DIR}/server/include) +include_directories(${CURL_INCLUDE_DIRS}) add_subdirectory(utils) add_subdirectory(log_manager) diff --git a/Documentation/Tutorials/Notification-Service.md b/Documentation/Tutorials/Notification-Service.md new file mode 100644 index 000000000..4cfc89cd2 --- /dev/null +++ b/Documentation/Tutorials/Notification-Service.md @@ -0,0 +1,94 @@ +# MaxScale Notification Service and Feedback Support + +Massimiliano Pinto + +Last Updated: 10th March 2015 + +## Contents + +## Document History + + + + + + + + + + + + +
DateChangeWho
10th March 2015Initial versionMassimiliano Pinto
+ + +## Overview + +The purpose of Notification Service in MaxScale is for a customer registered for the service to receive update notices, security bulletins, fixes and workarounds that are tailored to the database server configuration. + +## MaxScale Setup + +MaxScale may collect the installed plugins and send the informations nightly, between 2:00 AM and 4:59 AM. + +It tries to send data and if there is any failure (timeout, server is down, etc), the next retry is in 1800 seconds (30 minutes) + +This feature is not enabled by default: MaxScale must be configured in [feedback] section: + + + [feedback] + feedback_enable=1 + feedback_url=https://mariadb.org/feedback_plugin/post + feedback_user_info=x-y-z-w + +The activation code that will be provided by MariaDB corp upon request by the customer and it shlud be put in feedback_user_info. + +Example: +feedback_user_info=0467009f-b04d-45b1-a77b-b6b2ec9c6cf4 + + +MaxScale generates the feedback report containing following information: + + -The activation code used to enable feedback + - MaxScale Version + - An identifier of the MaxScale installation, i.e. the HEX encoding of SHA1 digest of the first network interface MAC address + - Operating System (i.e Linux) + - Operating Suystem Distribution (i.e. CentOS release 6.5 (Final)) + - All the modules in use in MaxScale and their API and version + - MaxScale server UNIX_TIME at generation time + +MaxScale shall send the generated feedback report to a feedback server specified in feedback_url + + +## Manual Operation + +If it’s not possible to send data due to firewall or security settings the report could be generated manually (feedback_user_info is required) via MaxAdmin + + +MaxScale>show feedback report + + +Report could be saved to report.txt file: + + +maxadmin -uxxx -pyyy show feedbackreport > ./report.txt + +curl -F data=@./report.txt https://mariadb.org/feedback_plugin/post + + +Report Example: + + FEEDBACK_SERVER_UID 6B5C44AEA73137D049B02E6D1C7629EF431A350F + FEEDBACK_USER_INFO 0467009f-b04d-45b1-a77b-b6b2ec9c6cf4 + VERSION 1.0.6-unstable + NOW 1425914890 + PRODUCT maxscale + Uname_sysname Linux + Uname_distribution CentOS release 6.5 (Final) + module_maxscaled_type Protocol + module_maxscaled_version V1.0.0 + module_maxscaled_api 1.0.0 + module_maxscaled_releasestatus GA + module_telnetd_type Protocol + module_telnetd_version V1.0.1 + module_telnetd_api 1.0.0 + module_telnetd_releasestatus GA diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index fbfccbaf0..6a481d370 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,14 +1,15 @@ if(BUILD_TESTS) file(GLOB FULLCORE_SRC *.c) add_library(fullcore STATIC ${FULLCORE_SRC}) - target_link_libraries(fullcore log_manager utils pthread ${EMBEDDED_LIB} ssl aio rt crypt dl crypto inih z m stdc++) + target_link_libraries(fullcore ${CURL_LIBRARIES} log_manager utils pthread ${EMBEDDED_LIB} ssl aio rt crypt dl crypto inih z m stdc++) endif() + add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c monitor.c adminusers.c secrets.c filter.c modutil.c hint.c housekeeper.c memlog.c resultset.c) -target_link_libraries(maxscale ${EMBEDDED_LIB} log_manager utils ssl aio pthread crypt dl crypto inih z rt m stdc++) +target_link_libraries(maxscale ${EMBEDDED_LIB} ${CURL_LIBRARIES} log_manager utils ssl aio pthread crypt dl crypto inih z rt m stdc++) install(TARGETS maxscale DESTINATION bin) add_executable(maxkeys maxkeys.c secrets.c utils.c) diff --git a/server/core/config.c b/server/core/config.c index aec9a5b13..26dc70473 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -41,6 +41,7 @@ * 30/10/14 Massimiliano Pinto Added disable_master_failback parameter * 07/11/14 Massimiliano Pinto Addition of monitor timeouts for connect/read/write * 20/02/15 Markus Mäkelä Added connection_timeout parameter for services + * 05/03/15 Massimiliano Pinto Added notification_feedback support * * @endverbatim */ @@ -55,9 +56,22 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -70,12 +84,19 @@ static int process_config_update(CONFIG_CONTEXT *); static void free_config_context(CONFIG_CONTEXT *); static char *config_get_value(CONFIG_PARAMETER *, const char *); static int handle_global_item(const char *, const char *); +static int handle_feedback_item(const char *, const char *); static void global_defaults(); +static void feedback_defaults(); static void check_config_objects(CONFIG_CONTEXT *context); -static int internalService(char *router); +int config_truth_value(char *str); +static int internalService(char *router); +int config_get_ifaddr(unsigned char *output); +int config_get_release_string(char* release); +FEEDBACK_CONF * config_get_feedback_data(); static char *config_file = NULL; static GATEWAY_CONF gateway; +static FEEDBACK_CONF feedback; char *version_string = NULL; @@ -121,6 +142,12 @@ CONFIG_PARAMETER *param, *p1; { return handle_global_item(name, value); } + + if (strcasecmp(section, "feedback") == 0) + { + return handle_feedback_item(name, value); + } + /* * If we already have some parameters for the object * add the parameters to that object. If not create @@ -192,6 +219,7 @@ int rval; } global_defaults(); + feedback_defaults(); config.object = ""; config.next = NULL; @@ -280,7 +308,7 @@ int error_count = 0; char *auth; char *enable_root_user; char *connection_timeout; - char *auth_all_servers; + char *auth_all_servers; char *strip_db_esc; char *weightby; char *version_string; @@ -296,19 +324,19 @@ int error_count = 0; "enable_root_user"); connection_timeout = - config_get_value( - obj->parameters, - "connection_timeout"); + config_get_value( + obj->parameters, + "connection_timeout"); - auth_all_servers = - config_get_value( - obj->parameters, - "auth_all_servers"); + auth_all_servers = + config_get_value( + obj->parameters, + "auth_all_servers"); strip_db_esc = - config_get_value( - obj->parameters, - "strip_db_esc"); + config_get_value( + obj->parameters, + "strip_db_esc"); allow_localhost_match_wildcard_host = config_get_value(obj->parameters, @@ -364,12 +392,14 @@ int error_count = 0; obj->element, atoi(connection_timeout)); - if(auth_all_servers) - serviceAuthAllServers(obj->element, - config_truth_value(auth_all_servers)); + if(auth_all_servers) + serviceAuthAllServers(obj->element, + config_truth_value(auth_all_servers)); + if(strip_db_esc) - serviceStripDbEsc(obj->element, - config_truth_value(strip_db_esc)); + serviceStripDbEsc(obj->element, + config_truth_value(strip_db_esc)); + if (weightby) serviceWeightBy(obj->element, weightby); @@ -751,7 +781,7 @@ int error_count = 0; /* if id is not set, do it now */ if (gateway.id == 0) { setipaddress(&serv_addr.sin_addr, (address == NULL) ? "0.0.0.0" : address); - gateway.id = (unsigned long) (serv_addr.sin_addr.s_addr + port + getpid()); + gateway.id = (unsigned long) (serv_addr.sin_addr.s_addr + atoi(port) + getpid()); } if (service && socket && protocol) { @@ -1214,6 +1244,16 @@ config_pollsleep() return gateway.pollsleep; } +/** + * Return the feedback config data pointer + * + * @return The feedback config data pointer + */ +FEEDBACK_CONF * +config_get_feedback_data() +{ + return &feedback; +} static struct { char *logname; @@ -1249,8 +1289,8 @@ int i; } else if (strcmp(name, "ms_timestamp") == 0) { - skygw_set_highp(atoi(value)); - } + skygw_set_highp(atoi(value)); + } else { for (i = 0; lognames[i].logname; i++) @@ -1267,12 +1307,48 @@ int i; return 1; } +/** + * Configuration handler for items in the feedback [feedback] section + * + * @param name The item name + * @param value The item value + * @return 0 on error + */ +static int +handle_feedback_item(const char *name, const char *value) +{ +int i; + if (strcmp(name, "feedback_enable") == 0) + { + feedback.feedback_enable = config_truth_value((char *)value); + } + else if (strcmp(name, "feedback_user_info") == 0) + { + feedback.feedback_user_info = strdup(value); + } + else if (strcmp(name, "feedback_url") == 0) + { + feedback.feedback_url = strdup(value); + } + if (strcmp(name, "feedback_timeout") == 0) + { + feedback.feedback_timeout = atoi(value); + } + if (strcmp(name, "feedback_connect_timeout") == 0) + { + feedback.feedback_connect_timeout = atoi(value); + } + return 1; +} + /** * Set the defaults for the global configuration options */ static void global_defaults() { + uint8_t mac_addr[6]=""; + struct utsname uname_data; gateway.n_threads = 1; gateway.n_nbpoll = DEFAULT_NBPOLLS; gateway.pollsleep = DEFAULT_POLLSLEEP; @@ -1281,6 +1357,41 @@ global_defaults() else gateway.version_string = NULL; gateway.id=0; + + /* get release string */ + if(!config_get_release_string(gateway.release_string)) + sprintf(gateway.release_string,"undefined"); + + /* get first mac_address in SHA1 */ + if(config_get_ifaddr(mac_addr)) { + gw_sha1_str(mac_addr, 6, gateway.mac_sha1); + } else { + memset(gateway.mac_sha1, '\0', sizeof(gateway.mac_sha1)); + memcpy(gateway.mac_sha1, "MAC-undef", 9); + } + + /* get uname info */ + if (uname(&uname_data)) + strcpy(gateway.sysname, "undefined"); + else + strncpy(gateway.sysname, uname_data.sysname, _SYSNAME_STR_LENGTH); +} + +/** + * Set the defaults for the feedback configuration options + */ +static void +feedback_defaults() +{ + feedback.feedback_enable = 0; + feedback.feedback_user_info = NULL; + feedback.feedback_last_action = _NOTIFICATION_SEND_PENDING; + feedback.feedback_timeout = _NOTIFICATION_OPERATION_TIMEOUT; + feedback.feedback_connect_timeout = _NOTIFICATION_CONNECT_TIMEOUT; + feedback.feedback_url = NULL; + feedback.release_info = gateway.release_string; + feedback.sysname = gateway.sysname; + feedback.mac_sha1 = gateway.mac_sha1; } /** @@ -1326,24 +1437,23 @@ SERVER *server; char *connection_timeout; - char* auth_all_servers; + char* auth_all_servers; char* strip_db_esc; - char* max_slave_conn_str; - char* max_slave_rlag_str; + char* max_slave_conn_str; + char* max_slave_rlag_str; char *version_string; char *allow_localhost_match_wildcard_host; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); connection_timeout = config_get_value(obj->parameters, "connection_timeout"); - - user = config_get_value(obj->parameters, - "user"); + user = config_get_value(obj->parameters, + "user"); auth = config_get_value(obj->parameters, - "passwd"); + "passwd"); - auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); - strip_db_esc = config_get_value(obj->parameters, "strip_db_esc"); + auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); + strip_db_esc = config_get_value(obj->parameters, "strip_db_esc"); version_string = config_get_value(obj->parameters, "version_string"); allow_localhost_match_wildcard_host = config_get_value(obj->parameters, "localhost_match_wildcard_host"); @@ -1476,13 +1586,12 @@ SERVER *server; char *enable_root_user; char *connection_timeout; char *allow_localhost_match_wildcard_host; - char *auth_all_servers; - char *strip_db_esc; + char *auth_all_servers; + char *strip_db_esc; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); - connection_timeout = config_get_value(obj->parameters, "connection_timeout"); @@ -1495,10 +1604,9 @@ SERVER *server; "strip_db_esc"); allow_localhost_match_wildcard_host = - config_get_value(obj->parameters, "localhost_match_wildcard_host"); - user = config_get_value(obj->parameters, + user = config_get_value(obj->parameters, "user"); auth = config_get_value(obj->parameters, "passwd"); @@ -1928,3 +2036,219 @@ int i; } return 0; } +/** + * Get the MAC address of first network interface + * + * and fill the provided allocated buffer with SHA1 encoding + * @param output Allocated 6 bytes buffer + * @return 1 on success, 0 on failure + * + */ +int +config_get_ifaddr(unsigned char *output) +{ + struct ifreq ifr; + struct ifconf ifc; + char buf[1024]; + struct ifreq* it; + struct ifreq* end; + int success = 0; + + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock == -1) { + return 0; + }; + + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { + return 0; + } + + it = ifc.ifc_req; + end = it + (ifc.ifc_len / sizeof(struct ifreq)); + + for (; it != end; ++it) { + strcpy(ifr.ifr_name, it->ifr_name); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) { + if (! (ifr.ifr_flags & IFF_LOOPBACK)) { /* don't count loopback */ + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { + success = 1; + break; + } + } + } else { + return 0; + } + } + + if (success) + memcpy(output, ifr.ifr_hwaddr.sa_data, 6); + + return success; +} + +/** + * Get the linux distribution info + * + * @param release The allocated buffer where + * the found distribution is copied into. + * @return 1 on success, 0 on failure + * + */ +int +config_get_release_string(char* release) +{ + const char *masks[]= { + "/etc/*-version", "/etc/*-release", + "/etc/*_version", "/etc/*_release" + }; + + bool have_distribution; + char distribution[_RELEASE_STR_LENGTH]=""; + int fd; + int i; + char *to; + + have_distribution= false; + + /* get data from lsb-release first */ + if ((fd= open("/etc/lsb-release", O_RDONLY)) != -1) + { + /* LSB-compliant distribution! */ + size_t len= read(fd, (char*)distribution, sizeof(distribution)-1); + close(fd); + if (len != (size_t)-1) + { + distribution[len]= 0; + char *found= strstr(distribution, "DISTRIB_DESCRIPTION="); + if (found) + { + have_distribution = true; + char *end = strstr(found, "\n"); + if (end == NULL) + end = distribution + len; + found += 20; + + if (*found == '"' && end[-1] == '"') + { + found++; + end--; + } + *end = 0; + + to = strcpy(distribution, "lsb: "); + memmove(to, found, end - found + 1); + + strncpy(release, to, _RELEASE_STR_LENGTH); + + return 1; + } + } + } + + /* if not an LSB-compliant distribution */ + for (i= 0; !have_distribution && i < 4; i++) + { + glob_t found; + char *new_to; + + if (glob(masks[i], GLOB_NOSORT, NULL, &found) == 0) + { + int fd; + int k = 0; + int skipindex = 0; + int startindex = 0; + + for (k = 0; k< found.gl_pathc; k++) { + if (strcmp(found.gl_pathv[k], "/etc/lsb-release") == 0) { + skipindex = k; + } + } + + if ( skipindex == 0) + startindex++; + + if ((fd= open(found.gl_pathv[startindex], O_RDONLY)) != -1) + { + /* + +5 and -8 below cut the file name part out of the + full pathname that corresponds to the mask as above. + */ + new_to = strcpy(distribution, found.gl_pathv[0] + 5); + new_to += 8; + *new_to++ = ':'; + *new_to++ = ' '; + + size_t to_len= distribution + sizeof(distribution) - 1 - new_to; + size_t len= read(fd, (char*)new_to, to_len); + + close(fd); + + if (len != (size_t)-1) + { + new_to[len]= 0; + char *end= strstr(new_to, "\n"); + if (end) + *end= 0; + + have_distribution= true; + strncpy(release, new_to, _RELEASE_STR_LENGTH); + } + } + } + globfree(&found); + } + + if (have_distribution) + return 1; + else + return 0; +} + +/** + * Add the 'send_feedback' task to the task list + */ +void +config_enable_feedback_task(void) { + FEEDBACK_CONF *cfg = config_get_feedback_data(); + int url_set = 0; + int user_info_set = 0; + int enable_set = cfg->feedback_enable; + + url_set = cfg->feedback_url != NULL && strlen(cfg->feedback_url); + user_info_set = cfg->feedback_user_info != NULL && strlen(cfg->feedback_user_info); + + if (enable_set && url_set && user_info_set) { + /* Add the task to the tasl list */ + if (hktask_add("send_feedback", module_feedback_send, cfg, 1800)) { + + LOGIF(LM, (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Notification service feedback task started: URL=%s, User-Info=%s, Frequency %u seconds", + cfg->feedback_url, + cfg->feedback_user_info, + 1800))); + } + } else { + if (enable_set) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Notification service feedback cannot start: feedback_enable=1 but" + " some required parameters are not set: %s%s%s", + url_set == 0 ? "feedback_url is not set" : "", (user_info_set == 0 && url_set == 0) ? ", " : "", user_info_set == 0 ? "feedback_user_info is not set" : ""))); + } else { + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "Notification service feedback is not enabled"))); + } + } +} + +/** + * Remove the 'send_feedback' task + */ +void +config_disable_feedback_task(void) { + hktask_remove("send_feedback"); +} diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 7f3fc587d..d8a735ecd 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -23,12 +23,13 @@ * @verbatim * Revision History * - * Date Who Description - * 13/06/13 Mark Riddoch Initial implementation - * 14/06/13 Mark Riddoch Updated to add call to ModuleInit if one is - * defined in the loaded module. - * Also updated to call fixed GetModuleObject - * 02/06/14 Mark Riddoch Addition of module info + * Date Who Description + * 13/06/13 Mark Riddoch Initial implementation + * 14/06/13 Mark Riddoch Updated to add call to ModuleInit if one is + * defined in the loaded module. + * Also updated to call fixed GetModuleObject + * 02/06/14 Mark Riddoch Addition of module info + * 26/02/15 Massimiliano Pinto Addition of module_feedback_send * * @endverbatim */ @@ -42,6 +43,11 @@ #include #include #include +#include +#include +#include +#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -58,6 +64,44 @@ static void register_module(const char *module, void *modobj, MODULE_INFO *info); static void unregister_module(const char *module); +int module_create_feedback_report(GWBUF **buffer, MODULES *modules, FEEDBACK_CONF *cfg); + +struct MemoryStruct { + char *data; + size_t size; +}; + +/** + * Callback write routine for curl library, getting remote server reply + * + * @param contents New data to add + * @param size Data size + * @param nmemb Elements in the buffer + * @param userp Pointer to the buffer + * @return 0 on failure, memory size on success + * + */ +static size_t +WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)userp; + + mem->data = realloc(mem->data, mem->size + realsize + 1); + if(mem->data == NULL) { + /* out of memory! */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_feedback_send(), not enough memory for realloc"))); + return 0; + } + + memcpy(&(mem->data[mem->size]), contents, realsize); + mem->size += realsize; + mem->data[mem->size] = 0; + + return realsize; +} char* get_maxscale_home(void) { @@ -409,6 +453,29 @@ MODULES *ptr = registered; dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n\n"); } +/** + * Print Modules to a DCB + * + * Diagnostic routine to display all the loaded modules + */ +void +moduleShowFeedbackReport(DCB *dcb) +{ +GWBUF *buffer; +MODULES *modules_list = registered; +FEEDBACK_CONF *feedback_config = config_get_feedback_data(); + + if (!module_create_feedback_report(&buffer, modules_list, feedback_config)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_create_feedback_report(): gwbuf_alloc() failed to allocate memory"))); + + return; + } + dcb_printf(dcb, (char *)GWBUF_DATA(buffer)); + gwbuf_free(buffer); +} + /** * Provide a row to the result set that defines the set of modules * @@ -485,3 +552,316 @@ int *data; return set; } + +/** + * Send loaded modules info to notification service + * + * @param data The configuration details of notification service + */ +void +module_feedback_send(void* data) { + MODULES *modules_list = registered; + CURL *curl = NULL; + CURLcode res; + struct curl_httppost *formpost=NULL; + struct curl_httppost *lastptr=NULL; + GWBUF *buffer = NULL; + void *data_ptr=NULL; + long http_code = 0; + struct MemoryStruct chunk; + int last_action = _NOTIFICATION_SEND_PENDING; + time_t now; + struct tm *now_tm; + int hour; + int n_mod=0; + char hex_setup_info[2 * SHA_DIGEST_LENGTH + 1]=""; + + now = time(NULL); + now_tm = localtime(&now); + hour = now_tm->tm_hour; + + FEEDBACK_CONF *feedback_config = (FEEDBACK_CONF *) data; + + /* Configuration check */ + + if (feedback_config->feedback_enable == 0 || feedback_config->feedback_url == NULL || feedback_config->feedback_user_info == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_feedback_send(): some mandatory parameters are not set" + " feedback_enable=%u, feedback_url=%s, feedback_user_info=%s", + feedback_config->feedback_enable, + feedback_config->feedback_url == NULL ? "NULL" : feedback_config->feedback_url, + feedback_config->feedback_user_info == NULL ? "NULL" : feedback_config->feedback_user_info))); + + feedback_config->feedback_last_action = _NOTIFICATION_SEND_ERROR; + + return; + } + + /** + * Task runs nightly, from 2 AM to 4 AM + * + * If it's done in that time interval, it will be skipped + */ + + if (hour > 4 || hour < 2) { + /* It's not the rigt time, mark it as to be done and return */ + feedback_config->feedback_last_action = _NOTIFICATION_SEND_PENDING; + + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "module_feedback_send(): execution skipped, current hour [%d]" + " is not within the proper interval (from 2 AM to 4 AM)", + hour))); + + return; + } + + /* Time to run the task: if a previous run was succesfull skip next runs */ + if (feedback_config->feedback_last_action == _NOTIFICATION_SEND_OK) { + /* task was done before, return */ + + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "module_feedback_send(): execution skipped because of previous succesful run: hour is [%d], last_action [%d]", + hour, feedback_config->feedback_last_action))); + + return; + } + + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "module_feedback_send(): task now runs: hour is [%d], last_action [%d]", + hour, feedback_config->feedback_last_action))); + + + /* allocate first memory chunck for httpd servr reply */ + chunk.data = malloc(1); /* will be grown as needed by the realloc above */ + chunk.size = 0; /* no data at this point */ + + if (!module_create_feedback_report(&buffer, modules_list, feedback_config)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_create_feedback_report(): gwbuf_alloc() failed to allocate memory"))); + + feedback_config->feedback_last_action = _NOTIFICATION_SEND_ERROR; + + return; + } + + + /* Initializing curl library for data send via HTTP */ + curl_global_init(CURL_GLOBAL_DEFAULT); + + curl = curl_easy_init(); + + if (curl) { + char error_message[CURL_ERROR_SIZE]=""; + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_message); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, feedback_config->feedback_connect_timeout); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, feedback_config->feedback_timeout); + + /* curl API call for data send via HTTP POST using a "file" type input */ + curl_formadd(&formpost, + &lastptr, + CURLFORM_COPYNAME, "data", + CURLFORM_BUFFER, "report.txt", + CURLFORM_BUFFERPTR, (char *)GWBUF_DATA(buffer), + CURLFORM_BUFFERLENGTH, strlen((char *)GWBUF_DATA(buffer)), + CURLFORM_CONTENTTYPE, "text/plain", + CURLFORM_END); + + curl_easy_setopt(curl, CURLOPT_HEADER, 1); + + /* some servers don't like requests that are made without a user-agent field, so we provide one */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "MaxScale-agent/http-1.0"); + /* Force HTTP/1.0 */ + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + curl_easy_setopt(curl, CURLOPT_URL, feedback_config->feedback_url); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + +#ifdef SKIP_PEER_VERIFICATION + /* + * This makes the connection A LOT LESS SECURE. + */ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); +#endif + +#ifdef SKIP_HOSTNAME_VERIFICATION + /* + * this will make the connection less secure. + */ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); +#endif + + /* send all data to this function */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + + /* we pass our 'chunk' struct to the callback function */ + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + + /* Check for errors */ + if(res != CURLE_OK) { + last_action = _NOTIFICATION_SEND_ERROR; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: module_feedback_send(), curl call for [%s] failed due: %s, %s", + feedback_config->feedback_url, + curl_easy_strerror(res), + error_message))); + } else { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + } + + if (http_code == 302) { + char *from = strstr(chunk.data, "

ok

"); + if (from) { + last_action = _NOTIFICATION_SEND_OK; + } else { + last_action = _NOTIFICATION_SEND_ERROR; + } + } else { + last_action = _NOTIFICATION_SEND_ERROR; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_feedback_send(), Bad HTTP Code from remote server: %lu", + http_code))); + } + } else { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error in module_feedback_send(), curl object not initialized"))); + last_action = _NOTIFICATION_SEND_ERROR; + } + + /* update last action in the config struct */ + feedback_config->feedback_last_action = last_action; + + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "module_feedback_send(): task run result: hour is [%d], last_action [%d], HTTP code [%d]", + hour, feedback_config->feedback_last_action, http_code))); + + if (chunk.data) + free(chunk.data); + + if (buffer) + gwbuf_free(buffer); + + if (curl) { + curl_easy_cleanup(curl); + curl_formfree(formpost); + } + + curl_global_cleanup(); +} + +/** + * Create the feedback report as string. + * I t could be sent to notification service + * or just printed via maxadmin/telnet + * + * @param buffe The pointr for GWBUF allocation, to be freed by the caller + * @param modules The mouleds list + * @param cfg The feedback configuration + * @return 0 on failure, 1 on success + * + */ + +int +module_create_feedback_report(GWBUF **buffer, MODULES *modules, FEEDBACK_CONF *cfg) { + + MODULES *ptr = modules; + int n_mod = 0; + char *data_ptr=NULL; + char hex_setup_info[2 * SHA_DIGEST_LENGTH + 1]=""; + time_t now; + struct tm *now_tm; + int report_max_bytes=0; + + now = time(NULL); + + /* count loaded modules */ + while (ptr) + { + ptr = ptr->next; + n_mod++; + } + + /* module lists pointer is set back to the head */ + ptr = modules; + + /** + * allocate gwbuf for data to send + * + * each module gives 4 rows + * product and release rows add 7 rows + * row is _NOTIFICATION_REPORT_ROW_LEN bytes long + */ + + report_max_bytes = ((n_mod * 4) + 7) * (_NOTIFICATION_REPORT_ROW_LEN + 1); + *buffer = gwbuf_alloc(report_max_bytes); + + if (buffer == NULL) { + return 0; + } + + /* encode MAC-sha1 to HEX */ + gw_bin2hex(hex_setup_info, cfg->mac_sha1, SHA_DIGEST_LENGTH); + + + data_ptr = (char *)GWBUF_DATA(*buffer); + + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "FEEDBACK_SERVER_UID\t%s\n", hex_setup_info); + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "FEEDBACK_USER_INFO\t%s\n", cfg->feedback_user_info == NULL ? "not_set" : cfg->feedback_user_info); + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "VERSION\t%s\n", MAXSCALE_VERSION); + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN * 2, "NOW\t%lu\nPRODUCT\t%s\n", now, "maxscale"); + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "Uname_sysname\t%s\n", cfg->sysname); + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "Uname_distribution\t%s\n", cfg->release_info); + data_ptr+=strlen(data_ptr); + + while (ptr) + { + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN * 2, "module_%s_type\t%s\nmodule_%s_version\t%s\n", ptr->module, ptr->type, ptr->module, ptr->version); + data_ptr+=strlen(data_ptr); + + if (ptr->info) { + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "module_%s_api\t%d.%d.%d\n", + ptr->module, + ptr->info->api_version.major, + ptr->info->api_version.minor, + ptr->info->api_version.patch); + + data_ptr+=strlen(data_ptr); + snprintf(data_ptr, _NOTIFICATION_REPORT_ROW_LEN, "module_%s_releasestatus\t%s\n", + ptr->module, + ptr->info->status == MODULE_IN_DEVELOPMENT + ? "In Development" + : (ptr->info->status == MODULE_ALPHA_RELEASE + ? "Alpha" + : (ptr->info->status == MODULE_BETA_RELEASE + ? "Beta" + : (ptr->info->status == MODULE_GA + ? "GA" + : (ptr->info->status == MODULE_EXPERIMENTAL + ? "Experimental" : "Unknown"))))); + data_ptr+=strlen(data_ptr); + } + ptr = ptr->next; + } + + return 1; +} + diff --git a/server/core/service.c b/server/core/service.c index 1d601c44a..129a20f29 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -35,6 +35,7 @@ * 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services) * 06/02/15 Mark Riddoch Added caching of authentication data * 18/02/15 Mark Riddoch Added result set management + * 03/03/15 Massimiliano Pinto Added config_enable_feedback_task() call in serviceStartAll * * @endverbatim */ @@ -61,7 +62,6 @@ #include #include - /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; extern size_t log_ses_count[]; @@ -477,6 +477,8 @@ serviceStartAll() SERVICE *ptr; int n = 0,i; + config_enable_feedback_task(); + ptr = allServices; while (ptr && !ptr->svc_do_shutdown) { diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 2a0977088..0cff62772 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(test_server testserver.c) add_executable(test_users testusers.c) add_executable(test_adminusers testadminusers.c) add_executable(testmemlog testmemlog.c) +add_executable(testfeedback testfeedback.c) target_link_libraries(test_mysql_users MySQLClient fullcore) target_link_libraries(test_hash fullcore) target_link_libraries(test_hint fullcore) @@ -26,6 +27,7 @@ target_link_libraries(test_server fullcore) target_link_libraries(test_users fullcore) target_link_libraries(test_adminusers fullcore) target_link_libraries(testmemlog fullcore) +target_link_libraries(testfeedback fullcore) add_test(testMySQLUsers test_mysql_users) add_test(TestHash test_hash) add_test(TestHint test_hint) @@ -40,6 +42,7 @@ add_test(TestServer test_server) add_test(TestUsers test_users) add_test(TestAdminUsers test_adminusers) add_test(TestMemlog testmemlog) +add_test(TestFeedback testfeedback) set_tests_properties(testMySQLUsers TestHash TestHint @@ -53,4 +56,5 @@ set_tests_properties(testMySQLUsers TestServer TestUsers TestAdminUsers - TestMemlog PROPERTIES ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/) + TestMemlog + TestFeedback PROPERTIES ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/) diff --git a/server/core/test/testfeedback.c b/server/core/test/testfeedback.c new file mode 100644 index 000000000..9e84b5cb2 --- /dev/null +++ b/server/core/test/testfeedback.c @@ -0,0 +1,84 @@ +/* + * This file is distributed as part of MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ + +/** + * + * @verbatim + * Revision History + * + * Date Who Description + * 09-03-2015 Markus Mäkelä Initial implementation + * + * @endverbatim + */ + +#define FAILTEST(s) printf("TEST FAILED: " s "\n");return 1; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + FEEDBACK_CONF* fc; + char* home; + char* cnf; + GWBUF* buf; + regex_t re; + + hkinit(); + home = getenv("MAXSCALE_HOME"); + + if(home == NULL) + { + FAILTEST("MAXSCALE_HOME was not defined."); + } + printf("Home: %s\n",home); + + cnf = malloc(strlen(home) + strlen("/etc/MaxScale.cnf") + 1); + strcpy(cnf,home); + strcat(cnf,"/etc/MaxScale.cnf"); + + printf("Config: %s\n",cnf); + + config_load(cnf); + + if((fc = config_get_feedback_data()) == NULL || + fc->feedback_user_info == NULL) + { + FAILTEST("Configuration was NULL."); + } + + regcomp(&re,fc->feedback_user_info,0); + + module_create_feedback_report(&buf,NULL,fc); + printf("%s",(char*)buf->start); + + if(regexec(&re,(char*)buf->start,0,NULL,0)) + { + FAILTEST("Regex match of 'user_info' failed."); + } + + return 0; +} diff --git a/server/include/config.h b/server/include/config.h index 2cf9dccb3..3648c97a0 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -18,7 +18,7 @@ * Copyright MariaDB Corporation Ab 2013-2014 */ #include - +#include /** * @file config.h The configuration handling elements * @@ -30,12 +30,15 @@ * 07/05/14 Massimiliano Pinto Added version_string to global configuration * 23/05/14 Massimiliano Pinto Added id to global configuration * 17/10/14 Mark Riddoch Added poll tuning configuration parameters + * 05/03/15 Massimiliano Pinto Added sysname, release, sha1_mac to gateway struct * * @endverbatim */ #define DEFAULT_NBPOLLS 3 /**< Default number of non block polls before we block */ #define DEFAULT_POLLSLEEP 1000 /**< Default poll wait time (milliseconds) */ +#define _SYSNAME_STR_LENGTH 256 /**< sysname len */ +#define _RELEASE_STR_LENGTH 256 /**< release len */ /** * Maximum length for configuration parameter value. */ @@ -92,22 +95,25 @@ typedef struct config_context { * The gateway global configuration data */ typedef struct { - int n_threads; /**< Number of polling threads */ - char *version_string; /**< The version string of embedded database library */ - unsigned long id; /**< MaxScale ID */ + int n_threads; /**< Number of polling threads */ + char *version_string; /**< The version string of embedded database library */ + char release_string[_SYSNAME_STR_LENGTH]; /**< The release name string of the system */ + char sysname[_SYSNAME_STR_LENGTH]; /**< The release name string of the system */ + uint8_t mac_sha1[SHA_DIGEST_LENGTH]; /*< The SHA1 digest of an interface MAC address */ + unsigned long id; /**< MaxScale ID */ unsigned int n_nbpoll; /**< Tune number of non-blocking polls */ unsigned int pollsleep; /**< Wait time in blocking polls */ } GATEWAY_CONF; -extern int config_load(char *); -extern int config_reload(); -extern int config_threadcount(); -extern unsigned int config_nbpolls(); -extern unsigned int config_pollsleep(); -CONFIG_PARAMETER* config_get_param(CONFIG_PARAMETER* params, const char* name); -config_param_type_t config_get_paramtype(CONFIG_PARAMETER* param); -CONFIG_PARAMETER* config_clone_param(CONFIG_PARAMETER* param); -int config_truth_value(char *str); +extern int config_load(char *); +extern int config_reload(); +extern int config_threadcount(); +extern unsigned int config_nbpolls(); +extern unsigned int config_pollsleep(); +CONFIG_PARAMETER* config_get_param(CONFIG_PARAMETER* params, const char* name); +config_param_type_t config_get_paramtype(CONFIG_PARAMETER* param); +CONFIG_PARAMETER* config_clone_param(CONFIG_PARAMETER* param); +extern int config_truth_value(char *); bool config_set_qualified_param( CONFIG_PARAMETER* param, void* val, @@ -131,4 +137,7 @@ bool config_get_valtarget( CONFIG_PARAMETER* param, const char* name, /*< if NULL examine current param only */ config_param_type_t ptype); + +void config_enable_feedback_task(void); +void config_disable_feedback_task(void); #endif diff --git a/server/include/modules.h b/server/include/modules.h index 56215b5d2..51e10b29d 100644 --- a/server/include/modules.h +++ b/server/include/modules.h @@ -29,13 +29,15 @@ * @verbatim * Revision History * - * Date Who Description - * 13/06/13 Mark Riddoch Initial implementation - * 08/07/13 Mark Riddoch Addition of monitor modules - * 29/05/14 Mark Riddoch Addition of filter modules - * 01/10/14 Mark Riddoch Addition of call to unload all modules on - * shutdown - * 19/02/15 Mark Riddoch Addition of moduleGetList + * Date Who Description + * 13/06/13 Mark Riddoch Initial implementation + * 08/07/13 Mark Riddoch Addition of monitor modules + * 29/05/14 Mark Riddoch Addition of filter modules + * 01/10/14 Mark Riddoch Addition of call to unload all modules on + * shutdown + * 19/02/15 Mark Riddoch Addition of moduleGetList + * 26/02/15 Massimiliano Pinto Addition of module_feedback_send + * * @endverbatim */ @@ -65,7 +67,9 @@ extern void unload_module(const char *module); extern void unload_all_modules(); extern void printModules(); extern void dprintAllModules(DCB *); -extern char *get_maxscale_home(void); extern RESULTSET *moduleGetList(); +extern char *get_maxscale_home(void); +extern void module_feedback_send(void*); +extern void moduleShowFeedbackReport(DCB *dcb); #endif diff --git a/server/include/notification.h b/server/include/notification.h new file mode 100644 index 000000000..6a7f07bac --- /dev/null +++ b/server/include/notification.h @@ -0,0 +1,63 @@ +#ifndef _NOTIFICATION_SERVICE_H +#define _NOTIFICATION_SERVICE_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file notification.h + * + * The configuration stuct for notification/feedback service + * + * @verbatim + * Revision History + * + * Date Who Description + * 02/03/15 Massimiliano Pinto Initial implementation + * + * @endverbatim + */ + +#define _NOTIFICATION_CONNECT_TIMEOUT 30 +#define _NOTIFICATION_OPERATION_TIMEOUT 30 +#define _NOTIFICATION_SEND_PENDING 0 +#define _NOTIFICATION_SEND_OK 1 +#define _NOTIFICATION_SEND_ERROR 2 +#define _NOTIFICATION_REPORT_ROW_LEN 255 + +#include + +/** + * The configuration and usage information data for feeback service + */ + +typedef struct { + int feedback_enable; /**< Enable/Disable Notification feedback */ + char *feedback_url; /**< URL to which the data is sent */ + char *feedback_user_info; /**< User info included in the feedback data sent */ + int feedback_timeout; /**< An attempt to write/read the data times out and fails after this many seconds */ + int feedback_connect_timeout; /**< An attempt to send the data times out and fails after this many seconds */ + int feedback_last_action; /**< Holds the feedback last send action status */ + char *release_info; /**< Operating system Release name */ + char *sysname; /**< Operating system name */ + uint8_t *mac_sha1; /**< First available MAC address*/ +} FEEDBACK_CONF; + +extern char *gw_bin2hex(char *out, const uint8_t *in, unsigned int len); +extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out); +extern FEEDBACK_CONF * config_get_feedback_data(); +#endif diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 821e38f23..253647786 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -36,12 +36,13 @@ * Date Who Description * 20/06/13 Mark Riddoch Initial implementation * 17/07/13 Mark Riddoch Additional commands - * 09/08/2013 Massimiliano Pinto Added enable/disable commands (now only for log) + * 09/08/13 Massimiliano Pinto Added enable/disable commands (now only for log) * 20/05/14 Mark Riddoch Added ability to give server and service names rather * than simply addresses * 23/05/14 Mark Riddoch Added support for developer and user modes * 29/05/14 Mark Riddoch Add Filter support * 16/10/14 Mark Riddoch Add show eventq + * 05/03/15 Massimiliano Pinto Added enable/disable feedback * * @endverbatim */ @@ -129,6 +130,10 @@ struct subcommand showoptions[] = { "Show the event statistics", "Show the event statistics", {0, 0, 0} }, + { "feedbackreport", 0, moduleShowFeedbackReport, + "Show the report of MaxScale loaded modules, suitable for Notification Service", + "Show the report of MaxScale loaded modules, suitable for Notification Service", + {0, 0, 0} }, { "filter", 1, dprintFilter, "Show details of a filter, called with a filter name", "Show details of a filter, called with the address of a filter", @@ -365,6 +370,8 @@ static void enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor); static void disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor); static void enable_service_root(DCB *dcb, SERVICE *service); static void disable_service_root(DCB *dcb, SERVICE *service); +static void enable_feedback_action(); +static void disable_feedback_action(); /** * * The subcommands of the enable command @@ -406,6 +413,14 @@ struct subcommand enableoptions[] = { "Enable root access to a service, pass a service name to enable root access", {ARG_TYPE_SERVICE, 0, 0} }, + { + "feedback", + 0, + enable_feedback_action, + "Enable MaxScale modules list sending via http to notification service", + "Enable MaxScale modules list sending via http to notification service", + {0, 0, 0} + }, { NULL, 0, @@ -458,6 +473,14 @@ struct subcommand disableoptions[] = { "Disable root access to a service", {ARG_TYPE_SERVICE, 0, 0} }, + { + "feedback", + 0, + disable_feedback_action, + "Disable MaxScale modules list sending via http to notification service", + "Disable MaxScale modules list sending via http to notification service", + {0, 0, 0} + }, { NULL, 0, @@ -1381,6 +1404,29 @@ set_nbpoll(DCB *dcb, int nb) poll_set_nonblocking_polls(nb); } +/** + * Re-enable sendig MaxScale module list via http + * Proper [feedback] section in MaxSclale.cnf + * is required. + */ +static void +enable_feedback_action(void) +{ + config_enable_feedback_task(); + return; +} + +/** + * Disable sendig MaxScale module list via http + */ + +static void +disable_feedback_action(void) +{ + config_disable_feedback_task(); + return; +} + #if defined(FAKE_CODE) static void fail_backendfd(void) { diff --git a/server/test/MaxScale_test.cnf b/server/test/MaxScale_test.cnf index 1ed59b5b0..06b783ca5 100644 --- a/server/test/MaxScale_test.cnf +++ b/server/test/MaxScale_test.cnf @@ -1,6 +1,13 @@ [maxscale] threads=4 +[feedback] +feedback_enable=true +feedback_user_info=user_info +feedback_url=http://127.0.0.1:8080/load.php +feedback_timeout=60 +feedback_connect_timeout=60 + [MySQL Monitor] type=monitor module=mysqlmon