diff --git a/Documentation/Debug And Diagnostic Support.pdf b/Documentation/Debug And Diagnostic Support.pdf index 2b848d77b..d7518bbee 100644 Binary files a/Documentation/Debug And Diagnostic Support.pdf and b/Documentation/Debug And Diagnostic Support.pdf differ diff --git a/Documentation/Max Scale 0.6 Release Notes.pdf b/Documentation/Max Scale 0.6 Release Notes.pdf deleted file mode 100644 index 418572497..000000000 Binary files a/Documentation/Max Scale 0.6 Release Notes.pdf and /dev/null differ diff --git a/Documentation/MaxScale 0.7 Release Notes.pdf b/Documentation/MaxScale 0.7 Release Notes.pdf new file mode 100644 index 000000000..d40d2d974 Binary files /dev/null and b/Documentation/MaxScale 0.7 Release Notes.pdf differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios.pdf b/Documentation/MaxScale Configuration And Usage Scenarios.pdf index 47a24088f..ec8a813f8 100644 Binary files a/Documentation/MaxScale Configuration And Usage Scenarios.pdf and b/Documentation/MaxScale Configuration And Usage Scenarios.pdf differ diff --git a/Documentation/history/MaxScale 0.6 Release Notes.pdf b/Documentation/history/MaxScale 0.6 Release Notes.pdf new file mode 100644 index 000000000..0be8ddf0a Binary files /dev/null and b/Documentation/history/MaxScale 0.6 Release Notes.pdf differ diff --git a/README b/README index 1e495b1b2..3750fb332 100644 --- a/README +++ b/README @@ -174,4 +174,47 @@ home directory. The -f flag can be used to set the name and the location of the configuration file. Without path expression the file is read from $MAXSCALE_HOME/etc directory. + +\section Testing Running MaxScale testsuite + +To run "make testall" you need to have three mysqld servers running +on localhost: + +* a master on port 3000, with server_id=2 +* a slave on port 3001, server_id doesn't matter +* a slave on port 2002, server_id doesn't matter + +On the master full privileges on the databases "test" and "FOO" +are needed, on the saves SELECT permissions on test.* should +be sufficient. + +You can use different port numbers but you'll have to change +the server settings at the end of server/test/MaxScale_test.cnf then. + +You also always need to edit the top level test.inc file, +this file contains appropriate default values for the +test setup as described above, these are only given as +comments though ... + +You can then run the full testsuite using + + make testall + +in the top level directory. After testing has finished you +can find a full testlog in test/test_maxscale.log + +You may also find additional information in the following +component specific logs: + + utils/test/testutils.log + query_classifier/test/testqclass.log + server/test/MaxScale/log/skygw_msg1.log + server/test/MaxScale/log/skygw_err1.log + server/test/MaxScale/log/skygw_trace1.log + server/test/MaxScale/log/skygw_debug1.log + server/test/testserver.log + server/core/test/testhash.log + test/test_maxscale.log + + */ diff --git a/VERSION b/VERSION index a918a2aa1..faef31a43 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/server/Makefile b/server/Makefile index 2ac7ed2be..d6aba988e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -33,6 +33,7 @@ all: (cd modules/routing/readwritesplit; touch depend.mk ;make) (cd modules/protocol; touch depend.mk ;make) (cd modules/monitor; touch depend.mk ;make) + (cd modules/filter; touch depend.mk ;make) cleantests: $(MAKE) -C test cleantests @@ -49,12 +50,14 @@ clean: (cd modules/routing; touch depend.mk ; make clean) (cd modules/protocol; touch depend.mk ; make clean) (cd modules/monitor; touch depend.mk ; make clean) + (cd modules/filter; touch depend.mk ; make clean) depend: (cd core; touch depend.mk ; make depend) (cd modules/routing; touch depend.mk ; make depend) (cd modules/protocol; touch depend.mk ; make depend) (cd modules/monitor; touch depend.mk ; make depend) + (cd modules/filter; touch depend.mk ; make depend) install: @mkdir -p $(DEST) @@ -71,3 +74,4 @@ install: (cd modules/routing; make DEST=$(DEST) install) (cd modules/protocol; make DEST=$(DEST) install) (cd modules/monitor; make DEST=$(DEST) install) + (cd modules/filter; make DEST=$(DEST) install) diff --git a/server/MaxScale_template.cnf b/server/MaxScale_template.cnf index ee7eb6e30..94981afbc 100644 --- a/server/MaxScale_template.cnf +++ b/server/MaxScale_template.cnf @@ -20,6 +20,8 @@ threads=1 # user = # passwd= +# monitor_interval= [MySQL Monitor] type=monitor diff --git a/server/core/Makefile b/server/core/Makefile index 3f862a475..0a8de1a4a 100644 --- a/server/core/Makefile +++ b/server/core/Makefile @@ -33,6 +33,7 @@ # 29/06/13 Vilho Raatikka Reverted Query classifier changes because # gateway needs mysql client lib, not qc. # 24/07/13 Mark Ridoch Addition of encryption routines +# 30/05/14 Mark Ridoch Filter API added include ../../build_gateway.inc @@ -41,7 +42,7 @@ UTILSPATH := $(ROOT_PATH)/utils CC=cc -CFLAGS=-c -I/usr/include -I../include -I../inih \ +CFLAGS=-c -I/usr/include -I../include -I../modules/include -I../inih \ $(MYSQL_HEADERS) \ -I$(LOGPATH) -I$(UTILSPATH) \ -Wall -g @@ -56,14 +57,15 @@ LDFLAGS=-rdynamic -L$(LOGPATH) \ SRCS= 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 + monitor.c adminusers.c secrets.c filter.c modutil.c HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.h \ - ../include/gw.h ../include/mysql_protocol.h \ + ../include/gw.h ../modules/include/mysql_client_server_protocol.h \ ../include/session.h ../include/spinlock.h ../include/thread.h \ ../include/modules.h ../include/poll.h ../include/config.h \ ../include/users.h ../include/hashtable.h ../include/gwbitmask.h \ - ../include/adminusers.h ../include/version.h ../include/maxscale.h + ../include/adminusers.h ../include/version.h ../include/maxscale.h \ + ../include/filter.h modutil.h OBJ=$(SRCS:.c=.o) diff --git a/server/core/buffer.c b/server/core/buffer.c index 7cb771b37..d36886c54 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -288,7 +288,11 @@ unsigned int gwbuf_length(GWBUF *head) { int rval = 0; - CHK_GWBUF(head); + + if (head) + { + CHK_GWBUF(head); + } while (head) { rval += GWBUF_LENGTH(head); diff --git a/server/core/config.c b/server/core/config.c index ee6a1d3bb..d53284fce 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -31,6 +31,9 @@ * 11/03/14 Massimiliano Pinto Added Unix socket support * 11/05/14 Massimiliano Pinto Added version_string support to service * 19/05/14 Mark Riddoch Added unique names from section headers + * 29/05/14 Mark Riddoch Addition of filter definition + * 23/05/14 Massimiliano Pinto Added automatic set of maxscale-id: first listening ipv4_raw + port + pid + * 28/05/14 Massimiliano Pinto Added detect_replication_lag parameter * * @endverbatim */ @@ -56,10 +59,11 @@ static char *config_get_value(CONFIG_PARAMETER *, const char *); static int handle_global_item(const char *, const char *); static void global_defaults(); static void check_config_objects(CONFIG_CONTEXT *context); +static int config_truth_value(char *str); static char *config_file = NULL; static GATEWAY_CONF gateway; -char *version_string = NULL; +char *version_string = NULL; /** * Config item handler for the ini file reader @@ -130,7 +134,6 @@ int rval; if (ptr) { *ptr = '\0'; } - } mysql_close(conn); } @@ -166,7 +169,6 @@ int rval; if (!config_file) return 0; - if (gateway.version_string) free(gateway.version_string); @@ -219,6 +221,8 @@ int error_count = 0; "router"); if (router) { + char* max_slave_conn_str; + obj->element = service_alloc(obj->object, router); char *user = config_get_value(obj->parameters, "user"); @@ -229,16 +233,9 @@ int error_count = 0; char *version_string = config_get_value(obj->parameters, "version_string"); - if (version_string) { - ((SERVICE *)(obj->element))->version_string = strdup(version_string); - } else { - if (gateway.version_string) - ((SERVICE *)(obj->element))->version_string = strdup(gateway.version_string); - } - if (obj->element == NULL) /*< if module load failed */ { - LOGIF(LE, (skygw_log_write_flush( + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Reading configuration " "for router service '%s' failed. " @@ -248,9 +245,20 @@ int error_count = 0; obj = obj->next; continue; /*< process next obj */ } + + if (version_string) { + ((SERVICE *)(obj->element))->version_string = strdup(version_string); + } else { + if (gateway.version_string) + ((SERVICE *)(obj->element))->version_string = strdup(gateway.version_string); + } + + max_slave_conn_str = + config_get_value(obj->parameters, + "max_slave_connections"); if (enable_root_user) - serviceEnableRootUser(obj->element, atoi(enable_root_user)); + serviceEnableRootUser(obj->element, config_truth_value(enable_root_user)); if (!auth) auth = config_get_value(obj->parameters, "auth"); @@ -268,6 +276,35 @@ int error_count = 0; "corresponding password.", obj->object))); } + if (max_slave_conn_str != NULL) + { + CONFIG_PARAMETER* param; + bool succp; + + param = config_get_param(obj->parameters, + "max_slave_connections"); + + succp = service_set_slave_conn_limit( + obj->element, + param, + max_slave_conn_str, + COUNT_ATMOST); + + if (!succp) + { + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : invalid value type " + "for parameter \'%s.%s = %s\'\n\tExpected " + "type is either for slave connection " + "count or\n\t%% for specifying the " + "maximum percentage of available the " + "slaves that will be connected.", + ((SERVICE*)obj->element)->name, + param->name, + param->value))); + } + } } else { @@ -326,6 +363,52 @@ int error_count = 0; obj->object))); } } + else if (!strcmp(type, "filter")) + { + char *module = config_get_value(obj->parameters, + "module"); + char *options = config_get_value(obj->parameters, + "options"); + + if (module) + { + obj->element = filter_alloc(obj->object, module); + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Filter '%s' has no module " + "defined defined to load.", + obj->object))); + error_count++; + } + if (obj->element && options) + { + char *s = strtok(options, ","); + while (s) + { + filterAddOption(obj->element, s); + s = strtok(NULL, ","); + } + } + if (obj->element) + { + CONFIG_PARAMETER *params = obj->parameters; + while (params) + { + if (strcmp(params->name, "module") + && strcmp(params->name, + "options")) + { + filterAddParameter(obj->element, + params->name, + params->value); + } + params = params->next; + } + } + } obj = obj->next; } @@ -343,7 +426,8 @@ int error_count = 0; { char *servers; char *roptions; - + char *filters = config_get_value(obj->parameters, + "filters"); servers = config_get_value(obj->parameters, "servers"); roptions = config_get_value(obj->parameters, "router_options"); @@ -385,6 +469,10 @@ int error_count = 0; s = strtok(NULL, ","); } } + if (filters) + { + serviceSetFilters(obj->element, filters); + } } else if (!strcmp(type, "listener")) { @@ -393,12 +481,19 @@ int error_count = 0; char *port; char *protocol; char *socket; + struct sockaddr_in serv_addr; service = config_get_value(obj->parameters, "service"); port = config_get_value(obj->parameters, "port"); address = config_get_value(obj->parameters, "address"); protocol = config_get_value(obj->parameters, "protocol"); socket = config_get_value(obj->parameters, "socket"); + + /* 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()); + } if (service && socket && protocol) { CONFIG_CONTEXT *ptr = context; @@ -461,18 +556,46 @@ int error_count = 0; char *servers; char *user; char *passwd; + unsigned long interval = 0; + int replication_heartbeat = 0; module = config_get_value(obj->parameters, "module"); servers = config_get_value(obj->parameters, "servers"); user = config_get_value(obj->parameters, "user"); passwd = config_get_value(obj->parameters, "passwd"); + if (config_get_value(obj->parameters, "monitor_interval")) { + interval = strtoul(config_get_value(obj->parameters, "monitor_interval"), NULL, 10); + } + + if (config_get_value(obj->parameters, "detect_replication_lag")) { + replication_heartbeat = atoi(config_get_value(obj->parameters, "detect_replication_lag")); + } if (module) { obj->element = monitor_alloc(obj->object, module); if (servers && obj->element) { - char *s = strtok(servers, ","); + char *s; + + /* if id is not set, compute it now with pid only */ + if (gateway.id == 0) { + gateway.id = getpid(); + } + + /* add the maxscale-id to monitor data */ + monitorSetId(obj->element, gateway.id); + + /* set monitor interval */ + if (interval > 0) + monitorSetInterval(obj->element, interval); + + /* set replication heartbeat */ + if(replication_heartbeat == 1) + monitorSetReplicationHeartbeat(obj->element, replication_heartbeat); + + /* get the servers to monitor */ + s = strtok(servers, ","); while (s) { CONFIG_CONTEXT *obj1 = context; @@ -517,7 +640,8 @@ int error_count = 0; error_count++; } } - else if (strcmp(type, "server") != 0) + else if (strcmp(type, "server") != 0 + && strcmp(type, "filter") != 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -562,6 +686,89 @@ config_get_value(CONFIG_PARAMETER *params, const char *name) return NULL; } + +CONFIG_PARAMETER* config_get_param( + CONFIG_PARAMETER* params, + const char* name) +{ + while (params) + { + if (!strcmp(params->name, name)) + return params; + params = params->next; + } + return NULL; +} + +config_param_type_t config_get_paramtype( + CONFIG_PARAMETER* param) +{ + return param->qfd_param_type; +} + +int config_get_valint( + CONFIG_PARAMETER* param, + const char* name, /*< if NULL examine current param only */ + config_param_type_t ptype) +{ + int val = -1; /*< -1 indicates failure */ + + while (param) + { + if (name == NULL || !strncmp(param->name, name, MAX_PARAM_LEN)) + { + switch (ptype) { + case COUNT_TYPE: + val = param->qfd.valcount; + goto return_val; + + case PERCENT_TYPE: + val = param->qfd.valpercent; + goto return_val; + + case BOOL_TYPE: + val = param->qfd.valbool; + goto return_val; + + default: + goto return_val; + } + } + else if (name == NULL) + { + goto return_val; + } + param = param->next; + } +return_val: + return val; +} + + +CONFIG_PARAMETER* config_clone_param( + CONFIG_PARAMETER* param) +{ + CONFIG_PARAMETER* p2; + + p2 = (CONFIG_PARAMETER*) malloc(sizeof(CONFIG_PARAMETER)); + + if (p2 == NULL) + { + goto return_p2; + } + memcpy(p2, param, sizeof(CONFIG_PARAMETER)); + p2->name = strndup(param->name, MAX_PARAM_LEN); + p2->value = strndup(param->value, MAX_PARAM_LEN); + + if (param->qfd_param_type == STRING_TYPE) + { + p2->qfd.valstr = strndup(param->qfd.valstr, MAX_PARAM_LEN); + } + +return_p2: + return p2; +} + /** * Free a config tree * @@ -650,6 +857,7 @@ global_defaults() gateway.version_string = strdup(version_string); else gateway.version_string = NULL; + gateway.id=0; } /** @@ -692,6 +900,7 @@ SERVER *server; char *user; char *auth; char *enable_root_user; + char* max_slave_conn_str; char *version_string; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); @@ -704,8 +913,9 @@ SERVER *server; version_string = config_get_value(obj->parameters, "version_string"); if (version_string) { - if (service->version_string) + if (service->version_string) { free(service->version_string); + } service->version_string = strdup(version_string); } @@ -715,6 +925,42 @@ SERVER *server; auth); if (enable_root_user) serviceEnableRootUser(service, atoi(enable_root_user)); + max_slave_conn_str = + config_get_value( + obj->parameters, + "max_slave_connections"); + + if (max_slave_conn_str != NULL) + { + CONFIG_PARAMETER* param; + bool succp; + + param = config_get_param(obj->parameters, + "max_slave_connections"); + + succp = service_set_slave_conn_limit( + service, + param, + max_slave_conn_str, + COUNT_ATMOST); + + if (!succp) + { + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : invalid value type " + "for parameter \'%s.%s = %s\'\n\tExpected " + "type is either for slave connection " + "count or\n\t%% for specifying the " + "maximum percentage of available the " + "slaves that will be connected.", + ((SERVICE*)obj->element)->name, + param->name, + param->value))); + } + } + + } obj->element = service; @@ -725,7 +971,9 @@ SERVER *server; char *auth; char *enable_root_user; - enable_root_user = config_get_value(obj->parameters, "enable_root_user"); + enable_root_user = + config_get_value(obj->parameters, + "enable_root_user"); user = config_get_value(obj->parameters, "user"); @@ -822,10 +1070,12 @@ SERVER *server; { char *servers; char *roptions; + char *filters; servers = config_get_value(obj->parameters, "servers"); roptions = config_get_value(obj->parameters, "router_options"); + filters = config_get_value(obj->parameters, "filters"); if (servers && obj->element) { char *s = strtok(servers, ","); @@ -859,6 +1109,8 @@ SERVER *server; s = strtok(NULL, ","); } } + if (filters) + serviceSetFilters(obj->element, filters); } else if (!strcmp(type, "listener")) { @@ -919,7 +1171,8 @@ SERVER *server; } } else if (strcmp(type, "server") != 0 && - strcmp(type, "monitor") != 0) + strcmp(type, "monitor") != 0 && + strcmp(type, "filter") != 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -941,7 +1194,9 @@ static char *service_params[] = "user", "passwd", "enable_root_user", + "max_slave_connections", "version_string", + "filters", NULL }; @@ -974,6 +1229,8 @@ static char *monitor_params[] = "servers", "user", "passwd", + "monitor_interval", + "detect_replication_lag", NULL }; /** @@ -1031,3 +1288,68 @@ int i; obj = obj->next; } } + +/** + * Set qualified parameter value to CONFIG_PARAMETER struct. + */ +bool config_set_qualified_param( + CONFIG_PARAMETER* param, + void* val, + config_param_type_t type) +{ + bool succp; + + switch (type) { + case STRING_TYPE: + param->qfd.valstr = strndup((const char *)val, MAX_PARAM_LEN); + succp = true; + break; + + case COUNT_TYPE: + param->qfd.valcount = *(int *)val; + succp = true; + break; + + case PERCENT_TYPE: + param->qfd.valpercent = *(int *)val; + succp = true; + break; + + case BOOL_TYPE: + param->qfd.valbool = *(bool *)val; + succp = true; + break; + + default: + succp = false; + break; + } + + if (succp) + { + param->qfd_param_type = type; + } + return succp; +} + +/** + * Used for boolean settings where values may be 1, yes or true + * to enable a setting or -, no, false to disable a setting. + * + * @param str String to convert to a boolean + * @return Truth value + */ +static int +config_truth_value(char *str) +{ + if (strcasecmp(str, "true") == 0 || strcasecmp(str, "on") == 0) + { + return 1; + } + if (strcasecmp(str, "flase") == 0 || strcasecmp(str, "off") == 0) + { + return 0; + } + return atoi(str); +} + diff --git a/server/core/dbusers.c b/server/core/dbusers.c index da079b3f7..1ef765bc2 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -42,6 +42,7 @@ #include #include #include +#include #define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')" #define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password) AS userdata FROM mysql.user WHERE user IS NOT NULL AND user <> ''" @@ -219,14 +220,14 @@ getUsers(SERVICE *service, struct users *users) */ server = service->databases; dpwd = decryptPassword(service_passwd); - while (server != NULL && mysql_real_connect(con, + while (server != NULL && (mysql_real_connect(con, server->name, service_user, dpwd, NULL, server->port, NULL, - 0) == NULL) + 0) == NULL)) { server = server->nextdb; } diff --git a/server/core/dcb.c b/server/core/dcb.c index 15f4ea637..713cf4b3c 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -68,6 +68,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; @@ -311,11 +312,16 @@ DCB_CALLBACK *cb; if (dcb->remote) free(dcb->remote); - /* Consume dcb->delayq buffer */ + /* Clear write and read buffers */ if (dcb->delayq) { GWBUF *queue = dcb->delayq; while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); } + if (dcb->dcb_readqueue) + { + GWBUF* queue = dcb->dcb_readqueue; + while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); + } spinlock_acquire(&dcb->cb_lock); while ((cb = dcb->callbacks) != NULL) @@ -325,6 +331,11 @@ DCB_CALLBACK *cb; } spinlock_release(&dcb->cb_lock); + if (dcb->dcb_readqueue) + { + GWBUF* queue = dcb->dcb_readqueue; + while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL); + } bitmask_free(&dcb->memdata.bitmask); simple_mutex_done(&dcb->dcb_read_lock); simple_mutex_done(&dcb->dcb_write_lock); @@ -707,11 +718,20 @@ int below_water; below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; ss_dassert(queue != NULL); + /** + * SESSION_STATE_STOPPING means that one of the backends is closing + * the router session. Some backends may have not completed + * authentication yet and thus they have no information about router + * being closed. Session state is changed to SESSION_STATE_STOPPING + * before router's closeSession is called and that tells that DCB may + * still be writable. + */ if (queue == NULL || (dcb->state != DCB_STATE_ALLOC && dcb->state != DCB_STATE_POLLING && dcb->state != DCB_STATE_LISTENING && - dcb->state != DCB_STATE_NOPOLLING)) + dcb->state != DCB_STATE_NOPOLLING && + dcb->session->state != SESSION_STATE_STOPPING)) { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -722,6 +742,7 @@ int below_water; dcb, STRDCBSTATE(dcb->state), dcb->fd))); + ss_dassert(false); return 0; } @@ -738,7 +759,10 @@ int below_water; * the routine that drains the queue data, so we should * not have a race condition on the event. */ - qlen = gwbuf_length(queue); + if (queue) + qlen = gwbuf_length(queue); + else + qlen = 0; atomic_add(&dcb->writeqlen, qlen); dcb->writeq = gwbuf_append(dcb->writeq, queue); dcb->stats.n_buffered++; @@ -852,7 +876,14 @@ int below_water; * for suspended write. */ dcb->writeq = queue; - qlen = gwbuf_length(queue); + if (queue) + { + qlen = gwbuf_length(queue); + } + else + { + qlen = 0; + } atomic_add(&dcb->writeqlen, qlen); if (queue != NULL) @@ -1055,14 +1086,21 @@ printDCB(DCB *dcb) printf("\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); if (dcb->remote) printf("\tConnected to: %s\n", dcb->remote); - printf("\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); + if (dcb->writeq) + printf("\tQueued write data: %d\n",gwbuf_length(dcb->writeq)); printf("\tStatistics:\n"); - printf("\t\tNo. of Reads: %d\n", dcb->stats.n_reads); - printf("\t\tNo. of Writes: %d\n", dcb->stats.n_writes); - printf("\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); - printf("\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); - printf("\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); - printf("\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); + printf("\t\tNo. of Reads: %d\n", + dcb->stats.n_reads); + printf("\t\tNo. of Writes: %d\n", + dcb->stats.n_writes); + printf("\t\tNo. of Buffered Writes: %d\n", + dcb->stats.n_buffered); + printf("\t\tNo. of Accepts: %d\n", + dcb->stats.n_accepts); + printf("\t\tNo. of High Water Events: %d\n", + dcb->stats.n_high_water); + printf("\t\tNo. of Low Water Events: %d\n", + dcb->stats.n_low_water); } /** @@ -1097,13 +1135,17 @@ DCB *dcb; while (dcb) { dcb_printf(pdcb, "DCB: %p\n", (void *)dcb); - dcb_printf(pdcb, "\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); + dcb_printf(pdcb, "\tDCB state: %s\n", + gw_dcb_state2string(dcb->state)); if (dcb->session && dcb->session->service) - dcb_printf(pdcb, "\tService: %s\n", dcb->session->service->name); + dcb_printf(pdcb, "\tService: %s\n", + dcb->session->service->name); if (dcb->remote) - dcb_printf(pdcb, "\tConnected to: %s\n", dcb->remote); + dcb_printf(pdcb, "\tConnected to: %s\n", + dcb->remote); if (dcb->writeq) - dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); + dcb_printf(pdcb, "\tQueued write data: %d\n", + gwbuf_length(dcb->writeq)); dcb_printf(pdcb, "\tStatistics:\n"); dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); @@ -1116,6 +1158,32 @@ DCB *dcb; spinlock_release(&dcbspin); } +/** + * Diagnotic routine to print DCB data in a tabular form. + * + * @param pdcb DCB to print results to + */ +void +dListDCBs(DCB *pdcb) +{ +DCB *dcb; + + spinlock_acquire(&dcbspin); + dcb = allDCBs; + dcb_printf(pdcb, " %-14s | %-26s | %-20s | %s\n", + "DCB", "State", "Service", "Remote"); + dcb_printf(pdcb, "-----------------------------------------------------------------------------\n"); + while (dcb) + { + dcb_printf(pdcb, " %-14p | %-26s | %-20s | %s\n", + dcb, gw_dcb_state2string(dcb->state), + (dcb->session->service ? dcb->session->service->name : ""), + (dcb->remote ? dcb->remote : "")); + dcb = dcb->next; + } + spinlock_release(&dcbspin); +} + /** * Diagnostic to print a DCB to another DCB * @@ -1129,16 +1197,24 @@ dprintDCB(DCB *pdcb, DCB *dcb) dcb_printf(pdcb, "\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); if (dcb->remote) dcb_printf(pdcb, "\tConnected to: %s\n", dcb->remote); - dcb_printf(pdcb, "\tOwning Session: %d\n", dcb->session); - dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); - dcb_printf(pdcb, "\tDelayed write data: %d\n", gwbuf_length(dcb->delayq)); + dcb_printf(pdcb, "\tOwning Session: %p\n", dcb->session); + if (dcb->writeq) + dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); + if (dcb->delayq) + dcb_printf(pdcb, "\tDelayed write data: %d\n", gwbuf_length(dcb->delayq)); dcb_printf(pdcb, "\tStatistics:\n"); - dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads); - dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes); - dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); - dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); - dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); - dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); + dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", + dcb->stats.n_reads); + dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", + dcb->stats.n_writes); + dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", + dcb->stats.n_buffered); + dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", + dcb->stats.n_accepts); + dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", + dcb->stats.n_high_water); + dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", + dcb->stats.n_low_water); } /** @@ -1533,7 +1609,7 @@ int rval = 1; /** * Remove a callback from the callback list for the DCB * - * Searches down the linked list to find he callback with a matching reason, function + * Searches down the linked list to find the callback with a matching reason, function * and userdata. * * @param dcb The DCB to add the callback to diff --git a/server/core/filter.c b/server/core/filter.c new file mode 100644 index 000000000..a3db72e3b --- /dev/null +++ b/server/core/filter.c @@ -0,0 +1,316 @@ +/* + * This file is distributed as part of MaxScale from SkySQL. 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 SkySQL Ab 2014 + */ + +/** + * @file filter.c - A representation of a filter within MaxScale. + * + * @verbatim + * Revision History + * + * Date Who Description + * 29/05/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int lm_enabled_logfiles_bitmask; + +static SPINLOCK filter_spin = SPINLOCK_INIT; +static FILTER_DEF *allFilters = NULL; + +/** + * Allocate a new filter within MaxScale + * + * + * @param name The filter name + * @param module The module to load + * + * @return The newly created filter or NULL if an error occured + */ +FILTER_DEF * +filter_alloc(char *name, char *module) +{ +FILTER_DEF *filter; + + if ((filter = (FILTER_DEF *)malloc(sizeof(FILTER_DEF))) == NULL) + return NULL; + filter->name = strdup(name); + filter->module = strdup(module); + filter->filter = NULL; + filter->options = NULL; + filter->obj = NULL; + filter->parameters = NULL; + + spinlock_init(&filter->spin); + + spinlock_acquire(&filter_spin); + filter->next = allFilters; + allFilters = filter; + spinlock_release(&filter_spin); + + return filter; +} + + +/** + * Deallocate the specified filter + * + * @param server The service to deallocate + * @return Returns true if the server was freed + */ +void +filter_free(FILTER_DEF *filter) +{ +FILTER_DEF *ptr; + + /* First of all remove from the linked list */ + spinlock_acquire(&filter_spin); + if (allFilters == filter) + { + allFilters = filter->next; + } + else + { + ptr = allFilters; + while (ptr && ptr->next != filter) + { + ptr = ptr->next; + } + if (ptr) + ptr->next = filter->next; + } + spinlock_release(&filter_spin); + + /* Clean up session and free the memory */ + free(filter->name); + free(filter->module); + free(filter); +} + +/** + * Find an existing filter using the unique section name in + * configuration file + * + * @param name The filter name + * @return The server or NULL if not found + */ +FILTER_DEF * +filter_find(char *name) +{ +FILTER_DEF *filter; + + spinlock_acquire(&filter_spin); + filter = allFilters; + while (filter) + { + if (strcmp(filter->name, name) == 0) + break; + filter = filter->next; + } + spinlock_release(&filter_spin); + return filter; +} + +/** + * Print all filters to a DCB + * + * Designed to be called within a debugger session in order + * to display all active filters within MaxScale + */ +void +dprintAllFilters(DCB *dcb) +{ +FILTER_DEF *ptr; +int i; + + spinlock_acquire(&filter_spin); + ptr = allFilters; + while (ptr) + { + dcb_printf(dcb, "Filter %p (%s)\n", ptr, ptr->name); + dcb_printf(dcb, "\tModule: %s\n", ptr->module); + if (ptr->options) + { + dcb_printf(dcb, "\tOptions: "); + for (i = 0; ptr->options && ptr->options[i]; i++) + dcb_printf(dcb, "%s ", ptr->options[i]); + dcb_printf(dcb, "\n"); + } + if (ptr->obj && ptr->filter) + ptr->obj->diagnostics(ptr->filter, NULL, dcb); + else + dcb_printf(dcb, "\tModule not loaded.\n"); + ptr = ptr->next; + } + spinlock_release(&filter_spin); +} + +/** + * Print filter details to a DCB + * + * Designed to be called within a debug CLI in order + * to display all active filters in MaxScale + */ +void +dprintFilter(DCB *dcb, FILTER_DEF *filter) +{ +int i; + + dcb_printf(dcb, "Filter %p (%s)\n", filter, filter->name); + dcb_printf(dcb, "\tModule: %s\n", filter->module); + if (filter->options) + { + dcb_printf(dcb, "\tOptions: "); + for (i = 0; filter->options && filter->options[i]; i++) + dcb_printf(dcb, "%s ", filter->options[i]); + dcb_printf(dcb, "\n"); + } + if (filter->obj && filter->filter) + filter->obj->diagnostics(filter->filter, NULL, dcb); +} + +/** + * List all filters in a tabular form to a DCB + * + */ +void +dListFilters(DCB *dcb) +{ +FILTER_DEF *ptr; +int i; + + spinlock_acquire(&filter_spin); + ptr = allFilters; + if (ptr) + { + dcb_printf(dcb, "%-18s | %-15s | Options\n", + "Filter", "Module"); + dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + } + while (ptr) + { + dcb_printf(dcb, "%-18s | %-15s | ", + ptr->name, ptr->module); + for (i = 0; ptr->options && ptr->options[i]; i++) + dcb_printf(dcb, "%s ", ptr->options[i]); + dcb_printf(dcb, "\n"); + ptr = ptr->next; + } + spinlock_release(&filter_spin); +} + +/** + * Add a router option to a service + * + * @param service The service to add the router option to + * @param option The option string + */ +void +filterAddOption(FILTER_DEF *filter, char *option) +{ +int i; + + spinlock_acquire(&filter->spin); + if (filter->options == NULL) + { + filter->options = (char **)calloc(2, sizeof(char *)); + filter->options[0] = strdup(option); + filter->options[1] = NULL; + } + else + { + for (i = 0; filter->options[i]; i++) + ; + filter->options = (char **)realloc(filter->options, + (i + 2) * sizeof(char *)); + filter->options[i] = strdup(option); + filter->options[i+1] = NULL; + } + spinlock_release(&filter->spin); +} + +/** + * Add a router parameter to a service + * + * @param service The service to add the router option to + * @param name The parameter name + * @param value The parameter value + */ +void +filterAddParameter(FILTER_DEF *filter, char *name, char *value) +{ +int i; + + spinlock_acquire(&filter->spin); + if (filter->parameters == NULL) + { + filter->parameters = (FILTER_PARAMETER **)calloc(2, sizeof(FILTER_PARAMETER *)); + i = 0; + } + else + { + for (i = 0; filter->parameters[i]; i++) + ; + filter->parameters = (FILTER_PARAMETER **)realloc(filter->parameters, + (i + 2) * sizeof(FILTER_PARAMETER *)); + } + filter->parameters[i] = (FILTER_PARAMETER *)calloc(1, sizeof(FILTER_PARAMETER)); + filter->parameters[i]->name = strdup(name); + filter->parameters[i]->value = strdup(value); + filter->parameters[i+1] = NULL; + spinlock_release(&filter->spin); +} + +DOWNSTREAM * +filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream) +{ +DOWNSTREAM *me; + + if (filter->obj == NULL) + { + /* Filter not yet loaded */ + if ((filter->obj = load_module(filter->module, + MODULE_FILTER)) == NULL) + { + return NULL; + } + } + if (filter->filter == NULL) + filter->filter = (filter->obj->createInstance)(filter->options, + filter->parameters); + if ((me = (DOWNSTREAM *)calloc(1, sizeof(DOWNSTREAM))) == NULL) + { + return NULL; + } + me->instance = filter->filter; + me->routeQuery = filter->obj->routeQuery; + me->session = filter->obj->newSession(me->instance, session); + + filter->obj->setDownstream(me->instance, me->session, downstream); + + return me; +} diff --git a/server/core/gateway.c b/server/core/gateway.c index bd773c7f9..2bd592fe7 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -136,7 +136,7 @@ static void usage(void); static char* get_expanded_pathname( char** abs_path, char* input_path, - char* fname); + const char* fname); static void print_log_n_stderr( bool do_log, bool do_stderr, @@ -725,7 +725,7 @@ static bool file_is_writable( static char* get_expanded_pathname( char** output_path, char* relative_path, - char* fname) + const char* fname) { char* cnf_file_buf = NULL; char* expanded_path; @@ -1228,7 +1228,7 @@ int main(int argc, char **argv) datadir))); LOGIF(LM, (skygw_log_write_flush( LOGFILE_MESSAGE, - "Configuration file : %s", + "Configuration file : %s", cnf_file_path))); /*< Update the server options */ diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 28c95a3ae..3ace0f91f 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -28,6 +28,7 @@ * 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 * * @endverbatim */ @@ -38,6 +39,7 @@ #include #include #include +#include #include #include @@ -47,10 +49,11 @@ static MODULES *registered = NULL; static MODULES *find_module(const char *module); static void register_module(const char *module, - const char *type, - void *dlhandle, - char *version, - void *modobj); + const char *type, + void *dlhandle, + char *version, + void *modobj, + MODULE_INFO *info); static void unregister_module(const char *module); char* get_maxscale_home(void) @@ -76,12 +79,13 @@ char* get_maxscale_home(void) void * load_module(const char *module, const char *type) { -char *home, *version; -char fname[MAXPATHLEN]; -void *dlhandle, *sym; -char *(*ver)(); -void *(*ep)(), *modobj; -MODULES *mod; +char *home, *version; +char fname[MAXPATHLEN]; +void *dlhandle, *sym; +char *(*ver)(); +void *(*ep)(), *modobj; +MODULES *mod; +MODULE_INFO *mod_info = NULL; if ((mod = find_module(module)) == NULL) { @@ -106,6 +110,7 @@ MODULES *mod; return NULL; } } + if ((dlhandle = dlopen(fname, RTLD_NOW|RTLD_LOCAL)) == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -140,6 +145,57 @@ MODULES *mod; ModuleInit(); } + if ((sym = dlsym(dlhandle, "info")) != NULL) + { + int fatal = 0; + mod_info = sym; + if (strcmp(type, MODULE_PROTOCOL) == 0 + && mod_info->modapi != MODULE_API_PROTOCOL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Module '%s' does not implement " + "the protocol API.\n", + module))); + fatal = 1; + } + if (strcmp(type, MODULE_ROUTER) == 0 + && mod_info->modapi != MODULE_API_ROUTER) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Module '%s' does not implement " + "the router API.\n", + module))); + fatal = 1; + } + if (strcmp(type, MODULE_MONITOR) == 0 + && mod_info->modapi != MODULE_API_MONITOR) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Module '%s' does not implement " + "the monitor API.\n", + module))); + fatal = 1; + } + if (strcmp(type, MODULE_FILTER) == 0 + && mod_info->modapi != MODULE_API_FILTER) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Module '%s' does not implement " + "the filter API.\n", + module))); + fatal = 1; + } + if (fatal) + { + dlclose(dlhandle); + return NULL; + } + } + if ((sym = dlsym(dlhandle, "GetModuleObject")) == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -156,10 +212,11 @@ MODULES *mod; LOGIF(LM, (skygw_log_write_flush( LOGFILE_MESSAGE, - "Loaded module %s: %s.", + "Loaded module %s: %s from %s", module, - version))); - register_module(module, type, dlhandle, version, modobj); + version, + fname))); + register_module(module, type, dlhandle, version, modobj, mod_info); } else { @@ -225,7 +282,7 @@ MODULES *mod = registered; * @param modobj The module object */ static void -register_module(const char *module, const char *type, void *dlhandle, char *version, void *modobj) +register_module(const char *module, const char *type, void *dlhandle, char *version, void *modobj, MODULE_INFO *mod_info) { MODULES *mod; @@ -237,6 +294,7 @@ MODULES *mod; mod->version = strdup(version); mod->modobj = modobj; mod->next = registered; + mod->info = mod_info; registered = mod; } @@ -301,11 +359,25 @@ dprintAllModules(DCB *dcb) { MODULES *ptr = registered; - dcb_printf(dcb, "%-15s | %-11s | Version\n", "Module Name", "Module Type"); - dcb_printf(dcb, "-----------------------------------------------------\n"); + dcb_printf(dcb, "%-15s | %-11s | Version | API | Status\n", "Module Name", "Module Type"); + dcb_printf(dcb, "--------------------------------------------------------------------------\n"); while (ptr) { - dcb_printf(dcb, "%-15s | %-11s | %s\n", ptr->module, ptr->type, ptr->version); + dcb_printf(dcb, "%-15s | %-11s | %-7s ", ptr->module, ptr->type, ptr->version); + if (ptr->info) + dcb_printf(dcb, "| %d.%d.%d | %s", + ptr->info->api_version.major, + ptr->info->api_version.minor, + ptr->info->api_version.patch, + 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" : "Unknown")))); + dcb_printf(dcb, "\n"); ptr = ptr->next; } } diff --git a/server/core/modutil.c b/server/core/modutil.c new file mode 100644 index 000000000..c6bc9bd00 --- /dev/null +++ b/server/core/modutil.c @@ -0,0 +1,123 @@ +/* + * This file is distributed as part of MaxScale from SkySQL. 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 SkySQL Ab 2014 + */ + +/** + * @file modutil.c - Implementation of useful routines for modules + * + * @verbatim + * Revision History + * + * Date Who Description + * 04/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include +#include + +/** + * Check if a GWBUF structure is a MySQL COM_QUERY packet + * + * @param buf Buffer to check + * @return True if GWBUF is a COM_QUERY packet + */ +int +modutil_is_SQL(GWBUF *buf) +{ +unsigned char *ptr; + + if (GWBUF_LENGTH(buf) < 5) + return 0; + ptr = GWBUF_DATA(buf); + return ptr[4] == 0x03; // COM_QUERY +} + +/** + * Extract the SQL portion of a COM_QUERY packet + * + * NB This sets *sql to point into the packet and does not + * allocate any new storage. The string pointed to by *sql is + * not NULL terminated. + * + * This routine is very simplistic and does not deal with SQL text + * that spans multiple buffers. + * + * @param buf The packet buffer + * @param sql Pointer that is set to point at the SQL data + * @param length Length of the SQL data + * @return True if the packet is a COM_QUERY packet + */ +int +modutil_extract_SQL(GWBUF *buf, char **sql, int *length) +{ +char *ptr; + + if (!modutil_is_SQL(buf)) + return 0; + ptr = GWBUF_DATA(buf); + *length = *ptr++; + *length += (*ptr++ << 8); + *length += (*ptr++ << 8); + ptr += 2; // Skip sequence id and COM_QUERY byte + *length = *length - 1; + *sql = ptr; + return 1; +} + + +GWBUF * +modutil_replace_SQL(GWBUF *orig, char *sql) +{ +char *ptr; +int length, newlength; +GWBUF *addition; + + if (!modutil_is_SQL(orig)) + return NULL; + ptr = GWBUF_DATA(orig); + length = *ptr++; + length += (*ptr++ << 8); + length += (*ptr++ << 8); + ptr += 2; // Skip sequence id and COM_QUERY byte + + newlength = strlen(sql); + if (length - 1 == newlength) + { + /* New SQL is the same length as old */ + memcpy(ptr, sql, newlength); + } + else if (length - 1 > newlength) + { + /* New SQL is shorter */ + memcpy(ptr, sql, newlength); + GWBUF_RTRIM(orig, (length - 1) - newlength); + } + else + { + memcpy(ptr, sql, length - 1); + addition = gwbuf_alloc(newlength - (length - 1)); + memcpy(GWBUF_DATA(addition), &sql[length - 1], newlength - (length - 1)); + ptr = GWBUF_DATA(orig); + *ptr++ = (newlength + 1) & 0xff; + *ptr++ = ((newlength + 1) >> 8) & 0xff; + *ptr++ = ((newlength + 1) >> 16) & 0xff; + orig->next = addition; + } + + return orig; +} diff --git a/server/core/monitor.c b/server/core/monitor.c index cee2f2d9e..2fca9bcac 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -22,8 +22,10 @@ * @verbatim * Revision History * - * Date Who Description - * 08/07/13 Mark Riddoch Initial implementation + * Date Who Description + * 08/07/13 Mark Riddoch Initial implementation + * 23/05/14 Massimiliano Pinto Addition of monitor_interval parameter + * and monitor id * * @endverbatim */ @@ -220,3 +222,47 @@ MONITOR *ptr; spinlock_release(&monLock); return ptr; } + + +/** + * Set the id of the monitor. + * + * @param mon The monitor instance + * @param id The id for the monitor + */ + +void +monitorSetId(MONITOR *mon, unsigned long id) +{ + if (mon->module->defaultId != NULL) { + mon->module->defaultId(mon->handle, id); + } +} + +/** + * Set the monitor sampling interval. + * + * @param mon The monitor instance + * @param interval The sampling interval in milliseconds + */ +void +monitorSetInterval (MONITOR *mon, unsigned long interval) +{ + if (mon->module->setInterval != NULL) { + mon->module->setInterval(mon->handle, interval); + } +} + +/** + * Enable Replication Heartbeat support in monitor. + * + * @param mon The monitor instance + * @param interval The sampling interval in milliseconds + */ +void +monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat) +{ + if (mon->module->replicationHeartbeat != NULL) { + mon->module->replicationHeartbeat(mon->handle, replication_heartbeat); + } +} diff --git a/server/core/poll.c b/server/core/poll.c index 365b05e0e..e86d656ad 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -27,6 +27,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; diff --git a/server/core/secrets.c b/server/core/secrets.c index a11b0276d..c4d1822f5 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -21,6 +21,7 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; /** diff --git a/server/core/server.c b/server/core/server.c index fd3e1c876..42a60caea 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -22,8 +22,12 @@ * @verbatim * Revision History * - * Date Who Description - * 18/06/13 Mark Riddoch Initial implementation + * Date Who Description + * 18/06/13 Mark Riddoch Initial implementation + * 17/05/14 Mark Riddoch Addition of unique_name + * 20/05/14 Massimiliano Pinto Addition of server_string + * 21/05/14 Massimiliano Pinto Addition of node_id + * 28/05/14 Massimiliano Pinto Addition of rlagd and node_ts fields * * @endverbatim */ @@ -68,6 +72,10 @@ SERVER *server; server->monuser = NULL; server->monpw = NULL; server->unique_name = NULL; + server->server_string = NULL; + server->node_id = -1; + server->rlag = -1; + server->node_ts = 0; spinlock_acquire(&server_spin); server->next = allServers; @@ -112,6 +120,8 @@ SERVER *ptr; free(server->protocol); if (server->unique_name) free(server->unique_name); + if (server->server_string) + free(server->server_string); free(server); return 1; } @@ -237,8 +247,19 @@ char *stat; free(stat); dcb_printf(dcb, "\tProtocol: %s\n", ptr->protocol); dcb_printf(dcb, "\tPort: %d\n", ptr->port); + if (ptr->server_string) + dcb_printf(dcb, "\tServer Version:\t\t%s\n", ptr->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", ptr->node_id); + if (SERVER_IS_SLAVE(ptr)) { + if (ptr->rlag >= 0) { + dcb_printf(dcb, "\tSlave delay:\t\t%d\n", ptr->rlag); + } + } + if (ptr->node_ts > 0) { + dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", ptr->node_ts); + } dcb_printf(dcb, "\tNumber of connections: %d\n", ptr->stats.n_connections); - dcb_printf(dcb, "\tCurrent no. of connections: %d\n", ptr->stats.n_current); + dcb_printf(dcb, "\tCurrent no. of conns: %d\n", ptr->stats.n_current); ptr = ptr->next; } spinlock_release(&server_spin); @@ -262,8 +283,50 @@ char *stat; free(stat); dcb_printf(dcb, "\tProtocol: %s\n", server->protocol); dcb_printf(dcb, "\tPort: %d\n", server->port); + if (server->server_string) + dcb_printf(dcb, "\tServer Version:\t\t%s\n", server->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", server->node_id); + if (SERVER_IS_SLAVE(server)) { + if (server->rlag >= 0) { + dcb_printf(dcb, "\tSlave delay:\t\t%d\n", server->rlag); + } + } + if (server->node_ts > 0) { + dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", server->node_ts); + } dcb_printf(dcb, "\tNumber of connections: %d\n", server->stats.n_connections); - dcb_printf(dcb, "\tCurrent No. of connections: %d\n", server->stats.n_current); + dcb_printf(dcb, "\tCurrent no. of conns: %d\n", server->stats.n_current); +} + +/** + * List all servers in a tabular form to a DCB + * + */ +void +dListServers(DCB *dcb) +{ +SERVER *ptr; +char *stat; + + spinlock_acquire(&server_spin); + ptr = allServers; + if (ptr) + { + dcb_printf(dcb, "%-18s | %-15s | Port | %-18s | Connections\n", + "Server", "Address", "Status"); + dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + } + while (ptr) + { + stat = server_status(ptr); + dcb_printf(dcb, "%-18s | %-15s | %5d | %-18s | %4d\n", + ptr->unique_name, ptr->name, + ptr->port, stat, + ptr->stats.n_current); + free(stat); + ptr = ptr->next; + } + spinlock_release(&server_spin); } /** @@ -281,6 +344,8 @@ char *status = NULL; if ((status = (char *)malloc(200)) == NULL) return NULL; status[0] = 0; + if (server->status & SERVER_MAINT) + strcat(status, "Maintenance, "); if (server->status & SERVER_MASTER) strcat(status, "Master, "); if (server->status & SERVER_SLAVE) diff --git a/server/core/service.c b/server/core/service.c index 93db01c83..d963987d5 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -30,6 +30,7 @@ * 28/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services) * 07/05/14 Massimiliano Pinto Added: version_string initialized to NULL * 23/05/14 Mark Riddoch Addition of service validation call + * 29/05/14 Mark Riddoch Filter API implementation * * @endverbatim */ @@ -46,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +58,11 @@ extern int lm_enabled_logfiles_bitmask; static SPINLOCK service_spin = SPINLOCK_INIT; static SERVICE *allServices = NULL; +static void service_add_qualified_param( + SERVICE* svc, + CONFIG_PARAMETER* param); + + /** * Allocate a new service for the gateway to support * @@ -103,6 +110,10 @@ SERVICE *service; service->enable_root = 0; service->routerOptions = NULL; service->databases = NULL; + service->svc_config_param = NULL; + service->svc_config_version = 0; + service->filters = NULL; + service->n_filters = 0; spinlock_init(&service->spin); spinlock_init(&service->users_table_spin); memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE)); @@ -531,10 +542,13 @@ serviceClearRouterOptions(SERVICE *service) int i; spinlock_acquire(&service->spin); - for (i = 0; service->routerOptions[i]; i++) - free(service->routerOptions[i]); - free(service->routerOptions); - service->routerOptions = NULL; + if (service->routerOptions != NULL) + { + for (i = 0; service->routerOptions[i]; i++) + free(service->routerOptions[i]); + free(service->routerOptions); + service->routerOptions = NULL; + } spinlock_release(&service->spin); } /** @@ -601,6 +615,63 @@ serviceEnableRootUser(SERVICE *service, int action) return 1; } +/** + * Trim whitespace from the from an rear of a string + * + * @param str String to trim + * @return Trimmed string, chanesg are done in situ + */ +static char * +trim(char *str) +{ +char *ptr; + + while (isspace(*str)) + str++; + + /* Point to last character of the string */ + ptr = str + strlen(str) - 1; + while (ptr > str && isspace(*ptr)) + *ptr-- = 0; + + return str; +} + +/** + * Set the filters used by the service + * + * @param service The service itself + * @param filters ASCII string of filters to use + */ +void +serviceSetFilters(SERVICE *service, char *filters) +{ +FILTER_DEF **flist; +char *ptr, *brkt; +int n = 0; + + flist = (FILTER_DEF *)malloc(sizeof(FILTER_DEF *)); + ptr = strtok_r(filters, "|", &brkt); + while (ptr) + { + n++; + flist = (FILTER_DEF *)realloc(flist, (n + 1) * sizeof(FILTER_DEF *)); + if ((flist[n-1] = filter_find(trim(ptr))) == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Unable to find filter '%s' for service '%s'\n", + trim(ptr), service->name + ))); + } + flist[n] = NULL; + ptr = strtok_r(NULL, "|", &brkt); + } + + service->filters = flist; + service->n_filters = n; +} + /** * Return a named service * @@ -631,6 +702,7 @@ void printService(SERVICE *service) { SERVER *ptr = service->databases; +int i; printf("Service %p\n", service); printf("\tService: %s\n", service->name); @@ -642,6 +714,16 @@ SERVER *ptr = service->databases; printf("\t\t%s:%d Protocol: %s\n", ptr->name, ptr->port, ptr->protocol); ptr = ptr->nextdb; } + if (service->n_filters) + { + printf("\tFilter chain: "); + for (i = 0; i < service->n_filters; i++) + { + printf("%s %s ", service->filters[i]->name, + i + 1 < service->n_filters ? "|" : ""); + } + printf("\n"); + } printf("\tUsers data: %p\n", service->users); printf("\tTotal connections: %d\n", service->stats.n_sessions); printf("\tCurrently connected: %d\n", service->stats.n_current); @@ -695,9 +777,10 @@ SERVICE *ptr; * @param dcb DCB to print data to * @param service The service to print */ -dprintService(DCB *dcb, SERVICE *service) +void dprintService(DCB *dcb, SERVICE *service) { SERVER *server = service->databases; +int i; dcb_printf(dcb, "Service %p\n", service); dcb_printf(dcb, "\tService: %s\n", service->name); @@ -707,6 +790,16 @@ SERVER *server = service->databases; service->router->diagnostics(service->router_instance, dcb); dcb_printf(dcb, "\tStarted: %s", asctime(localtime(&service->stats.started))); + if (service->n_filters) + { + dcb_printf(dcb, "\tFilter chain: "); + for (i = 0; i < service->n_filters; i++) + { + dcb_printf(dcb, "%s %s ", service->filters[i]->name, + i + 1 < service->n_filters ? "|" : ""); + } + dcb_printf(dcb, "\n"); + } dcb_printf(dcb, "\tBackend databases\n"); while (server) { @@ -719,6 +812,72 @@ SERVER *server = service->databases; dcb_printf(dcb, "\tCurrently connected: %d\n", service->stats.n_current); } +/** + * List the defined services in a tabular format. + * + * @param dcb DCB to print the service list to. + */ +void +dListServices(DCB *dcb) +{ +SERVICE *ptr; + + spinlock_acquire(&service_spin); + ptr = allServices; + if (ptr) + { + dcb_printf(dcb, "%-25s | %-20s | #Users | Total Sessions\n", + "Service Name", "Router Module"); + dcb_printf(dcb, "--------------------------------------------------------------------------\n"); + } + while (ptr) + { + dcb_printf(dcb, "%-25s | %-20s | %6d | %5d\n", + ptr->name, ptr->routerModule, + ptr->stats.n_current, ptr->stats.n_sessions); + ptr = ptr->next; + } + spinlock_release(&service_spin); +} + +/** + * List the defined listeners in a tabular format. + * + * @param dcb DCB to print the service list to. + */ +void +dListListeners(DCB *dcb) +{ +SERVICE *ptr; +SERV_PROTOCOL *lptr; + + spinlock_acquire(&service_spin); + ptr = allServices; + if (ptr) + { + dcb_printf(dcb, "%-20s | %-18s | %-15s | Port | State\n", + "Service Name", "Protocol Module", "Address"); + dcb_printf(dcb, "---------------------------------------------------------------------------\n"); + } + while (ptr) + { + lptr = ptr->ports; + while (lptr) + { + dcb_printf(dcb, "%-20s | %-18s | %-15s | %5d | %s\n", + ptr->name, lptr->protocol, + (lptr != NULL) ? lptr->address : "*", + lptr->port, + (lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ? "Stopped" : "Running" + ); + + lptr = lptr->next; + } + ptr = ptr->next; + } + spinlock_release(&service_spin); +} + /** * Update the definition of a service * @@ -810,3 +969,111 @@ int service_refresh_users(SERVICE *service) { else return 1; } + +bool service_set_slave_conn_limit ( + SERVICE* service, + CONFIG_PARAMETER* param, + char* valstr, + count_spec_t count_spec) +{ + char* p; + int valint; + bool percent = false; + bool succp; + + /** + * Find out whether the value is numeric and ends with '%' or '\0' + */ + p = valstr; + + while(isdigit(*p)) p++; + + errno = 0; + + if (p == valstr || (*p != '%' && *p != '\0')) + { + succp = false; + } + else if (*p == '%') + { + if (*(p+1) == '\0') + { + *p = '\0'; + valint = (int) strtol(valstr, (char **)NULL, 10); + + if (valint == 0 && errno != 0) + { + succp = false; + } + else + { + succp = true; + config_set_qualified_param(param, (void *)&valint, PERCENT_TYPE); + } + } + else + { + succp = false; + } + } + else if (*p == '\0') + { + valint = (int) strtol(valstr, (char **)NULL, 10); + + if (valint == 0 && errno != 0) + { + succp = false; + } + else + { + succp = true; + config_set_qualified_param(param, (void *)&valint, COUNT_TYPE); + } + } + + if (succp) + { + service_add_qualified_param(service, param); /*< add param to svc */ + } + return succp; +} + +/** + * Add qualified config parameter to SERVICE struct. + */ +static void service_add_qualified_param( + SERVICE* svc, + CONFIG_PARAMETER* param) +{ + CONFIG_PARAMETER** p; + + spinlock_acquire(&svc->spin); + + p = &svc->svc_config_param; + + if ((*p) != NULL) + { + do + { + /** If duplicate is found, latter remains */ + if (strncasecmp(param->name, + (*p)->name, + strlen(param->name)) == 0) + { + *p = config_clone_param(param); + break; + } + } + while ((*p)->next != NULL); + + (*p)->next = config_clone_param(param); + } + else + { + (*p) = config_clone_param(param); + } + /** Increment service's configuration version */ + atomic_add(&svc->svc_config_version, 1); + (*p)->next = NULL; + spinlock_release(&svc->spin); +} diff --git a/server/core/session.c b/server/core/session.c index bb039edf5..cb392258c 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -25,6 +25,7 @@ * Date Who Description * 17/06/13 Mark Riddoch Initial implementation * 02/09/13 Massimiliano Pinto Added session refcounter + * 29/05/14 Mark Riddoch Addition of filter mechanism * * @endverbatim */ @@ -47,6 +48,9 @@ extern int lm_enabled_logfiles_bitmask; static SPINLOCK session_spin = SPINLOCK_INIT; static SESSION *allSessions = NULL; + +static int session_setup_filters(SESSION *session); + /** * Allocate a new session for a new client of the specified service. * @@ -90,6 +94,7 @@ session_alloc(SERVICE *service, DCB *client_dcb) spinlock_acquire(&session->ses_lock); session->service = service; session->client = client_dcb; + session->n_filters = 0; memset(&session->stats, 0, sizeof(SESSION_STATS)); session->stats.connect = time(0); session->state = SESSION_STATE_ALLOC; @@ -129,6 +134,10 @@ session_alloc(SERVICE *service, DCB *client_dcb) session); if (session->router_session == NULL) { + /** + * Inform other threads that session is closing. + */ + session->state = SESSION_STATE_STOPPING; /*< * Decrease refcount, set dcb's session pointer NULL * and set session pointer to NULL. @@ -138,12 +147,50 @@ session_alloc(SERVICE *service, DCB *client_dcb) session = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Failed to create router " - "client session. Freeing allocated resources."))); + "Error : Failed to create %s session.", + service->name))); goto return_session; } + + /* + * Pending filter chain being setup set the head of the chain to + * be the router. As filters are inserted the current head will + * be pushed to the filter and the head updated. + * + * NB This dictates that filters are created starting at the end + * of the chain nearest the router working back to the client + * protocol end of the chain. + */ + session->head.instance = service->router_instance; + session->head.session = session->router_session; + + session->head.routeQuery = service->router->routeQuery; + + if (service->n_filters > 0) + { + if (!session_setup_filters(session)) + { + /** + * Inform other threads that session is closing. + */ + session->state = SESSION_STATE_STOPPING; + /*< + * Decrease refcount, set dcb's session pointer NULL + * and set session pointer to NULL. + */ + session_free(session); + client_dcb->session = NULL; + session = NULL; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to create %s session.", + service->name))); + goto return_session; + } + } } + spinlock_acquire(&session_spin); session->state = SESSION_STATE_ROUTER_READY; session->next = allSessions; @@ -223,6 +270,7 @@ bool session_free( bool succp = false; SESSION *ptr; int nlink; + int i; CHK_SESSION(session); @@ -258,10 +306,23 @@ bool session_free( /* Free router_session and session */ if (session->router_session) { + session->service->router->closeSession( + session->service->router_instance, + session->router_session); session->service->router->freeSession( session->service->router_instance, session->router_session); } + if (session->n_filters) + { + for (i = 0; i < session->n_filters; i++) + { + session->filters[i].filter->obj->freeSession( + session->filters[i].instance, + session->filters[i].session); + } + free(session->filters); + } free(session); succp = true; @@ -394,6 +455,7 @@ int norouter = 0; if (norouter) printf("%d Sessions have no router session\n", norouter); } + /** * Print all sessions to a DCB * @@ -435,6 +497,8 @@ SESSION *ptr; void dprintSession(DCB *dcb, SESSION *ptr) { +int i; + dcb_printf(dcb, "Session %p\n", ptr); dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state)); dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service); @@ -442,6 +506,49 @@ dprintSession(DCB *dcb, SESSION *ptr) if (ptr->client && ptr->client->remote) dcb_printf(dcb, "\tClient Address: %s\n", ptr->client->remote); dcb_printf(dcb, "\tConnected: %s", asctime(localtime(&ptr->stats.connect))); + if (ptr->n_filters) + { + for (i = 0; i < ptr->n_filters; i++) + { + dcb_printf(dcb, "\tFilter: %s\n", + ptr->filters[i].filter->name); + ptr->filters[i].filter->obj->diagnostics( + ptr->filters[i].instance, + ptr->filters[i].session, + dcb); + } + } +} + +/** + * List all sessions in tabular form to a DCB + * + * Designed to be called within a debugger session in order + * to display all active sessions within the gateway + * + * @param dcb The DCB to print to + */ +void +dListSessions(DCB *dcb) +{ +SESSION *ptr; + + spinlock_acquire(&session_spin); + ptr = allSessions; + if (ptr) + { + dcb_printf(dcb, "Session | Client | State\n"); + dcb_printf(dcb, "------------------------------------------\n"); + } + while (ptr) + { + dcb_printf(dcb, "%-16p | %-15s | %s\n", ptr, + ((ptr->client && ptr->client->remote) + ? ptr->client->remote : ""), + session_state(ptr->state)); + ptr = ptr->next; + } + spinlock_release(&session_spin); } /** @@ -459,6 +566,8 @@ session_state(int state) return "Session Allocated"; case SESSION_STATE_READY: return "Session Ready"; + case SESSION_STATE_ROUTER_READY: + return "Session ready for routing"; case SESSION_STATE_LISTENER: return "Listener Session"; case SESSION_STATE_LISTENER_STOPPED: @@ -467,3 +576,69 @@ session_state(int state) return "Invalid State"; } } + +SESSION* get_session_by_router_ses( + void* rses) +{ + SESSION* ses = allSessions; + + while (ses->router_session != rses && ses->next != NULL) + ses = ses->next; + + if (ses->router_session != rses) + { + ses = NULL; + } + return ses; +} + + +/** + * Create the filter chain for this session. + * + * Filters must be setup in reverse order, starting with the last + * filter in the chain and working back towards the client connection + * Each filter is passed the current session head of the filter chain + * this head becomes the destination for the filter. The newly created + * filter becomes the new head of the filter chain. + * + * @param session The session that requires the chain + * @return 0 if filter creation fails + */ +static int +session_setup_filters(SESSION *session) +{ +SERVICE *service = session->service; +DOWNSTREAM *head; +int i; + + if ((session->filters = calloc(service->n_filters, + sizeof(SESSION_FILTER))) == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Insufficient memory to allocate session filter " + "tracking.\n"))); + return 0; + } + session->n_filters = service->n_filters; + for (i = service->n_filters - 1; i >= 0; i--) + { + if ((head = filterApply(service->filters[i], session, + &session->head)) == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Failed to create filter '%s' for service '%s'.\n", + service->filters[i]->name, + service->name))); + return 0; + } + session->filters[i].filter = service->filters[i]; + session->filters[i].session = head->session; + session->filters[i].instance = head->instance; + session->head = *head; + } + + return 1; +} diff --git a/server/include/buffer.h b/server/include/buffer.h index 53c81a4a6..9651031b2 100644 --- a/server/include/buffer.h +++ b/server/include/buffer.h @@ -93,6 +93,8 @@ typedef struct gwbuf { /*< Consume a number of bytes in the buffer */ #define GWBUF_CONSUME(b, bytes) (b)->start += bytes +#define GWBUF_RTRIM(b, bytes) (b)->end -= bytes + #define GWBUF_TYPE(b) (b)->gwbuf_type /*< * Function prototypes for the API to maniplate the buffers diff --git a/server/include/config.h b/server/include/config.h index 03da9b029..dc94e3ad9 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -17,6 +17,7 @@ * * Copyright SkySQL Ab 2013 */ +#include /** * @file config.h The configuration handling elements @@ -27,16 +28,37 @@ * Date Who Description * 21/06/13 Mark Riddoch Initial implementation * 07/05/14 Massimiliano Pinto Added version_string to global configuration + * 23/05/14 Massimiliano Pinto Added id to global configuration * * @endverbatim */ +/** + * Maximum length for configuration parameter value. + */ +enum {MAX_PARAM_LEN=256}; + +typedef enum { + UNDEFINED_TYPE=0, + STRING_TYPE, + COUNT_TYPE, + PERCENT_TYPE, + BOOL_TYPE +} config_param_type_t; + /** * The config parameter */ typedef struct config_parameter { char *name; /**< The name of the parameter */ - char *value; /**< The value of the parameter */ + char *value; /**< The value of the parameter */ + union { /*< qualified parameter value by type */ + char* valstr; /*< terminated char* array */ + int valcount; /*< int */ + int valpercent; /*< int */ + bool valbool; /*< bool */ + } qfd; + config_param_type_t qfd_param_type; struct config_parameter *next; /**< Next pointer in the linked list */ } CONFIG_PARAMETER; @@ -57,9 +79,25 @@ typedef struct config_context { typedef struct { int n_threads; /**< Number of polling threads */ char *version_string; /**< The version string of embedded database library */ + unsigned long id; /**< MaxScale ID */ } GATEWAY_CONF; -extern int config_load(char *); -extern int config_reload(); -extern int config_threadcount(); +extern int config_load(char *); +extern int config_reload(); +extern int config_threadcount(); +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); + +bool config_set_qualified_param( + CONFIG_PARAMETER* param, + void* val, + config_param_type_t type); + + +int config_get_valint( + CONFIG_PARAMETER* param, + const char* name, /*< if NULL examine current param only */ + config_param_type_t ptype); + #endif diff --git a/server/include/dcb.h b/server/include/dcb.h index 6b9ef093d..e7d2ec716 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -19,6 +19,7 @@ */ #include #include +#include #include #include #include @@ -93,6 +94,13 @@ typedef struct gw_protocol { int (*session)(struct dcb *, void *); } GWPROTOCOL; +/** + * The GWPROTOCOL version data. The following should be updated whenever + * the GWPROTOCOL structure is changed. See the rules defined in modinfo.h + * that define how these numbers should change. + */ +#define GWPROTOCOL_VERSION {1, 0, 0} + /** * The statitics gathered on a descriptor control block */ @@ -206,6 +214,7 @@ typedef struct dcb { GWBUF *writeq; /**< Write Data Queue */ SPINLOCK delayqlock; /**< Delay Backend Write Queue spinlock */ GWBUF *delayq; /**< Delay Backend Write Data Queue */ + GWBUF *dcb_readqueue; /**< read queue for storing incomplete reads */ SPINLOCK authlock; /**< Generic Authorization spinlock */ DCBSTATS stats; /**< DCB related statistics */ @@ -264,6 +273,7 @@ void printAllDCBs(); /* Debug to print all DCB in the system */ void printDCB(DCB *); /* Debug print routine */ void dprintAllDCBs(DCB *); /* Debug to print all DCB in the system */ void dprintDCB(DCB *, DCB *); /* Debug to print a DCB in the system */ +void dListDCBs(DCB *); /* List all DCBs in the system */ const char *gw_dcb_state2string(int); /* DCB state to string */ void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */ int dcb_isclient(DCB *); /* the DCB is the client of the session */ diff --git a/server/include/filter.h b/server/include/filter.h new file mode 100644 index 000000000..8ddcfb00d --- /dev/null +++ b/server/include/filter.h @@ -0,0 +1,114 @@ +#ifndef _FILTER_H +#define _FILTER_H +/* + * This file is distributed as part of the SkySQL Gateway. 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 SkySQL Ab 2014 + */ + +/** + * @file filter.h - The filter interface mechanisms + * + * Revision History + * + * Date Who Description + * 27/05/2014 Mark Riddoch Initial implementation + * + */ +#include +#include +#include +#include + +/** + * The FILTER handle points to module specific data, so the best we can do + * is to make it a void * externally. + */ +typedef void *FILTER; + +/** + * The structure used to pass name, value pairs to the filter instances + */ +typedef struct { + char *name; /**< Name of the parameter */ + char *value; /**< Value of the parameter */ +} FILTER_PARAMETER; + +/** + * @verbatim + * The "module object" structure for a query router module + * + * The entry points are: + * createInstance Called by the service to create a new + * instance of the filter + * newSession Called to create a new user session + * within the filter + * closeSession Called when a session is closed + * freeSession Called when a session is freed + * setDownstream Sets the downstream component of the + * filter pipline + * routeQuery Called on each query that requires + * routing + * diagnostics Called to force the filter to print + * diagnostic output + * + * @endverbatim + * + * @see load_module + */ +typedef struct filter_object { + FILTER *(*createInstance)(char **options, FILTER_PARAMETER **); + void *(*newSession)(FILTER *instance, SESSION *session); + void (*closeSession)(FILTER *instance, void *fsession); + void (*freeSession)(FILTER *instance, void *fsession); + void (*setDownstream)(FILTER *instance, void *fsession, DOWNSTREAM *downstream); + int (*routeQuery)(FILTER *instance, void *fsession, GWBUF *queue); + void (*diagnostics)(FILTER *instance, void *fsession, DCB *dcb); +} FILTER_OBJECT; + +/** + * The filter API version. If the FILTER_OBJECT structure or the filter API + * is changed these values must be updated in line with the rules in the + * file modinfo.h. + */ +#define FILTER_VERSION {1, 0, 0} +/** + * The definition of a filter form the configuration file. + * This is basically the link between a plugin to load and the + * optons to pass to that plugin. + */ +typedef struct filter_def { + char *name; /*< The Filter name */ + char *module; /*< The module to load */ + char **options; /*< The options set for this filter */ + FILTER_PARAMETER + **parameters; /*< The filter parameters */ + FILTER filter; + FILTER_OBJECT *obj; + SPINLOCK spin; + struct filter_def + *next; /*< Next filter in the chain of all filters */ +} FILTER_DEF; + +FILTER_DEF *filter_alloc(char *, char *); +void filter_free(FILTER_DEF *); +FILTER_DEF *filter_find(char *); +void filterAddOption(FILTER_DEF *, char *); +void filterAddParameter(FILTER_DEF *, char *, char *); +DOWNSTREAM *filterApply(FILTER_DEF *, SESSION *, DOWNSTREAM *); +void dprintAllFilters(DCB *); +void dprintFilter(DCB *, FILTER_DEF *); +void dListFilters(DCB *); +#endif diff --git a/server/include/modinfo.h b/server/include/modinfo.h new file mode 100644 index 000000000..c3c1e64da --- /dev/null +++ b/server/include/modinfo.h @@ -0,0 +1,85 @@ +#ifndef _MODINFO_H +#define _MODINFO_H +/* + * 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 SkySQL Ab 2014 + */ + +/** + * @file modinfo.h The module information interface + * + * @verbatim + * Revision History + * + * Date Who Description + * 02/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +/** + * The status of the module. This gives some idea of the module + * maturity. + */ +typedef enum { + MODULE_IN_DEVELOPMENT = 0, + MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, + MODULE_GA +} MODULE_STATUS; + +/** + * The API implemented by the module + */ +typedef enum { + MODULE_API_PROTOCOL = 0, + MODULE_API_ROUTER, + MODULE_API_MONITOR, + MODULE_API_FILTER, + MODULE_API_AUTHENTICATION +} MODULE_API; + +/** + * The module version structure. + * + * The rules for changing these values are: + * + * Any change that affects an inexisting call in the API in question, + * making the new API no longer compatible with the old, + * must increment the major version. + * + * Any change that adds to the API, but does not alter the existing API + * calls, must increment the minor version. + * + * Any change that is purely cosmetic and does not affect the calling + * conventions of the API must increment only the patch version number. + */ +typedef struct { + int major; + int minor; + int patch; +} MODULE_VERSION; + +/** + * The module information structure + */ +typedef struct { + MODULE_API modapi; + MODULE_STATUS status; + MODULE_VERSION api_version; + char *description; +} MODULE_INFO; +#endif diff --git a/server/include/modules.h b/server/include/modules.h index c90cf45a1..199e3a24b 100644 --- a/server/include/modules.h +++ b/server/include/modules.h @@ -18,6 +18,7 @@ * Copyright SkySQL Ab 2013 */ #include +#include /** * @file modules.h Utilities for loading modules @@ -30,6 +31,7 @@ * 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 * @endverbatim */ @@ -39,6 +41,8 @@ typedef struct modules { char *version; /**< Module version */ void *handle; /**< The handle returned by dlopen */ void *modobj; /**< The module "object" this is the set of entry points */ + MODULE_INFO + *info; /**< The module information */ struct modules *next; /**< Next module in the linked list */ } MODULES; @@ -49,6 +53,7 @@ typedef struct modules { #define MODULE_PROTOCOL "Protocol" /**< A protocol module type */ #define MODULE_ROUTER "Router" /**< A router module type */ #define MODULE_MONITOR "Monitor" /**< A database monitor module type */ +#define MODULE_FILTER "Filter" /**< A filter module type */ extern void *load_module(const char *module, const char *type); diff --git a/server/include/modutil.h b/server/include/modutil.h new file mode 100644 index 000000000..2092ddea5 --- /dev/null +++ b/server/include/modutil.h @@ -0,0 +1,37 @@ +#ifndef _MODUTIL_H +#define _MODUTIL_H +/* + * This file is distributed as part of MaxScale from SkySQL. 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 SkySQL Ab 2014 + */ + +/** + * @file modutil.h A set of useful routines for module writers + * + * @verbatim + * Revision History + * + * Date Who Description + * 04/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include + +extern int modutil_is_SQL(GWBUF *); +extern int modutil_extract_SQL(GWBUF *, char **, int *); +extern GWBUF *modutil_replace_SQL(GWBUF *, char *); +#endif diff --git a/server/include/monitor.h b/server/include/monitor.h index 6444fecd5..861c1c070 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -26,10 +26,11 @@ * @verbatim * Revision History * - * Date Who Description - * 07/07/13 Mark Riddoch Initial implementation - * 25/07/13 Mark Riddoch Addition of diagnotics - * 23/05/14 Mark Riddoch Addition of routine to find monitors by name + * Date Who Description + * 07/07/13 Mark Riddoch Initial implementation + * 25/07/13 Mark Riddoch Addition of diagnotics + * 23/05/14 Mark Riddoch Addition of routine to find monitors by name + * 23/05/14 Massimiliano Pinto Addition of defaultId and setInterval * * @endverbatim */ @@ -66,8 +67,17 @@ typedef struct { void (*unregisterServer)(void *, SERVER *); void (*defaultUser)(void *, char *, char *); void (*diagnostics)(DCB *, void *); + void (*setInterval)(void *, unsigned long); + void (*defaultId)(void *, unsigned long); + void (*replicationHeartbeat)(void *, int); } MONITOR_OBJECT; +/** + * The monitor API version number. Any change to the monitor module API + * must change these versions usign the rules defined in modinfo.h + */ +#define MONITOR_VERSION {1, 0, 0} + /** * Representation of the running monitor. */ @@ -87,4 +97,7 @@ extern void monitorStop(MONITOR *); extern void monitorStart(MONITOR *); extern void monitorStopAll(); extern void monitorShowAll(DCB *); +extern void monitorSetId(MONITOR *, unsigned long); +extern void monitorSetInterval (MONITOR *, unsigned long); +extern void monitorSetReplicationHeartbeat(MONITOR *, int); #endif diff --git a/server/include/router.h b/server/include/router.h index 004f91d51..ce8f547d8 100644 --- a/server/include/router.h +++ b/server/include/router.h @@ -78,6 +78,13 @@ typedef struct router_object { uint8_t (*getCapabilities)(ROUTER *instance, void* router_session); } ROUTER_OBJECT; +/** + * The router module API version. Any change that changes the router API + * must update these versions numbers in accordance with the rules in + * modinfo.h. + */ +#define ROUTER_VERSION { 1, 0, 0 } + typedef enum router_capability_t { RCAP_TYPE_UNDEFINED = 0, RCAP_TYPE_STMT_INPUT = (1 << 0), diff --git a/server/include/server.h b/server/include/server.h index 6b4598c36..b15453c18 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -27,10 +27,15 @@ * @verbatim * Revision History * - * Date Who Description - * 14/06/13 Mark Riddoch Initial implementation - * 21/06/13 Mark Riddoch Addition of server status flags - * 22/07/13 Mark Riddoch Addition of JOINED status for Galera + * Date Who Description + * 14/06/13 Mark Riddoch Initial implementation + * 21/06/13 Mark Riddoch Addition of server status flags + * 22/07/13 Mark Riddoch Addition of JOINED status for Galera + * 18/05/14 Mark Riddoch Addition of unique_name field + * 20/05/14 Massimiliano Pinto Addition of server_string field + * 20/05/14 Massimiliano Pinto Addition of node_id field + * 23/05/14 Massimiliano Pinto Addition of rlag and node_ts fields + * 03/06/14 Mark Riddoch Addition of maintainance mode * * @endverbatim */ @@ -61,6 +66,10 @@ typedef struct server { SERVER_STATS stats; /**< The server statistics */ struct server *next; /**< Next server */ struct server *nextdb; /**< Next server in list attached to a service */ + char *server_string; /**< Server version string, i.e. MySQL server version */ + long node_id; /**< Node id, server_id for M/S or local_index for Galera */ + int rlag; /**< Replication Lag for Master / Slave replication */ + unsigned long node_ts; /**< Last timestamp set from M/S monitor module */ } SERVER; /** @@ -72,12 +81,13 @@ typedef struct server { #define SERVER_MASTER 0x0002 /**<< The server is a master, i.e. can handle writes */ #define SERVER_SLAVE 0x0004 /**<< The server is a slave, i.e. can handle reads */ #define SERVER_JOINED 0x0008 /**<< The server is joined in a Galera cluster */ +#define SERVER_MAINT 0x1000 /**<< Server is in maintenance mode */ /** * Is the server running - the macro returns true if the server is marked as running * regardless of it's state as a master or slave */ -#define SERVER_IS_RUNNING(server) ((server)->status & SERVER_RUNNING) +#define SERVER_IS_RUNNING(server) (((server)->status & (SERVER_RUNNING|SERVER_MAINT)) == SERVER_RUNNING) /** * Is the server marked as down - the macro returns true if the server is beleived * to be inoperable. @@ -88,19 +98,24 @@ typedef struct server { * in order for the macro to return true */ #define SERVER_IS_MASTER(server) \ - (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_MASTER)) + (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER)) /** * Is the server a slave? The server must be both running and marked as a slave * in order for the macro to return true */ #define SERVER_IS_SLAVE(server) \ - (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) == (SERVER_RUNNING|SERVER_SLAVE)) + (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_SLAVE)) /** * Is the server joined Galera node? The server must be running and joined. */ #define SERVER_IS_JOINED(server) \ - (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) == (SERVER_RUNNING|SERVER_JOINED)) + (((server)->status & (SERVER_RUNNING|SERVER_JOINED|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_JOINED)) + +/** + * Is the server in maintenance mode. + */ +#define SERVER_IN_MAINT(server) ((server)->status & SERVER_MAINT) extern SERVER *server_alloc(char *, char *, unsigned short); extern int server_free(SERVER *); @@ -110,9 +125,11 @@ extern void printServer(SERVER *); extern void printAllServers(); extern void dprintAllServers(DCB *); extern void dprintServer(DCB *, SERVER *); +extern void dListServers(DCB *); extern char *server_status(SERVER *); extern void server_set_status(SERVER *, int); extern void server_clear_status(SERVER *, int); extern void serverAddMonUser(SERVER *, char *, char *); extern void server_update(SERVER *, char *, char *, char *); +extern void server_set_unique_name(SERVER *, char *); #endif diff --git a/server/include/service.h b/server/include/service.h index c7b9126f0..40023332b 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include "config.h" /** * @file service.h @@ -38,7 +40,9 @@ * 23/06/13 Mark Riddoch Added service user and users * 06/02/14 Massimiliano Pinto Added service flag for root user access * 25/02/14 Massimiliano Pinto Added service refresh limit feature - * 07/05/14 Massimiliano Pinto Added version_string field to service struct + * 07/05/14 Massimiliano Pinto Added version_string field to service + * struct + * 29/05/14 Mark Riddoch Filter API mechanism * * @endverbatim */ @@ -72,9 +76,10 @@ typedef struct { } SERVICE_STATS; /** - * The service user structure holds the information that is needed for this service to - * allow the gateway to login to the backend database and extact information such as - * the user table or other database status or configuration data. + * The service user structure holds the information that is needed + for this service to allow the gateway to login to the backend + database and extact information such as the user table or other + database status or configuration data. */ typedef struct { char *name; /**< The user name to use to extract information */ @@ -82,8 +87,8 @@ typedef struct { } SERVICE_USER; /** - * The service refresh rate hols the counter and last load timet for this service to - * load users data from the backend database + * The service refresh rate holds the counter and last load time_t + for this service to load users data from the backend database */ typedef struct { int nloads; @@ -98,31 +103,38 @@ typedef struct { * to the service. */ typedef struct service { - char *name; /**< The service name */ - int state; /**< The service state */ - SERV_PROTOCOL *ports; /**< Linked list of ports and protocols - * that this service will listen on. - */ - char *routerModule; /**< Name of router module to use */ - char **routerOptions; /**< Router specific option strings */ + char *name; /**< The service name */ + int state; /**< The service state */ + SERV_PROTOCOL *ports; /**< Linked list of ports and protocols + * 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 */ + *router; /**< The router we are using */ void *router_instance; - /**< The router instance for this service */ - char *version_string; /** version string for this service listeners */ - struct server *databases; /**< The set of servers in the backend */ - SERVICE_USER credentials; /**< The cedentials of the service user */ - SPINLOCK spin; /**< The service spinlock */ - SERVICE_STATS stats; /**< The service statistics */ - struct users *users; /**< The user data for this service */ - int enable_root; /**< Allow root user access */ + /**< The router instance for this service */ + char *version_string;/** version string for this service listeners */ + struct server *databases; /**< The set of servers in the backend */ + SERVICE_USER credentials; /**< The cedentials of the service user */ + SPINLOCK spin; /**< The service spinlock */ + SERVICE_STATS stats; /**< The service statistics */ + struct users *users; /**< The user data for this service */ + int enable_root; /**< Allow root user access */ + CONFIG_PARAMETER* + svc_config_param; /*< list of config params and values */ + int svc_config_version; /*< Version number of configuration */ SPINLOCK users_table_spin; /**< The spinlock for users data refresh */ SERVICE_REFRESH_RATE rate_limit; /**< The refresh rate limit for users table */ + FILTER_DEF **filters; /**< Ordered list of filters */ + int n_filters; /**< Number of filters */ struct service *next; /**< The next service in the linked list */ } SERVICE; +typedef enum count_spec_t {COUNT_ATLEAST=0, COUNT_EXACT, COUNT_ATMOST} count_spec_t; + #define SERVICE_STATE_ALLOC 1 /**< The service has been allocated */ #define SERVICE_STATE_STARTED 2 /**< The service has been started */ @@ -149,5 +161,12 @@ extern int service_refresh_users(SERVICE *); extern void printService(SERVICE *); extern void printAllServices(); extern void dprintAllServices(DCB *); +bool service_set_slave_conn_limit ( + SERVICE* service, + CONFIG_PARAMETER* param, + char* valstr, + count_spec_t count_spec); extern void dprintService(DCB *, SERVICE *); +extern void dListServices(DCB *); +extern void dListListeners(DCB *); #endif diff --git a/server/include/session.h b/server/include/session.h index c9f9f673c..f99982802 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -31,16 +31,20 @@ * 01-07-2013 Massimiliano Pinto Removed backends pointer * from struct session * 02-09-2013 Massimiliano Pinto Added session ref counter + * 29-05-2014 Mark Riddoch Support for filter mechanism + * added * * @endverbatim */ #include #include +#include #include #include struct dcb; struct service; +struct filter_def; /** * The session statistics structure @@ -53,11 +57,45 @@ typedef enum { SESSION_STATE_ALLOC, /*< for all sessions */ SESSION_STATE_READY, /*< for router session */ SESSION_STATE_ROUTER_READY, /*< for router session */ + SESSION_STATE_STOPPING, /*< router is being closed */ SESSION_STATE_LISTENER, /*< for listener session */ SESSION_STATE_LISTENER_STOPPED, /*< for listener session */ SESSION_STATE_FREE /*< for all sessions */ } session_state_t; +/** + * The downstream element in the filter chain. This may refer to + * another filter or to a router. + */ +typedef struct { + void *instance; + void *session; + int (*routeQuery)(void *instance, + void *router_session, GWBUF *queue); +} DOWNSTREAM; + +/** + * The upstream element in the filter chain. This may refer to + * another filter or to the protocol implementation. + */ +typedef struct { + void *instance; + void *session; + int (*write)(void *, void *, GWBUF *); + int (*error)(void *); +} UPSTREAM; + +/** + * Structure used to track the filter instances and sessions of the filters + * that are in use within a session. + */ +typedef struct { + struct filter_def + *filter; + void *instance; + void *session; +} SESSION_FILTER; + /** * The session status block * @@ -76,6 +114,9 @@ typedef struct session { void *router_session;/**< The router instance data */ SESSION_STATS stats; /**< Session statistics */ struct service *service; /**< The service this session is using */ + int n_filters; /**< Number of filter sessions */ + SESSION_FILTER *filters; /**< The filters in use within this session */ + DOWNSTREAM head; /**< Head of the filter chain */ struct session *next; /**< Linked list of all sessions */ int refcount; /**< Reference count on the session */ #if defined(SS_DEBUG) @@ -85,6 +126,15 @@ typedef struct session { #define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client, type) +/** + * A convenience macro that can be used by the protocol modules to route + * the incoming data to the first element in the pipeline of filters and + * routers. + */ +#define SESSION_ROUTE_QUERY(session, buf) \ + ((session)->head.routeQuery)((session)->head.instance, \ + (session)->head.session, (buf)) + SESSION *session_alloc(struct service *, struct dcb *); bool session_free(SESSION *); int session_isvalid(SESSION *); @@ -92,6 +142,8 @@ void printAllSessions(); void printSession(SESSION *); void dprintAllSessions(struct dcb *); void dprintSession(struct dcb *, SESSION *); +void dListSessions(struct dcb *); char *session_state(int); bool session_link_dcb(SESSION *, struct dcb *); +SESSION* get_session_by_router_ses(void* rses); #endif diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile new file mode 100644 index 000000000..14b226b7d --- /dev/null +++ b/server/modules/filter/Makefile @@ -0,0 +1,86 @@ +# This file is distributed as part of MaxScale form SkySQL. 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 SkySQL Ab 2014 +# +# Revision History +# Date Who Description +# 29/05/14 Mark Riddoch Initial module development + +include ../../../build_gateway.inc + +LOGPATH := $(ROOT_PATH)/log_manager +UTILSPATH := $(ROOT_PATH)/utils + +CC=cc +CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ + -I$(UTILSPATH) -Wall -g + +include ../../../makefile.inc + +LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \ + -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) + +TESTSRCS=testfilter.c +TESTOBJ=$(TESTSRCS:.c=.o) +QLASRCS=qlafilter.c +QLAOBJ=$(QLASRCS:.c=.o) +REGEXSRCS=regexfilter.c +REGEXOBJ=$(REGEXSRCS:.c=.o) +SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) +OBJ=$(SRCS:.c=.o) +LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager +MODULES= libtestfilter.so libqlafilter.so libregexfilter.so + + +all: $(MODULES) + +libtestfilter.so: $(TESTOBJ) + $(CC) $(LDFLAGS) $(TESTOBJ) $(LIBS) -o $@ + +libqlafilter.so: $(QLAOBJ) + $(CC) $(LDFLAGS) $(QLAOBJ) $(LIBS) -o $@ + +libregexfilter.so: $(REGEXOBJ) + $(CC) $(LDFLAGS) $(REGEXOBJ) $(LIBS) -o $@ + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -f $(OBJ) $(MODULES) + +tags: + ctags $(SRCS) $(HDRS) + +depend: + @rm -f depend.mk + cc -M $(CFLAGS) $(SRCS) > depend.mk + +install: $(MODULES) + install -D $(MODULES) $(DEST)/MaxScale/modules + +cleantests: + $(MAKE) -C test cleantests + +buildtests: + $(MAKE) -C test DEBUG=Y buildtests + +runtests: + $(MAKE) -C test runtests + +testall: + $(MAKE) -C test testall + +include depend.mk diff --git a/server/modules/filter/qlafilter.c b/server/modules/filter/qlafilter.c new file mode 100644 index 000000000..520f1e1a9 --- /dev/null +++ b/server/modules/filter/qlafilter.c @@ -0,0 +1,295 @@ +/* + * This file is distributed as part of MaxScale by SkySQL. 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 SkySQL Ab 2014 + */ + +/** + * QLA Filter - Query Log All. A primitive query logging filter, simply + * used to verify the filter mechanism for downstream filters. All queries + * that are passed through the filter will be written to file. + * + * The filter makes no attempt to deal with query packets that do not fit + * in a single GWBUF. + * + * A single option may be passed to the filter, this is the name of the + * file to which the queries are logged. A serial number is appended to this + * name in order that each session logs to a different file. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A simple query logging filter" +}; + +static char *version_str = "V1.0.0"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + routeQuery, + diagnostic, +}; + +/** + * A instance structure, the assumption is that the option passed + * to the filter is simply a base for the filename to which the queries + * are logged. + * + * To this base a session number is attached such that each session will + * have a nique name. + */ +typedef struct { + int sessions; + char *filebase; +} QLA_INSTANCE; + +/** + * The session structure for this QLA filter. + * This stores the downstream filter information, such that the + * filter is able to pass the query on to the next filter (or router) + * in the chain. + * + * It also holds the file descriptor to which queries are written. + */ +typedef struct { + DOWNSTREAM down; + char *filename; + int fd; +} QLA_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +QLA_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(QLA_INSTANCE))) != NULL) + { + if (options) + my_instance->filebase = strdup(options[0]); + else + my_instance->filebase = strdup("qla"); + my_instance->sessions = 0; + } + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * Create the file to log to and open it. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +QLA_INSTANCE *my_instance = (QLA_INSTANCE *)instance; +QLA_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(QLA_SESSION))) != NULL) + { + if ((my_session->filename = + (char *)malloc(strlen(my_instance->filebase) + 20)) + == NULL) + { + free(my_session); + return NULL; + } + sprintf(my_session->filename, "%s.%d", my_instance->filebase, + my_instance->sessions); + my_instance->sessions++; + my_session->fd = open(my_session->filename, + O_WRONLY|O_CREAT|O_TRUNC, 0666); + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * In the case of the QLA filter we simple close the file descriptor. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +QLA_SESSION *my_session = (QLA_SESSION *)session; + + close(my_session->fd); +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ +QLA_SESSION *my_session = (QLA_SESSION *)session; + + free(my_session->filename); + free(session); + return; +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +QLA_SESSION *my_session = (QLA_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query should normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param queue The query data + */ +static int +routeQuery(FILTER *instance, void *session, GWBUF *queue) +{ +QLA_SESSION *my_session = (QLA_SESSION *)session; +char *ptr, t_buf[40]; +int length; +struct tm t; +struct timeval tv; + + if (modutil_extract_SQL(queue, &ptr, &length)) + { + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &t); + sprintf(t_buf, "%02d:%02d:%02d.%-3d %d/%02d/%d, ", + t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000), + t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year); + write(my_session->fd, t_buf, strlen(t_buf)); + write(my_session->fd, ptr, length); + write(my_session->fd, "\n", 1); + } + + /* Pass the query downstream */ + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * 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 + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ +QLA_SESSION *my_session = (QLA_SESSION *)fsession; + + if (my_session) + { + dcb_printf(dcb, "\t\tLogging to file %s.\n", + my_session->filename); + } +} diff --git a/server/modules/filter/regexfilter.c b/server/modules/filter/regexfilter.c new file mode 100644 index 000000000..ad773c40c --- /dev/null +++ b/server/modules/filter/regexfilter.c @@ -0,0 +1,355 @@ +/* + * This file is distributed as part of MaxScale by SkySQL. 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 SkySQL Ab 2014 + */ +#include +#include +#include +#include +#include +#include + +/** + * regexfilter.c - a very simple regular expression rewrite filter. + * + * A simple regular expression query rewrite filter. + * Two parameters should be defined in the filter configuration + * match= + * replace= + */ + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A query rewrite filter that uses regular expressions to rewite queries" +}; + +static char *version_str = "V1.0.0"; + +static FILTER *createInstance(char **options, FILTER_PARAMETER **params); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + +static char *regex_replace(char *sql, int length, regex_t *re, char *replace); + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + routeQuery, + diagnostic, +}; + +/** + * Instance structure + */ +typedef struct { + char *match; /* Regular expression to match */ + char *replace; /* Replacement text */ + regex_t re; /* Compiled regex text */ +} REGEX_INSTANCE; + +/** + * The session structure for this regex filter + */ +typedef struct { + DOWNSTREAM down; + int no_change; + int replacements; +} REGEX_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +REGEX_INSTANCE *my_instance; +int i; + + if ((my_instance = calloc(1, sizeof(REGEX_INSTANCE))) != NULL) + { + my_instance->match = NULL; + my_instance->replace = NULL; + + for (i = 0; params[i]; i++) + { + if (!strcmp(params[i]->name, "match")) + my_instance->match = strdup(params[i]->value); + if (!strcmp(params[i]->name, "replace")) + my_instance->replace = strdup(params[i]->value); + } + if (my_instance->match == NULL || my_instance->replace == NULL) + { + return NULL; + } + + if (regcomp(&my_instance->re, my_instance->match, REG_ICASE)) + { + free(my_instance->match); + free(my_instance->replace); + free(my_instance); + return NULL; + } + } + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +REGEX_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(REGEX_SESSION))) != NULL) + { + my_session->no_change = 0; + my_session->replacements = 0; + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +} + +/** + * Free the memory associated with this filter session. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +freeSession(FILTER *instance, void *session) +{ + free(session); + return; +} + +/** + * Set the downstream component for this filter. + * + * @param instance The filter instance data + * @param session The session being closed + * @param downstream The downstream filter or router + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +REGEX_SESSION *my_session = (REGEX_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query shoudl normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param queue The query data + */ +static int +routeQuery(FILTER *instance, void *session, GWBUF *queue) +{ +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; +REGEX_SESSION *my_session = (REGEX_SESSION *)session; +char *sql, *newsql; +int length; + + if (modutil_is_SQL(queue)) + { + modutil_extract_SQL(queue, &sql, &length); + newsql = regex_replace(sql, length, &my_instance->re, + my_instance->replace); + if (newsql) + { + queue = modutil_replace_SQL(queue, newsql); + free(newsql); + my_session->replacements++; + } + else + my_session->no_change++; + + } + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * 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 + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; +REGEX_SESSION *my_session = (REGEX_SESSION *)fsession; + + dcb_printf(dcb, "\t\tSearch and replace: s/%s/%s/\n", + my_instance->match, my_instance->replace); + if (my_session) + { + dcb_printf(dcb, "\t\tNo. of queries unaltered by filter: %d\n", + my_session->no_change); + dcb_printf(dcb, "\t\tNo. of queries altered by filter: %d\n", + my_session->replacements); + } +} + +/** + * Perform a regular expression match and subsititution on the SQL + * + * @param sql The original SQL text + * @param length The length of the SQL text + * @param re The compiled regular expression + * @param replace The replacement text + * @return The replaced text or NULL if no replacement was done. + */ +static char * +regex_replace(char *sql, int length, regex_t *re, char *replace) +{ +char *orig, *result, *ptr; +int i, res_size, res_length, rep_length; +int last_match; +regmatch_t match[10]; + + orig = strndup(sql, length); + if (regexec(re, orig, 10, match, 0)) + { + free(orig); + return NULL; + } + res_size = 2 * length; + result = (char *)malloc(res_size); + res_length = 0; + rep_length = strlen(replace); + last_match = 0; + + for (i = 0; i < 10; i++) + { + if (match[i].rm_so != -1) + { + ptr = &result[res_length]; + if (last_match < match[i].rm_so) + { + int to_copy = match[i].rm_so - last_match; + if (last_match + to_copy > res_size) + { + res_size = last_match + to_copy + length; + result = (char *)realloc(result, res_size); + } + memcpy(ptr, &sql[last_match], to_copy); + res_length += to_copy; + } + last_match = match[i].rm_eo; + if (res_length + rep_length > res_size) + { + res_size += rep_length; + result = (char *)realloc(result, res_size); + } + ptr = &result[res_length]; + memcpy(ptr, replace, rep_length); + res_length += rep_length; + } + } + + if (last_match < length) + { + int to_copy = length - last_match; + if (last_match + to_copy > res_size) + { + res_size = last_match + to_copy + 1; + result = (char *)realloc(result, res_size); + } + ptr = &result[res_length]; + memcpy(ptr, &sql[last_match], to_copy); + res_length += to_copy; + } + result[res_length] = 0; + + return result; +} diff --git a/server/modules/filter/testfilter.c b/server/modules/filter/testfilter.c new file mode 100644 index 000000000..270dbd1cb --- /dev/null +++ b/server/modules/filter/testfilter.c @@ -0,0 +1,234 @@ +/* + * This file is distributed as part of MaxScale by SkySQL. 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 SkySQL Ab 2014 + */ +#include +#include +#include +#include + +/** + * testfilter.c - a very simple test filter. + * + * This filter is a very simple example used to test the filter API, + * it merely counts the number of statements that flow through the + * filter pipeline. + * + * Reporting is done via the diagnostics print routine. + */ + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A simple query counting filter" +}; + +static char *version_str = "V1.0.0"; + +static FILTER *createInstance(char **options, FILTER_PARAMETER **params); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + routeQuery, + diagnostic, +}; + +/** + * A dummy instance structure + */ +typedef struct { + int sessions; +} TEST_INSTANCE; + +/** + * A dummy session structure for this test filter + */ +typedef struct { + DOWNSTREAM down; + int count; +} TEST_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +TEST_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(TEST_INSTANCE))) != NULL) + my_instance->sessions = 0; + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +TEST_INSTANCE *my_instance = (TEST_INSTANCE *)instance; +TEST_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(TEST_SESSION))) != NULL) + { + my_instance->sessions++; + my_session->count = 0; + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +} + +/** + * Free the memory associated with this filter session. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +freeSession(FILTER *instance, void *session) +{ + free(session); + return; +} + +/** + * Set the downstream component for this filter. + * + * @param instance The filter instance data + * @param session The session being closed + * @param downstream The downstream filter or router + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +TEST_SESSION *my_session = (TEST_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query shoudl normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param queue The query data + */ +static int +routeQuery(FILTER *instance, void *session, GWBUF *queue) +{ +TEST_SESSION *my_session = (TEST_SESSION *)session; + + if (modutil_is_SQL(queue)) + my_session->count++; + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * 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 + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ +TEST_INSTANCE *my_instance = (TEST_INSTANCE *)instance; +TEST_SESSION *my_session = (TEST_SESSION *)fsession; + + if (my_session) + dcb_printf(dcb, "\t\tNo. of queries routed by filter: %d\n", + my_session->count); + else + dcb_printf(dcb, "\t\tNo. of sessions created: %d\n", + my_instance->sessions); +} diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 3ad4502eb..41bcf0416 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -304,4 +304,5 @@ char *gw_strend(register const char *s); int setnonblocking(int fd); int setipaddress(struct in_addr *a, char *p); int gw_read_gwbuff(DCB *dcb, GWBUF **head, int b); -GWBUF* gw_MySQL_get_next_stmt(GWBUF** p_readbuf); +GWBUF* gw_MySQL_get_next_packet(GWBUF** p_readbuf); + diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 17697f101..00857ae1b 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -31,15 +31,13 @@ #include -/** - * Internal structure used to define the set of backend servers we are routing - * connections to. This provides the storage for routing module specific data - * that is required for each of the backend servers. - */ -typedef struct backend { - SERVER* backend_server; /*< The server itself */ - int backend_conn_count; /*< Number of connections to the server */ -} BACKEND; +typedef enum backend_type_t { + BE_UNDEFINED=-1, + BE_MASTER, + BE_JOINED = BE_MASTER, + BE_SLAVE, + BE_COUNT +} backend_type_t; typedef struct rses_property_st rses_property_t; typedef struct router_client_session ROUTER_CLIENT_SES; @@ -52,14 +50,34 @@ typedef enum rses_property_type_t { RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1 } rses_property_type_t; -typedef enum backend_type_t { - BE_UNDEFINED=-1, - BE_MASTER, - BE_JOINED = BE_MASTER, - BE_SLAVE, - BE_COUNT -} backend_type_t; + +/** + * This criteria is used when backends are chosen for a router session's use. + * Backend servers are sorted to ascending order according to the criteria + * and top N are chosen. + */ +typedef enum select_criteria { + UNDEFINED_CRITERIA=0, + LEAST_GLOBAL_CONNECTIONS, /*< all connections established by MaxScale */ + DEFAULT_CRITERIA=LEAST_GLOBAL_CONNECTIONS, + LEAST_ROUTER_CONNECTIONS, /*< connections established by this router */ + LEAST_BEHIND_MASTER, + LAST_CRITERIA /*< not used except for an index */ +} select_criteria_t; + + +/** default values for rwsplit configuration parameters */ +#define CONFIG_MAX_SLAVE_CONN 1 + +#define GET_SELECT_CRITERIA(s) \ + (strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \ + LEAST_GLOBAL_CONNECTIONS : ( \ + strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \ + LEAST_BEHIND_MASTER : ( \ + strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \ + LEAST_ROUTER_CONNECTIONS : UNDEFINED_CRITERIA))) + /** * Session variable command */ @@ -98,13 +116,63 @@ struct rses_property_st { }; typedef struct sescmd_cursor_st { +#if defined(SS_DEBUG) + skygw_chk_t scmd_cur_chk_top; +#endif ROUTER_CLIENT_SES* scmd_cur_rses; /*< pointer to owning router session */ rses_property_t** scmd_cur_ptr_property; /*< address of pointer to owner property */ mysql_sescmd_t* scmd_cur_cmd; /*< pointer to current session command */ bool scmd_cur_active; /*< true if command is being executed */ - backend_type_t scmd_cur_be_type; /*< BE_MASTER or BE_SLAVE */ +#if defined(SS_DEBUG) + skygw_chk_t scmd_cur_chk_tail; +#endif } sescmd_cursor_t; +/** + * Internal structure used to define the set of backend servers we are routing + * connections to. This provides the storage for routing module specific data + * that is required for each of the backend servers. + * + * Owned by router_instance, referenced by each routing session. + */ +typedef struct backend_st { +#if defined(SS_DEBUG) + skygw_chk_t be_chk_top; +#endif + SERVER* backend_server; /*< The server itself */ + int backend_conn_count; /*< Number of connections to the server */ + bool be_valid; /*< valid when belongs to the router's configuration */ +#if defined(SS_DEBUG) + skygw_chk_t be_chk_tail; +#endif +} BACKEND; + + +/** + * Reference to BACKEND. + * + * Owned by router client session. + */ +typedef struct backend_ref_st { +#if defined(SS_DEBUG) + skygw_chk_t bref_chk_top; +#endif + BACKEND* bref_backend; + DCB* bref_dcb; + sescmd_cursor_t bref_sescmd_cur; +#if defined(SS_DEBUG) + skygw_chk_t bref_chk_tail; +#endif +} backend_ref_t; + + +typedef struct rwsplit_config_st { + int rw_max_slave_conn_percent; + int rw_max_slave_conn_count; + select_criteria_t rw_slave_select_criteria; +} rwsplit_config_t; + + /** * The client session structure used within this router. */ @@ -113,17 +181,18 @@ struct router_client_session { skygw_chk_t rses_chk_top; #endif SPINLOCK rses_lock; /*< protects rses_deleted */ - int rses_versno; /*< even = no active update, else odd */ + int rses_versno; /*< even = no active update, else odd. not used 4/14 */ bool rses_closed; /*< true when closeSession is called */ /** Properties listed by their type */ rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; - BACKEND* rses_backend[BE_COUNT];/*< Backends used by client session */ - DCB* rses_dcb[BE_COUNT]; - /*< cursor is pointer and status variable to current session command */ - sescmd_cursor_t rses_cursor[BE_COUNT]; + backend_ref_t* rses_master_ref; + backend_ref_t* rses_backend_ref; /*< Pointer to backend reference array */ + rwsplit_config_t rses_config; /*< copied config info from router instance */ + int rses_nbackends; int rses_capabilities; /*< input type, for example */ bool rses_autocommit_enabled; bool rses_transaction_active; + uint64_t rses_id; /*< ID for router client session */ struct router_client_session* next; #if defined(SS_DEBUG) skygw_chk_t rses_chk_tail; @@ -151,6 +220,8 @@ typedef struct router_instance { SPINLOCK lock; /*< Lock for the instance data */ BACKEND** servers; /*< Backend servers */ BACKEND* master; /*< NULL or pointer */ + rwsplit_config_t rwsplit_config; /*< expanded config info from SERVICE */ + int rwsplit_version;/*< version number for router's config */ unsigned int bitmask; /*< Bitmask to apply to server->status */ unsigned int bitvalue; /*< Required value of server->status */ ROUTER_STATS stats; /*< Statistics for this router */ @@ -158,7 +229,6 @@ typedef struct router_instance { } ROUTER_INSTANCE; #define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \ - (SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : \ - (SERVER_IS_JOINED((b)->backend_server) ? BE_JOINED : BE_UNDEFINED))); + (SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED)); #endif /*< _RWSPLITROUTER_H */ diff --git a/server/modules/monitor/galera_mon.c b/server/modules/monitor/galera_mon.c index 1f277dcbc..a9f242756 100644 --- a/server/modules/monitor/galera_mon.c +++ b/server/modules/monitor/galera_mon.c @@ -22,8 +22,13 @@ * @verbatim * Revision History * - * Date Who Description - * 22/07/13 Mark Riddoch Initial implementation + * Date Who Description + * 22/07/13 Mark Riddoch Initial implementation + * 21/05/14 Massimiliano Pinto Monitor sets a master server + * that has the lowest value of wsrep_local_index + * 23/05/14 Massimiliano Pinto Added 1 configuration option (setInterval). + * Interval is printed in diagnostics. + * 03/06/14 Mark Riddoch Add support for maintenance mode * * @endverbatim */ @@ -40,12 +45,20 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; static void monitorMain(void *); -static char *version_str = "V1.0.0"; +static char *version_str = "V1.2.0"; + +MODULE_INFO info = { + MODULE_API_MONITOR, + MODULE_ALPHA_RELEASE, + MONITOR_VERSION, + "A Galera cluster monitor" +}; static void *startMonitor(void *); static void stopMonitor(void *); @@ -53,8 +66,9 @@ static void registerServer(void *, SERVER *); static void unregisterServer(void *, SERVER *); static void defaultUsers(void *, char *, char *); static void diagnostics(DCB *, void *); +static void setInterval(void *, unsigned long); -static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics }; +static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics, setInterval, NULL, NULL }; /** * Implementation of the mandatory version entry point @@ -119,9 +133,11 @@ MYSQL_MONITOR *handle; handle->shutdown = 0; handle->defaultUser = NULL; handle->defaultPasswd = NULL; + handle->id = MONITOR_DEFAULT_ID; + handle->interval = MONITOR_INTERVAL; spinlock_init(&handle->lock); } - handle->tid = thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, handle); return handle; } @@ -136,7 +152,7 @@ stopMonitor(void *arg) MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; handle->shutdown = 1; - thread_wait(handle->tid); + thread_wait((void *)handle->tid); } /** @@ -234,7 +250,10 @@ char *sep; dcb_printf(dcb, "\tMonitor stopped\n"); break; } + + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); dcb_printf(dcb, "\tMonitored servers: "); + db = handle->databases; sep = ""; while (db) @@ -280,6 +299,8 @@ MYSQL_RES *result; int num_fields; int isjoined = 0; char *uname = defaultUser, *passwd = defaultPasswd; +unsigned long int server_version = 0; +char *server_string; if (database->server->monuser != NULL) { @@ -289,14 +310,31 @@ char *uname = defaultUser, *passwd = defaultPasswd; if (uname == NULL) return; + /* Don't even probe server flagged as in maintenance */ + if (SERVER_IN_MAINT(database->server)) + return; + if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); + int rc; + int read_timeout = 1; + database->con = mysql_init(NULL); + rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + if (mysql_real_connect(database->con, database->server->name, uname, dpwd, NULL, database->server->port, NULL, 0) == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor was unable to connect to " + "server %s:%d : \"%s\"", + database->server->name, + database->server->port, + mysql_error(database->con)))); server_clear_status(database->server, SERVER_RUNNING); + database->server->node_id = -1; free(dpwd); return; } @@ -306,6 +344,15 @@ char *uname = defaultUser, *passwd = defaultPasswd; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); + /* get server version from current server */ + server_version = mysql_get_server_version(database->con); + + /* get server version string */ + server_string = (char *)mysql_get_server_info(database->con); + if (server_string) { + database->server->server_string = strdup(server_string); + } + /* Check if the the Galera FSM shows this node is joined to the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state_comment'") == 0 && (result = mysql_store_result(database->con)) != NULL) @@ -319,6 +366,25 @@ char *uname = defaultUser, *passwd = defaultPasswd; mysql_free_result(result); } + /* Check the the Galera node index in the cluster */ + if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_index'") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + long local_index = -1; + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + local_index = strtol(row[1], NULL, 10); + if ((errno == ERANGE && (local_index == LONG_MAX + || local_index == LONG_MIN)) || (errno != 0 && local_index == 0)) + { + local_index = -1; + } + database->server->node_id = local_index; + } + mysql_free_result(result); + } + if (isjoined) server_set_status(database->server, SERVER_JOINED); else @@ -335,6 +401,7 @@ monitorMain(void *arg) { MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; MONITOR_SERVERS *ptr; +long master_id; if (mysql_thread_init()) { @@ -347,6 +414,8 @@ MONITOR_SERVERS *ptr; handle->status = MONITOR_RUNNING; while (1) { + master_id = -1; + if (handle->shutdown) { handle->status = MONITOR_STOPPING; @@ -354,12 +423,74 @@ MONITOR_SERVERS *ptr; handle->status = MONITOR_STOPPED; return; } + ptr = handle->databases; + while (ptr) { + unsigned int prev_status = ptr->server->status; monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd); + + /* set master_id to the lowest value of ptr->server->node_id */ + + if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) { + if (ptr->server->node_id < master_id && master_id >= 0) { + master_id = ptr->server->node_id; + } else { + if (master_id < 0) { + master_id = ptr->server->node_id; + } + } + } else if (!SERVER_IN_MAINT(ptr->server)) { + /* clear M/S status */ + server_clear_status(ptr->server, SERVER_SLAVE); + server_clear_status(ptr->server, SERVER_MASTER); + } + if (ptr->server->status != prev_status || + SERVER_IS_DOWN(ptr->server)) + { + LOGIF(LM, (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Backend server %s:%d state : %s", + ptr->server->name, + ptr->server->port, + STRSRVSTATUS(ptr->server)))); + } ptr = ptr->next; } - thread_millisleep(10000); + + ptr = handle->databases; + + /* this server loop sets Master and Slave roles */ + while (ptr) + { + if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && master_id >= 0) { + /* set the Master role */ + if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id == master_id)) { + server_set_status(ptr->server, SERVER_MASTER); + server_clear_status(ptr->server, SERVER_SLAVE); + } else if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id > master_id)) { + /* set the Slave role */ + server_set_status(ptr->server, SERVER_SLAVE); + server_clear_status(ptr->server, SERVER_MASTER); + } + } + + ptr = ptr->next; + } + thread_millisleep(handle->interval); } } + +/** + * Set the monitor sampling interval. + * + * @param arg The handle allocated by startMonitor + * @param interval The interval to set in monitor struct, in milliseconds + */ +static void +setInterval(void *arg, unsigned long interval) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + memcpy(&handle->interval, &interval, sizeof(unsigned long)); +} diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index fbb537a9c..d643a00b9 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -22,12 +22,17 @@ * @verbatim * Revision History * - * Date Who Description - * 08/07/13 Mark Riddoch Initial implementation - * 11/07/13 Mark Riddoch Addition of code to check replication - * status - * 25/07/13 Mark Riddoch Addition of decrypt for passwords and - * diagnostic interface + * Date Who Description + * 08/07/13 Mark Riddoch Initial implementation + * 11/07/13 Mark Riddoch Addition of code to check replication + * status + * 25/07/13 Mark Riddoch Addition of decrypt for passwords and + * diagnostic interface + * 20/05/14 Massimiliano Pinto Addition of support for MariadDB multimaster replication setup. + * New server field version_string is updated. + * 28/05/14 Massimiliano Pinto Added set Id and configuration options (setInverval) + * Parameters are now printed in diagnostics + * 03/06/14 Mark Ridoch Add support for maintenance mode * * @endverbatim */ @@ -44,12 +49,20 @@ #include #include #include +#include extern int lm_enabled_logfiles_bitmask; static void monitorMain(void *); -static char *version_str = "V1.0.0"; +static char *version_str = "V1.2.0"; + +MODULE_INFO info = { + MODULE_API_MONITOR, + MODULE_ALPHA_RELEASE, + MONITOR_VERSION, + "A MySQL Master/Slave replication monitor" +}; static void *startMonitor(void *); static void stopMonitor(void *); @@ -57,8 +70,11 @@ static void registerServer(void *, SERVER *); static void unregisterServer(void *, SERVER *); static void defaultUser(void *, char *, char *); static void diagnostics(DCB *, void *); +static void setInterval(void *, unsigned long); +static void defaultId(void *, unsigned long); +static void replicationHeartbeat(void *, int); -static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUser, diagnostics }; +static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUser, diagnostics, setInterval, defaultId, replicationHeartbeat }; /** * Implementation of the mandatory version entry point @@ -124,9 +140,11 @@ MYSQL_MONITOR *handle; handle->shutdown = 0; handle->defaultUser = NULL; handle->defaultPasswd = NULL; + handle->id = MONITOR_DEFAULT_ID; + handle->interval = MONITOR_INTERVAL; spinlock_init(&handle->lock); } - handle->tid = thread_start(monitorMain, handle); + handle->tid = (THREAD)thread_start(monitorMain, handle); return handle; } @@ -141,7 +159,7 @@ stopMonitor(void *arg) MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; handle->shutdown = 1; - thread_wait(handle->tid); + thread_wait((void *)handle->tid); } /** @@ -259,7 +277,12 @@ char *sep; dcb_printf(dcb, "\tMonitor stopped\n"); break; } + + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id); + dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled"); dcb_printf(dcb, "\tMonitored servers: "); + db = handle->databases; sep = ""; while (db) @@ -278,18 +301,21 @@ char *sep; /** * Monitor an individual server * + * @param handle The MySQL Monitor object * @param database The database to probe - * @param defaultUser Default username for the monitor - * @param defaultPasswd Default password for the monitor */ static void -monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd) +monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { MYSQL_ROW row; MYSQL_RES *result; int num_fields; int ismaster = 0, isslave = 0; -char *uname = defaultUser, *passwd = defaultPasswd; +char *uname = handle->defaultUser, *passwd = handle->defaultPasswd; +unsigned long int server_version = 0; +char *server_string; +unsigned long id = handle->id; +int replication_heartbeat = handle->replicationHeartbeat; if (database->server->monuser != NULL) { @@ -298,10 +324,19 @@ char *uname = defaultUser, *passwd = defaultPasswd; } if (uname == NULL) return; + + /* Don't probe servers in maintenance mode */ + if (SERVER_IN_MAINT(database->server)) + return; + if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); + int rc; + int read_timeout = 1; database->con = mysql_init(NULL); + rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); + if (mysql_real_connect(database->con, database->server->name, uname, @@ -311,6 +346,14 @@ char *uname = defaultUser, *passwd = defaultPasswd; NULL, 0) == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor was unable to connect to " + "server %s:%d : \"%s\"", + database->server->name, + database->server->port, + mysql_error(database->con)))); + free(dpwd); server_clear_status(database->server, SERVER_RUNNING); return; @@ -321,6 +364,34 @@ char *uname = defaultUser, *passwd = defaultPasswd; /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); + /* get server version from current server */ + server_version = mysql_get_server_version(database->con); + + /* get server version string */ + server_string = (char *)mysql_get_server_info(database->con); + if (server_string) { + database->server->server_string = strdup(server_string); + } + + /* get server_id form current node */ + if (mysql_query(database->con, "SELECT @@server_id") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + long server_id = -1; + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + server_id = strtol(row[0], NULL, 10); + if ((errno == ERANGE && (server_id == LONG_MAX + || server_id == LONG_MIN)) || (errno != 0 && server_id == 0)) + { + server_id = -1; + } + database->server->node_id = server_id; + } + mysql_free_result(result); + } + /* Check SHOW SLAVE HOSTS - if we get rows then we are a master */ if (mysql_query(database->con, "SHOW SLAVE HOSTS")) { @@ -328,31 +399,228 @@ char *uname = defaultUser, *passwd = defaultPasswd; { /* Log lack of permission */ } - } - else if ((result = mysql_store_result(database->con)) != NULL) - { + + database->server->rlag = -1; + } else if ((result = mysql_store_result(database->con)) != NULL) { num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { ismaster = 1; } mysql_free_result(result); + + if (ismaster && replication_heartbeat == 1) { + time_t heartbeat; + time_t purge_time; + char heartbeat_insert_query[128]=""; + char heartbeat_purge_query[128]=""; + + handle->master_id = database->server->node_id; + + /* create the maxscale_schema database */ + if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error creating maxscale_schema database in Master server" + ": %s", mysql_error(database->con)))); + + database->server->rlag = -1; + } + + /* create repl_heartbeat table in maxscale_schema database */ + if (mysql_query(database->con, "CREATE TABLE IF NOT EXISTS " + "maxscale_schema.replication_heartbeat " + "(maxscale_id INT NOT NULL, " + "master_server_id INT NOT NULL, " + "master_timestamp INT UNSIGNED NOT NULL, " + "PRIMARY KEY ( master_server_id, maxscale_id ) ) " + "ENGINE=MYISAM DEFAULT CHARSET=latin1")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error creating maxscale_schema.replication_heartbeat table in Master server" + ": %s", mysql_error(database->con)))); + + database->server->rlag = -1; + } + + /* auto purge old values after 48 hours*/ + purge_time = time(0) - (3600 * 48); + + sprintf(heartbeat_purge_query, "DELETE FROM maxscale_schema.replication_heartbeat WHERE master_timestamp < %lu", purge_time); + + if (mysql_query(database->con, heartbeat_purge_query)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error deleting from maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_purge_query, + mysql_error(database->con)))); + } + + heartbeat = time(0); + + /* set node_ts for master as time(0) */ + database->server->node_ts = heartbeat; + + sprintf(heartbeat_insert_query, "UPDATE maxscale_schema.replication_heartbeat SET master_timestamp = %lu WHERE master_server_id = %i AND maxscale_id = %lu", heartbeat, handle->master_id, id); + + /* Try to insert MaxScale timestamp into master */ + if (mysql_query(database->con, heartbeat_insert_query)) { + + database->server->rlag = -1; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error updating maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_insert_query, + mysql_error(database->con)))); + } else { + if (mysql_affected_rows(database->con) == 0) { + heartbeat = time(0); + sprintf(heartbeat_insert_query, "REPLACE INTO maxscale_schema.replication_heartbeat (master_server_id, maxscale_id, master_timestamp ) VALUES ( %i, %lu, %lu)", handle->master_id, id, heartbeat); + + if (mysql_query(database->con, heartbeat_insert_query)) { + + database->server->rlag = -1; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error inserting into maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_insert_query, + mysql_error(database->con)))); + } else { + /* Set replication lag to 0 for the master */ + database->server->rlag = 0; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: heartbeat table inserted data for %s:%i", database->server->name, database->server->port))); + } + } else { + /* Set replication lag as 0 for the master */ + database->server->rlag = 0; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: heartbeat table updated for %s:%i", database->server->name, database->server->port))); + } + } + } } /* Check if the Slave_SQL_Running and Slave_IO_Running status is * set to Yes */ - if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 - && (result = mysql_store_result(database->con)) != NULL) - { - num_fields = mysql_num_fields(result); - while ((row = mysql_fetch_row(result))) + + /* Check first for MariaDB 10.x.x and get status for multimaster replication */ + if (server_version >= 100000) { + + if (mysql_query(database->con, "SHOW ALL SLAVES STATUS") == 0 + && (result = mysql_store_result(database->con)) != NULL) { - if (strncmp(row[10], "Yes", 3) == 0 - && strncmp(row[11], "Yes", 3) == 0) + int i = 0; + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + if (strncmp(row[12], "Yes", 3) == 0 + && strncmp(row[13], "Yes", 3) == 0) { + isslave += 1; + } + i++; + } + mysql_free_result(result); + + if (isslave == i) isslave = 1; + else + isslave = 0; + } + } else { + if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + if (strncmp(row[10], "Yes", 3) == 0 + && strncmp(row[11], "Yes", 3) == 0) + isslave = 1; + } + mysql_free_result(result); + } + } + + /* Get the master_timestamp value from maxscale_schema.replication_heartbeat table */ + if (isslave && replication_heartbeat == 1) { + time_t heartbeat; + char select_heartbeat_query[256] = ""; + + sprintf(select_heartbeat_query, "SELECT master_timestamp " + "FROM maxscale_schema.replication_heartbeat " + "WHERE maxscale_id = %lu AND master_server_id = %i", + id, handle->master_id); + + /* if there is a master then send the query to the slave with master_id*/ + if (handle->master_id >= 0 && (mysql_query(database->con, select_heartbeat_query) == 0 + && (result = mysql_store_result(database->con)) != NULL)) { + num_fields = mysql_num_fields(result); + + while ((row = mysql_fetch_row(result))) { + int rlag = -1; + time_t slave_read; + + heartbeat = time(0); + slave_read = strtoul(row[0], NULL, 10); + + if ((errno == ERANGE && (slave_read == LONG_MAX || slave_read == LONG_MIN)) || (errno != 0 && slave_read == 0)) { + slave_read = 0; + } + + if (slave_read) { + /* set the replication lag */ + rlag = heartbeat - slave_read; + } + + /* set this node_ts as master_timestamp read from replication_heartbeat table */ + database->server->node_ts = slave_read; + + if (rlag >= 0) { + /* store rlag only if greater than monitor sampling interval */ + database->server->rlag = (rlag > (handle->interval / 1000)) ? rlag : 0; + } else { + database->server->rlag = -1; + } + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: replication heartbeat: " + "server %s:%i is %i seconds behind master", + database->server->name, + database->server->port, + database->server->rlag))); + } + mysql_free_result(result); + } else { + database->server->rlag = -1; + database->server->node_ts = 0; + + if (handle->master_id < 0) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: error: replication heartbeat: " + "master_server_id NOT available for %s:%i", + database->server->name, + database->server->port))); + } else { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: error: replication heartbeat: " + "failed selecting from hearthbeat table of %s:%i : [%s], %s", + database->server->name, + database->server->port, + select_heartbeat_query, + mysql_error(database->con)))); + } } - mysql_free_result(result); } if (ismaster) @@ -370,7 +638,6 @@ char *uname = defaultUser, *passwd = defaultPasswd; server_clear_status(database->server, SERVER_SLAVE); server_clear_status(database->server, SERVER_MASTER); } - } /** @@ -405,9 +672,62 @@ MONITOR_SERVERS *ptr; ptr = handle->databases; while (ptr) { - monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd); + unsigned int prev_status = ptr->server->status; + + monitorDatabase(handle, ptr); + + if (ptr->server->status != prev_status || + SERVER_IS_DOWN(ptr->server)) + { + LOGIF(LM, (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Backend server %s:%d state : %s", + ptr->server->name, + ptr->server->port, + STRSRVSTATUS(ptr->server)))); + } + ptr = ptr->next; } - thread_millisleep(10000); + thread_millisleep(handle->interval); } } + +/** + * Set the default id to use in the monitor. + * + * @param arg The handle allocated by startMonitor + * @param id The id to set in monitor struct + */ +static void +defaultId(void *arg, unsigned long id) + { +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + memcpy(&handle->id, &id, sizeof(unsigned long)); + } + +/** + * Set the monitor sampling interval. + * + * @param arg The handle allocated by startMonitor + * @param interval The interval to set in monitor struct, in milliseconds + */ +static void +setInterval(void *arg, unsigned long interval) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + memcpy(&handle->interval, &interval, sizeof(unsigned long)); + } + +/** + * Enable/Disable the MySQL Replication hearbeat, detecting slave lag behind master. + * + * @param arg The handle allocated by startMonitor + * @param replicationHeartbeat To enable it 1, disable it with 0 + */ +static void +replicationHeartbeat(void *arg, int replicationHeartbeat) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + memcpy(&handle->replicationHeartbeat, &replicationHeartbeat, sizeof(int)); +} diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index c2aacd21e..8f5bcd704 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -27,8 +27,10 @@ * @verbatim * Revision History * - * Date Who Description - * 08/07/13 Mark Riddoch Initial implementation + * Date Who Description + * 08/07/13 Mark Riddoch Initial implementation + * 26/05/14 Massimiliano Pinto Default values for MONITOR_INTERVAL + * 28/05/14 Massimiliano Pinto Addition of new fields in MYSQL_MONITOR struct * * @endverbatim */ @@ -54,6 +56,10 @@ typedef struct { int status; /**< Monitor status */ char *defaultUser; /**< Default username for monitoring */ char *defaultPasswd; /**< Default password for monitoring */ + unsigned long interval; /**< Monitor sampling interval */ + unsigned long id; /**< Monitor ID */ + int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */ + int master_id; /**< Master server-id for MySQL Master/Slave replication */ MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ } MYSQL_MONITOR; @@ -61,4 +67,7 @@ typedef struct { #define MONITOR_STOPPING 2 #define MONITOR_STOPPED 3 +#define MONITOR_INTERVAL 10000 // in milliseconds +#define MONITOR_DEFAULT_ID 1UL // unsigned long value + #endif diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index f92e49598..7d06264b9 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -39,6 +39,14 @@ #include #include +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_IN_DEVELOPMENT, + GWPROTOCOL_VERSION, + "An experimental HTTPD implementation for use in admnistration" +}; #define ISspace(x) isspace((int)(x)) #define HTTP_SERVER_STRING "Gateway(c) v.1.0.0" diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 1653de5f0..f262232be 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -43,6 +43,14 @@ * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0 * */ +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_ALPHA_RELEASE, + GWPROTOCOL_VERSION, + "The MySQL to backend server protocol" +}; extern int lm_enabled_logfiles_bitmask; @@ -282,7 +290,13 @@ static int gw_read_backend_event(DCB *dcb) { } /* switch */ } - if (backend_protocol->state == MYSQL_AUTH_FAILED) { + if (backend_protocol->state == MYSQL_AUTH_FAILED) + { + /** + * protocol state won't change anymore, + * lock can be freed + */ + spinlock_release(&dcb->authlock); spinlock_acquire(&dcb->delayqlock); /*< * vraa : errorHandle @@ -306,12 +320,14 @@ static int gw_read_backend_event(DCB *dcb) { /* try reload users' table for next connection */ service_refresh_users(dcb->session->client->service); - while (session->state != SESSION_STATE_ROUTER_READY) + while (session->state != SESSION_STATE_ROUTER_READY && + session->state != SESSION_STATE_STOPPING) { ss_dassert( session->state == SESSION_STATE_READY || session->state == - SESSION_STATE_ROUTER_READY); + SESSION_STATE_ROUTER_READY || + session->state == SESSION_STATE_STOPPING); /** * Session shouldn't be NULL at this point * anymore. Just checking.. @@ -319,10 +335,19 @@ static int gw_read_backend_event(DCB *dcb) { if (session->client->session == NULL) { rc = 1; - goto return_with_lock; + goto return_rc; } usleep(1); } + + if (session->state == SESSION_STATE_STOPPING) + { + goto return_rc; + } + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + /** * rsession shouldn't be NULL since session * state indicates that it was initialized @@ -340,7 +365,7 @@ static int gw_read_backend_event(DCB *dcb) { /* close router_session */ router->closeSession(router_instance, rsession); rc = 1; - goto return_with_lock; + goto return_rc; } else { @@ -357,8 +382,7 @@ static int gw_read_backend_event(DCB *dcb) { /* check the delay queue and flush the data */ if (dcb->delayq) { - backend_write_delayqueue(dcb); - rc = 1; + rc = backend_write_delayqueue(dcb); goto return_with_lock; } } @@ -569,9 +593,8 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) snprintf(str, len+1, "%s", startpoint); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Routing query \"%s\" failed due to " - "authentication failure.", - str))); + "Error : Unable to write to backend due to " + "authentication failure."))); /** Consume query buffer */ while ((queue = gwbuf_consume( queue, @@ -669,6 +692,10 @@ static int gw_error_backend_event(DCB *dcb) { if (session->state == SESSION_STATE_ROUTER_READY) { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + rsession = session->router_session; /*< * rsession should never be NULL here. @@ -874,34 +901,36 @@ static int backend_write_delayqueue(DCB *dcb) spinlock_acquire(&dcb->delayqlock); + if (dcb->delayq == NULL) + { + spinlock_release(&dcb->delayqlock); + rc = 1; + } + else + { localq = dcb->delayq; dcb->delayq = NULL; - spinlock_release(&dcb->delayqlock); rc = dcb_write(dcb, localq); + } if (rc == 0) { - /*< vraa : errorHandle */ - /** - * This error can be muted because it is often due - * unexpected dcb state which means that concurrent thread - * already wrote the queue and closed dcb. - */ -#if 0 LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "%lu [backend_write_delayqueue] Some error occurred in " - "backend.", - pthread_self()))); -#endif + "Error : failed to write buffered data to back-end " + "server. Buffer was empty of back-end was disconnected " + "during operation."))); + mysql_send_custom_error( dcb->session->client, 1, 0, - "Unable to write to backend server. Connection was " - "closed."); + "Failed to write buffered data to back-end server. " + "Buffer was empty or back-end was disconnected during " + "operation."); dcb_close(dcb); } + return rc; } diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 5e4c9bb21..8134005a4 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -36,11 +36,18 @@ * 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake * */ - #include #include #include #include +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_ALPHA_RELEASE, + GWPROTOCOL_VERSION, + "The client to MaxScale MySQL protocol implementation" +}; extern int lm_enabled_logfiles_bitmask; @@ -58,11 +65,7 @@ static int gw_client_hangup_event(DCB *dcb); int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); int MySQLSendHandshake(DCB* dcb); static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); -static int route_by_statement( - ROUTER* router_instance, - ROUTER_OBJECT* router, - void* rsession, - GWBUF* read_buf); +static int route_by_statement(SESSION *, GWBUF *); /* * The "module object" for the mysqld client protocol module. @@ -507,9 +510,10 @@ int gw_read_client_event(DCB* dcb) { ROUTER *router_instance = NULL; void *rsession = NULL; MySQLProtocol *protocol = NULL; + GWBUF *read_buffer = NULL; int b = -1; int rc = 0; - + int nbytes_read = 0; CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); @@ -536,7 +540,6 @@ int gw_read_client_event(DCB* dcb) { /* * Handle the closed client socket. */ - if (b == 0) { char c; int l_errno = 0; @@ -553,7 +556,7 @@ int gw_read_client_event(DCB* dcb) { goto return_rc; } - // close client socket and the sessioA too + // close client socket and the session too dcb->func.close(dcb); } else { // do nothing if reading 1 byte @@ -561,41 +564,80 @@ int gw_read_client_event(DCB* dcb) { goto return_rc; } + rc = gw_read_gwbuff(dcb, &read_buffer, b); + + if (rc != 0) { + goto return_rc; + } + + nbytes_read = gwbuf_length(read_buffer); + ss_dassert(nbytes_read > 0); + + /** + * if read queue existed appent read to it. + * if length of read buffer is less than 3 or less than mysql packet + * then return. + * else copy mysql packets to separate buffers from read buffer and + * continue. + * else + * if read queue didn't exist, length of read is less than 3 or less + * than mysql packet then + * create read queue and append to it and return. + * if length read is less than mysql packet length append to read queue + * append to it and return. + * else (complete packet was read) continue. + */ + if (dcb->dcb_readqueue) + { + uint8_t* data = (uint8_t *)GWBUF_DATA(read_buffer); + + read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer); + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data)) + { + rc = 0; + goto return_rc; + } + else + { + /** + * There is at least one complete mysql packet read + */ + read_buffer = dcb->dcb_readqueue; + dcb->dcb_readqueue = NULL; + } + } + else + { + uint8_t* data = (uint8_t *)GWBUF_DATA(read_buffer); + size_t packetlen = MYSQL_GET_PACKET_LEN(data)+4; + if (nbytes_read < 3 || nbytes_read < packetlen) + { + gwbuf_append(dcb->dcb_readqueue, read_buffer); + rc = 0; + goto return_rc; + } + } + + /** + * Now there should be at least one complete mysql packet in read_buffer. + */ switch (protocol->state) { + case MYSQL_AUTH_SENT: /* * Read all the data that is available into a chain of buffers */ { - int len = -1; - GWBUF *queue = NULL; - GWBUF *gw_buffer = NULL; int auth_val = -1; - ////////////////////////////////////////////////////// - // read and handle errors & close, or return if busy - // note: if b == 0 error handling is not - // triggered, just return - // without closing - ////////////////////////////////////////////////////// - rc = gw_read_gwbuff(dcb, &gw_buffer, b); - - if (rc != 0) { - goto return_rc; - } - - // example with consume, assuming one buffer only ... - queue = gw_buffer; - len = GWBUF_LENGTH(queue); - - ss_dassert(len > 0); - - auth_val = gw_mysql_do_authentication(dcb, queue); - + + auth_val = gw_mysql_do_authentication(dcb, read_buffer); // Data handled withot the dcb->func.write // so consume it now // be sure to consume it all - queue = gwbuf_consume(queue, len); + read_buffer = gwbuf_consume(read_buffer, nbytes_read); if (auth_val == 0) { @@ -638,9 +680,7 @@ int gw_read_client_event(DCB* dcb) { * Read all the data that is available into a chain of buffers */ { - int len = -1; uint8_t cap = 0; - GWBUF *read_buffer = NULL; uint8_t *ptr_buff = NULL; int mysql_command = -1; bool stmt_input; /*< router input type */ @@ -655,22 +695,14 @@ int gw_read_client_event(DCB* dcb) { session->service->router_instance; rsession = session->router_session; } - - ////////////////////////////////////////////////////// - // read and handle errors & close, or return if busy - ////////////////////////////////////////////////////// - rc = gw_read_gwbuff(dcb, &read_buffer, b); - - if (rc != 0) { - goto return_rc; - } + /* Now, we are assuming in the first buffer there is * the information form mysql command */ - len = GWBUF_LENGTH(read_buffer); ptr_buff = GWBUF_DATA(read_buffer); /* get mysql commang at fifth byte */ if (ptr_buff) { + ss_dassert(nbytes_read >= 5); mysql_command = ptr_buff[4]; } /** @@ -695,13 +727,13 @@ int gw_read_client_event(DCB* dcb) { dcb, 1, 0, - "Query routing failed. Connection to " + "Can't route query. Connection to " "backend lost"); protocol->state = MYSQL_IDLE; } rc = 1; /** Free buffer */ - read_buffer = gwbuf_consume(read_buffer, len); + read_buffer = gwbuf_consume(read_buffer, nbytes_read); goto return_rc; } /** Ask what type of input the router expects */ @@ -737,19 +769,15 @@ int gw_read_client_event(DCB* dcb) { /** Route COM_QUIT to backend */ if (mysql_command == '\x01') { - router->routeQuery(router_instance, rsession, read_buffer); + SESSION_ROUTE_QUERY(session, read_buffer); LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_read_client_event] Routed COM_QUIT to " "backend. Close client dcb %p", pthread_self(), dcb))); - - /** close client connection */ - (dcb->func).close(dcb); - /** close backends connection */ - router->closeSession(router_instance, rsession); - rc = 1; + /** close client connection, closes router session too */ + rc = dcb->func.close(dcb); } else { @@ -759,17 +787,17 @@ int gw_read_client_event(DCB* dcb) { * Feed each statement completely and separately * to router. */ - rc = route_by_statement(router_instance, - router, - rsession, - read_buffer); + rc = route_by_statement(session, read_buffer); + if (read_buffer != NULL) + { + /** add incomplete mysql packet to read queue */ + gwbuf_append(dcb->dcb_readqueue, read_buffer); + } } else { /** Feed whole packet to router */ - rc = router->routeQuery(router_instance, - rsession, - read_buffer); + rc = SESSION_ROUTE_QUERY(session, read_buffer); } /** succeed */ @@ -1230,37 +1258,16 @@ return_rc: return rc; } -static int gw_error_client_event(DCB *dcb) { - SESSION* session; - ROUTER_OBJECT* router; - void* router_instance; - void* rsession; - -#if defined(SS_DEBUG) - MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol; - if (dcb->state == DCB_STATE_POLLING || - dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE) +static int gw_error_client_event( + DCB* dcb) { - CHK_PROTOCOL(protocol); - } -#endif + int rc; - session = dcb->session; + CHK_DCB(dcb); - /** - * session may be NULL if session_alloc failed. - * In that case router session was not created. - */ - if (session != NULL) { - CHK_SESSION(session); - router = session->service->router; - router_instance = session->service->router_instance; - rsession = session->router_session; - router->closeSession(router_instance, rsession); - } - dcb_close(dcb); - return 1; + rc = dcb->func.close(dcb); + + return rc; } static int @@ -1287,6 +1294,10 @@ gw_client_close(DCB *dcb) */ if (session != NULL) { CHK_SESSION(session); + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; @@ -1309,42 +1320,11 @@ gw_client_close(DCB *dcb) static int gw_client_hangup_event(DCB *dcb) { - SESSION* session; - ROUTER_OBJECT* router; - void* router_instance; - void* rsession; - int rc = 1; + int rc; - #if defined(SS_DEBUG) - MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol; - if (dcb->state == DCB_STATE_POLLING || - dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE) - { - CHK_PROTOCOL(protocol); - } -#endif CHK_DCB(dcb); + rc = dcb->func.close(dcb); - if (dcb->state != DCB_STATE_POLLING) { - goto return_rc; - } - - session = dcb->session; - /** - * session may be NULL if session_alloc failed. - * In that case router session was not created. - */ - if (session != NULL) { - CHK_SESSION(session); - router = session->service->router; - router_instance = session->service->router_instance; - rsession = session->router_session; - router->closeSession(router_instance, rsession); - } - - dcb_close(dcb); -return_rc: return rc; } @@ -1353,34 +1333,34 @@ return_rc: * Detect if buffer includes partial mysql packet or multiple packets. * Store partial packet to pendingqueue. Send complete packets one by one * to router. + * + * It is assumed readbuf includes at least one complete packet. + * Return 1 in success. If the last packet is incomplete return success but + * leave incomplete packet to readbuf. */ -static int route_by_statement( - ROUTER* router_instance, - ROUTER_OBJECT* router, - void* rsession, - GWBUF* readbuf) +static int route_by_statement(SESSION *session, GWBUF *readbuf) { int rc = -1; - DCB* master_dcb; - GWBUF* stmtbuf; - uint8_t* payload; - static size_t len; + GWBUF* packetbuf; do { - stmtbuf = gw_MySQL_get_next_stmt(&readbuf); - ss_dassert(stmtbuf != NULL); - CHK_GWBUF(stmtbuf); - - payload = (uint8_t *)GWBUF_DATA(stmtbuf); - /** - * If message is longer than read data, suspend routing and - * add statement buffer to wait queue. - */ - rc = router->routeQuery(router_instance, rsession, stmtbuf); + packetbuf = gw_MySQL_get_next_packet(&readbuf); + + if (packetbuf != NULL) + { + CHK_GWBUF(packetbuf); + rc = SESSION_ROUTE_QUERY(session, packetbuf); + } + else + { + rc = 1; + goto return_rc; + } } while (readbuf != NULL); +return_rc: return rc; } diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 354bb4e03..1d0932d7b 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -315,12 +315,17 @@ int gw_receive_backend_auth( /*< * 5th byte is 0x0 if successful. */ - if (ptr[4] == '\x00') { + if (ptr[4] == 0x00) + { rc = 1; - } else { - uint8_t* tmpbuf = - (uint8_t *)calloc(1, GWBUF_LENGTH(head)+1); - memcpy(tmpbuf, ptr, GWBUF_LENGTH(head)); + } + else if (ptr[4] == 0xff) + { + size_t packetlen = MYSQL_GET_PACKET_LEN(ptr)+4; + char* bufstr = (char *)calloc(1, packetlen-3); + + snprintf(bufstr, packetlen-6, "%s", &ptr[7]); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Invalid " @@ -329,11 +334,35 @@ int gw_receive_backend_auth( pthread_self(), dcb, dcb->fd, - tmpbuf[4], - tmpbuf))); + ptr[4], + bufstr))); - free(tmpbuf); - rc = -1; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Invalid authentication message " + "from backend. Msg : %s", + bufstr))); + + free(bufstr); + rc = -1; + } + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_receive_backend_auth] Invalid " + "authentication message from backend dcb %p " + "fd %d, ptr[4] = %p", + pthread_self(), + dcb, + dcb->fd, + ptr[4]))); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Invalid authentication message " + "from backend. Packet type : %p", + ptr[4]))); } /*< * Remove data from buffer. @@ -605,7 +634,7 @@ int gw_do_connect_to_backend( LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error: Establishing connection to backend server " - "%s:%d failed. Socket creation failed due " + "%s:%d failed.\n\t\t Socket creation failed due " "%d, %s.", host, port, @@ -1279,54 +1308,85 @@ mysql_send_auth_error (DCB *dcb, int packet_number, int in_affected_rows, const /** - * Remove the first mysql statement from buffer. Return pointer to the removed - * statement or NULL if buffer is empty. - * - * Clone buf, calculate the length of included mysql stmt, and point the - * statement with cloned buffer. Move the start pointer of buf accordingly - * so that it only cover the remaining buffer. + * Buffer contains at least one of the following: + * complete [complete] [partial] mysql packet * + * return pointer to gwbuf containing a complete packet or + * NULL if no complete packet was found. */ -GWBUF* gw_MySQL_get_next_stmt( +GWBUF* gw_MySQL_get_next_packet( GWBUF** p_readbuf) { - GWBUF* stmtbuf; - size_t buflen; - size_t strlen; - uint8_t* packet; - - if (*p_readbuf == NULL) - { - stmtbuf = NULL; - goto return_stmtbuf; - } - CHK_GWBUF(*p_readbuf); - - if (GWBUF_EMPTY(*p_readbuf)) - { - stmtbuf = NULL; - goto return_stmtbuf; - } - buflen = GWBUF_LENGTH((*p_readbuf)); - packet = GWBUF_DATA((*p_readbuf)); - strlen = MYSQL_GET_PACKET_LEN(packet); + GWBUF* packetbuf; + GWBUF* readbuf; + size_t buflen; + size_t packetlen; + size_t totalbuflen; + uint8_t* data; + readbuf = *p_readbuf; - if (strlen+4 == buflen) + if (readbuf == NULL) { - stmtbuf = *p_readbuf; - *p_readbuf = NULL; - goto return_stmtbuf; - } - /** vraa :Multi-packet stmt is not supported as of 7.3.14 */ - if (strlen-1 > buflen-5) - { - stmtbuf = NULL; - goto return_stmtbuf; - } - stmtbuf = gwbuf_clone_portion(*p_readbuf, 0, strlen+4); - *p_readbuf = gwbuf_consume(*p_readbuf, strlen+4); + packetbuf = NULL; + goto return_packetbuf; + } + CHK_GWBUF(readbuf); -return_stmtbuf: - return stmtbuf; + if (GWBUF_EMPTY(readbuf)) + { + packetbuf = NULL; + goto return_packetbuf; + } + + buflen = GWBUF_LENGTH((readbuf)); + totalbuflen = gwbuf_length(readbuf); + data = (uint8_t *)GWBUF_DATA((readbuf)); + packetlen = MYSQL_GET_PACKET_LEN(data)+4; + + /** packet is incomplete */ + if (packetlen > totalbuflen) + { + packetbuf = NULL; + goto return_packetbuf; + } + + if (packetlen == buflen) + { + packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); + *p_readbuf = gwbuf_consume(readbuf, packetlen); + goto return_packetbuf; + } + /** + * Packet spans multiple buffers. + * Allocate buffer for complete packet + * copy packet parts into it and consume copied bytes + */ + else if (packetlen > buflen) + { + size_t nbytes_copied = 0; + uint8_t* target; + + packetbuf = gwbuf_alloc(packetlen); + target = GWBUF_DATA(packetbuf); + + while (nbytes_copied < packetlen) + { + uint8_t* src = GWBUF_DATA(readbuf); + size_t buflen = GWBUF_LENGTH(readbuf); + + memcpy(target+nbytes_copied, src, buflen); + *p_readbuf = gwbuf_consume(readbuf, buflen); + nbytes_copied += buflen; + } + ss_dassert(nbytes_copied == packetlen); + } + else + { + packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); + *p_readbuf = gwbuf_consume(readbuf, packetlen); + } + +return_packetbuf: + return packetbuf; } diff --git a/server/modules/protocol/tags b/server/modules/protocol/tags deleted file mode 100644 index 9b3d32216..000000000 --- a/server/modules/protocol/tags +++ /dev/null @@ -1,62 +0,0 @@ -GetModuleObject httpd.c /^GetModuleObject()$/ -ISspace httpd.c /^#define ISspace(x) isspace((int)(x))$/ -ModuleInit httpd.c /^ModuleInit()$/ -MySQLSendHandshake mysql_client.c /^MySQLSendHandshake(DCB* dcb)$/ -backend_set_delayqueue mysql_backend.c /^static void backend_set_delayqueue(DCB *dcb, GWBUF/ -backend_write_delayqueue mysql_backend.c /^static int backend_write_delayqueue(DCB *dcb)$/ -gw_MySQLAccept mysql_client.c /^int gw_MySQLAccept(DCB *listener)$/ -gw_MySQLListener mysql_client.c /^int gw_MySQLListener($/ -gw_MySQLWrite_backend mysql_backend.c /^gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)$/ -gw_MySQLWrite_client mysql_client.c /^gw_MySQLWrite_client(DCB *dcb, GWBUF *queue)$/ -gw_MySQL_get_next_stmt mysql_common.c /^GWBUF* gw_MySQL_get_next_stmt($/ -gw_backend_close mysql_backend.c /^gw_backend_close(DCB *dcb)$/ -gw_backend_hangup mysql_backend.c /^gw_backend_hangup(DCB *dcb)$/ -gw_change_user mysql_backend.c /^static int gw_change_user(DCB *backend, SERVER *se/ -gw_check_mysql_scramble_data mysql_common.c /^int gw_check_mysql_scramble_data(DCB *dcb, uint8_t/ -gw_client_close mysql_client.c /^gw_client_close(DCB *dcb)$/ -gw_client_hangup_event mysql_client.c /^gw_client_hangup_event(DCB *dcb)$/ -gw_create_backend_connection mysql_backend.c /^static int gw_create_backend_connection($/ -gw_decode_mysql_server_handshake mysql_common.c /^int gw_decode_mysql_server_handshake(MySQLProtocol/ -gw_do_connect_to_backend mysql_common.c /^int gw_do_connect_to_backend($/ -gw_error_backend_event mysql_backend.c /^static int gw_error_backend_event(DCB *dcb) {$/ -gw_error_client_event mysql_client.c /^static int gw_error_client_event(DCB *dcb) {$/ -gw_find_mysql_user_password_sha1 mysql_common.c /^int gw_find_mysql_user_password_sha1(char *usernam/ -gw_get_or_create_querystr mysql_client.c /^static char* gw_get_or_create_querystr ($/ -gw_get_shared_session_auth_info mysql_backend.c /^static MYSQL_session* gw_get_shared_session_auth_i/ -gw_mysql_close mysql_common.c /^void gw_mysql_close(MySQLProtocol **ptr) {$/ -gw_mysql_do_authentication mysql_client.c /^static int gw_mysql_do_authentication(DCB *dcb, GW/ -gw_mysql_protocol_state2string mysql_common.c /^gw_mysql_protocol_state2string (int state) {$/ -gw_read_backend_event mysql_backend.c /^static int gw_read_backend_event(DCB *dcb) {$/ -gw_read_backend_handshake mysql_common.c /^int gw_read_backend_handshake(MySQLProtocol *conn)/ -gw_read_client_event mysql_client.c /^int gw_read_client_event(DCB* dcb) {$/ -gw_receive_backend_auth mysql_common.c /^int gw_receive_backend_auth($/ -gw_send_authentication_to_backend mysql_common.c /^int gw_send_authentication_to_backend($/ -gw_send_change_user_to_backend mysql_common.c /^int gw_send_change_user_to_backend(char *dbname, c/ -gw_write_backend_event mysql_backend.c /^static int gw_write_backend_event(DCB *dcb) {$/ -gw_write_client_event mysql_client.c /^int gw_write_client_event(DCB *dcb)$/ -httpd_accept httpd.c /^httpd_accept(DCB *dcb)$/ -httpd_close httpd.c /^httpd_close(DCB *dcb)$/ -httpd_error httpd.c /^httpd_error(DCB *dcb)$/ -httpd_get_line httpd.c /^static int httpd_get_line(int sock, char *buf, int/ -httpd_hangup httpd.c /^httpd_hangup(DCB *dcb)$/ -httpd_listen httpd.c /^httpd_listen(DCB *listener, char *config)$/ -httpd_read_event httpd.c /^httpd_read_event(DCB* dcb)$/ -httpd_send_headers httpd.c /^static void httpd_send_headers(DCB *dcb, int final/ -httpd_write httpd.c /^httpd_write(DCB *dcb, GWBUF *queue)$/ -httpd_write_event httpd.c /^httpd_write_event(DCB *dcb)$/ -mysql_protocol_init mysql_common.c /^MySQLProtocol* mysql_protocol_init($/ -mysql_send_auth_error mysql_common.c /^mysql_send_auth_error (DCB *dcb, int packet_number/ -mysql_send_custom_error mysql_common.c /^mysql_send_custom_error (DCB *dcb, int packet_numb/ -mysql_send_ok mysql_client.c /^mysql_send_ok(DCB *dcb, int packet_number, int in_/ -route_by_statement mysql_client.c /^static int route_by_statement($/ -telnetd_accept telnetd.c /^telnetd_accept(DCB *dcb)$/ -telnetd_close telnetd.c /^telnetd_close(DCB *dcb)$/ -telnetd_command telnetd.c /^telnetd_command(DCB *dcb, unsigned char *cmd)$/ -telnetd_echo telnetd.c /^telnetd_echo(DCB *dcb, int enable)$/ -telnetd_error telnetd.c /^telnetd_error(DCB *dcb)$/ -telnetd_hangup telnetd.c /^telnetd_hangup(DCB *dcb)$/ -telnetd_listen telnetd.c /^telnetd_listen(DCB *listener, char *config)$/ -telnetd_read_event telnetd.c /^telnetd_read_event(DCB* dcb)$/ -telnetd_write telnetd.c /^telnetd_write(DCB *dcb, GWBUF *queue)$/ -telnetd_write_event telnetd.c /^telnetd_write_event(DCB *dcb)$/ -version httpd.c /^version()$/ diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index f7d6c1815..86e98f397 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -36,6 +36,14 @@ #include #include #include +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_ALPHA_RELEASE, + GWPROTOCOL_VERSION, + "A telnet deamon protocol for simple administration interface" +}; extern int lm_enabled_logfiles_bitmask; @@ -140,9 +148,6 @@ telnetd_read_event(DCB* dcb) int n; GWBUF *head = NULL; SESSION *session = dcb->session; -ROUTER_OBJECT *router = session->service->router; -ROUTER *router_instance = session->service->router_instance; -void *rsession = session->router_session; TELNETD *telnetd = (TELNETD *)dcb->protocol; char *password, *t; @@ -196,7 +201,7 @@ char *password, *t; free(password); break; case TELNETD_STATE_DATA: - router->routeQuery(router_instance, rsession, head); + SESSION_ROUTE_QUERY(session, head); break; } } diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 1737a0f76..5e2c45845 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -49,8 +49,8 @@ MODULES= libdebugcli.so libreadconnroute.so libtestroute.so all: $(MODULES) - (cd readwritesplit; make ) - (cd binlog; make ) + (cd readwritesplit; make) + (cd binlog; make) libtestroute.so: $(TESTOBJ) $(CC) $(LDFLAGS) $(TESTOBJ) $(LIBS) -o $@ @@ -70,12 +70,10 @@ libreadwritesplit.so: clean: rm -f $(OBJ) $(MODULES) (cd readwritesplit; touch depend.mk; make clean) - (cd binlog; touch depend.mk; make clean) tags: ctags $(SRCS) $(HDRS) (cd readwritesplit; make tags) - (cd binlog; make tags) depend: @rm -f depend.mk diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 006b30861..0fdb7961c 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -200,6 +200,9 @@ int i; * user= * password= * master-id= + * filestem= + * lowwater= + * highwater= */ if (options) { diff --git a/server/modules/routing/debugcli.c b/server/modules/routing/debugcli.c index d6becdfda..bf2404c08 100644 --- a/server/modules/routing/debugcli.c +++ b/server/modules/routing/debugcli.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -43,9 +44,17 @@ #include #include + +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_ALPHA_RELEASE, + ROUTER_VERSION, + "The debug user interface" +}; + extern int lm_enabled_logfiles_bitmask; -static char *version_str = "V1.1.0"; +static char *version_str = "V1.1.1"; /* The router entry points */ static ROUTER *createInstance(SERVICE *service, char **options); diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index b78620b09..4bd19e957 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -40,6 +40,7 @@ * 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 * * @endverbatim */ @@ -50,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +79,8 @@ #define ARG_TYPE_SESSION 6 #define ARG_TYPE_DCB 7 #define ARG_TYPE_MONITOR 8 +#define ARG_TYPE_FILTER 9 + /** * The subcommand structure * @@ -112,6 +116,14 @@ struct subcommand showoptions[] = { "Show the poll statistics", "Show the poll statistics", {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", + {ARG_TYPE_FILTER, 0, 0} }, + { "filters", 0, dprintAllFilters, + "Show all filters", + "Show all filters", + {0, 0, 0} }, { "modules", 0, dprintAllModules, "Show all currently loaded modules", "Show all currently loaded modules", @@ -152,6 +164,42 @@ struct subcommand showoptions[] = { {0, 0, 0} } }; +/** + * The subcommands of the list command + */ +struct subcommand listoptions[] = { + { "dcbs", 0, dListDCBs, + "List all the DCBs active within MaxScale", + "List all the DCBs active within MaxScale", + {0, 0, 0} }, + { "filters", 0, dListFilters, + "List all the filters defined within MaxScale", + "List all the filters defined within MaxScale", + {0, 0, 0} }, + { "listeners", 0, dListListeners, + "List all the listeners defined within MaxScale", + "List all the listeners defined within MaxScale", + {0, 0, 0} }, + { "modules", 0, dprintAllModules, + "Show all currently loaded modules", + "Show all currently loaded modules", + {0, 0, 0} }, + { "services", 0, dListServices, + "List all the services defined within MaxScale", + "List all the services defined within MaxScale", + {0, 0, 0} }, + { "servers", 0, dListServers, + "List all the servers defined within MaxScale", + "List all the servers defined within MaxScale", + {0, 0, 0} }, + { "sessions", 0, dListSessions, + "List all the active sessions within MaxScale", + "List all the active sessions within MaxScale", + {0, 0, 0} }, + { NULL, 0, NULL, NULL, NULL, + {0, 0, 0} } +}; + extern void shutdown_server(); static void shutdown_service(DCB *dcb, SERVICE *service); static void shutdown_monitor(DCB *dcb, MONITOR *monitor); @@ -395,17 +443,18 @@ static struct { } cmds[] = { { "add", addoptions }, { "clear", clearoptions }, + { "disable", disableoptions }, + { "enable", enableoptions }, +#if defined(SS_DEBUG) + { "fail", failoptions }, +#endif + { "list", listoptions }, + { "reload", reloadoptions }, { "remove", removeoptions }, { "restart", restartoptions }, { "set", setoptions }, { "show", showoptions }, { "shutdown", shutdownoptions }, - { "reload", reloadoptions }, - { "enable", enableoptions }, - { "disable", disableoptions }, -#if defined(SS_DEBUG) - { "fail", failoptions }, -#endif { NULL, NULL } }; @@ -463,6 +512,10 @@ SERVICE *service; if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) rval = (unsigned long)monitor_find(arg); return rval; + case ARG_TYPE_FILTER: + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + rval = (unsigned long)filter_find(arg); + return rval; } return 0; } @@ -486,8 +539,7 @@ execute_cmd(CLI_SESSION *cli) { DCB *dcb = cli->session->client; int argc, i, j, found = 0; -char *args[MAXARGS]; -char *saveptr, *delim = " \t\r\n"; +char *args[MAXARGS + 1]; unsigned long arg1, arg2, arg3; int in_quotes = 0, escape_next = 0; char *ptr, *lptr; @@ -725,11 +777,13 @@ static struct { char *str; unsigned int bit; } ServerBits[] = { - { "running", SERVER_RUNNING }, - { "master", SERVER_MASTER }, - { "slave", SERVER_SLAVE }, - { "synced", SERVER_JOINED }, - { NULL, 0 } + { "running", SERVER_RUNNING }, + { "master", SERVER_MASTER }, + { "slave", SERVER_SLAVE }, + { "synced", SERVER_JOINED }, + { "maintenance", SERVER_MAINT }, + { "maint", SERVER_MAINT }, + { NULL, 0 } }; /** * Map the server status bit diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index f587b2c63..0652f9f0c 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -79,6 +79,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,13 @@ extern int lm_enabled_logfiles_bitmask; +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_ALPHA_RELEASE, + ROUTER_VERSION, + "A connection based router to load balance based on connections" +}; + static char *version_str = "V1.0.2"; /* The router entry points */ @@ -252,10 +260,12 @@ int i, n; } else { - LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, - "Warning : Unsupported router " - "option %s for readconnroute.", + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : Unsupported router " + "option \'%s\' for readconnroute. " + "Expected router options are " + "[slave|master|synced]", options[i]))); } } @@ -342,6 +352,9 @@ int master_host = -1; inst->bitmask))); } + if (SERVER_IN_MAINT(inst->servers[i]->server)) + continue; + /* * If router_options=slave, get the running master * It will be used if there are no running slaves at all diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index dcea42d19..9fec7e1bd 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -30,6 +30,15 @@ #include #include #include +#include + +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_ALPHA_RELEASE, + ROUTER_VERSION, + "A Read/Write splitting router for enhancement read scalability" +}; + extern int lm_enabled_logfiles_bitmask; @@ -70,12 +79,46 @@ static void clientReply( DCB* backend_dcb); static uint8_t getCapabilities (ROUTER* inst, void* router_session); +int bref_cmp_global_conn( + const void* bref1, + const void* bref2); -static bool search_backend_servers( - BACKEND** p_master, - BACKEND** p_slave, +int bref_cmp_router_conn( + const void* bref1, + const void* bref2); + +int bref_cmp_behind_master( + const void* bref1, + const void* bref2); + +int (*criteria_cmpfun[LAST_CRITERIA])(const void*, const void*)= +{ + NULL, + bref_cmp_global_conn, + bref_cmp_router_conn, + bref_cmp_behind_master +}; + +static bool select_connect_backend_servers( + backend_ref_t** p_master_ref, + backend_ref_t* backend_ref, + int router_nservers, + int max_nslaves, + select_criteria_t select_criteria, + SESSION* session, ROUTER_INSTANCE* router); +static bool get_dcb( + DCB** dcb, + ROUTER_CLIENT_SES* rses, + backend_type_t btype); + +static void rwsplit_process_options( + ROUTER_INSTANCE* router, + char** options); + + + static ROUTER_OBJECT MyObject = { createInstance, newSession, @@ -118,13 +161,8 @@ static void rses_property_done( static mysql_sescmd_t* rses_property_get_sescmd( rses_property_t* prop); -static sescmd_cursor_t* rses_get_sescmd_cursor( - ROUTER_CLIENT_SES* rses, - backend_type_t be_type); - static bool execute_sescmd_in_backend( - ROUTER_CLIENT_SES* rses, - backend_type_t be_type); + backend_ref_t* backend_ref); static void sescmd_cursor_set_active( sescmd_cursor_t* sescmd_cursor, @@ -147,14 +185,10 @@ static GWBUF* sescmd_cursor_process_replies( GWBUF* replybuf, sescmd_cursor_t* scur); -static bool cont_exec_sescmd_in_backend( - ROUTER_CLIENT_SES* rses, - backend_type_t be_type); - static void tracelog_routed_query( ROUTER_CLIENT_SES* rses, char* funcname, - DCB* dcb, + backend_ref_t* bref, GWBUF* buf); static bool route_session_write( @@ -164,6 +198,10 @@ static bool route_session_write( unsigned char packet_type, skygw_query_type_t qtype); +static void refreshInstance( + ROUTER_INSTANCE* router, + CONFIG_PARAMETER* param); + static SPINLOCK instlock; static ROUTER_INSTANCE* instances; @@ -205,6 +243,35 @@ ROUTER_OBJECT* GetModuleObject() return &MyObject; } +static void refreshInstance( + ROUTER_INSTANCE* router, + CONFIG_PARAMETER* param) +{ + config_param_type_t paramtype; + + paramtype = config_get_paramtype(param); + + if (paramtype == COUNT_TYPE) + { + if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + { + router->rwsplit_config.rw_max_slave_conn_percent = 0; + router->rwsplit_config.rw_max_slave_conn_count = + config_get_valint(param, NULL, paramtype); + } + } + else if (paramtype == PERCENT_TYPE) + { + if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + { + router->rwsplit_config.rw_max_slave_conn_count = 0; + router->rwsplit_config.rw_max_slave_conn_percent = + config_get_valint(param, NULL, paramtype); + } + } +} + + /** * Create an instance of read/write statemtn router within the MaxScale. * @@ -220,8 +287,9 @@ static ROUTER* createInstance( { ROUTER_INSTANCE* router; SERVER* server; - int n; + int nservers; int i; + CONFIG_PARAMETER* param; if ((router = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; @@ -231,49 +299,50 @@ static ROUTER* createInstance( /** Calculate number of servers */ server = service->databases; + nservers = 0; - for (n=0; server != NULL; server=server->nextdb) { - n++; + while (server != NULL) + { + nservers++; + server=server->nextdb; } - router->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *)); + router->servers = (BACKEND **)calloc(nservers + 1, sizeof(BACKEND *)); if (router->servers == NULL) { free(router); return NULL; } - - if (options != NULL) - { - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Router options supplied to read/write statement router " - "module but none are supported. The options will be " - "ignored."))); - } /** * Create an array of the backend servers in the router structure to * maintain a count of the number of connections to each * backend server. */ server = service->databases; - n = 0; + nservers= 0; + while (server != NULL) { - if ((router->servers[n] = malloc(sizeof(BACKEND))) == NULL) + if ((router->servers[nservers] = malloc(sizeof(BACKEND))) == NULL) { - for (i = 0; i < n; i++) { + /** clean up */ + for (i = 0; i < nservers; i++) { free(router->servers[i]); } free(router->servers); free(router); return NULL; } - router->servers[n]->backend_server = server; - router->servers[n]->backend_conn_count = 0; - n += 1; + router->servers[nservers]->backend_server = server; + router->servers[nservers]->backend_conn_count = 0; + router->servers[nservers]->be_valid = false; +#if defined(SS_DEBUG) + router->servers[nservers]->be_chk_top = CHK_NUM_BACKEND; + router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND; +#endif + nservers += 1; server = server->nextdb; } - router->servers[n] = NULL; + router->servers[nservers] = NULL; /** * vraa : is this necessary for readwritesplit ? @@ -288,25 +357,32 @@ static ROUTER* createInstance( router->bitvalue = 0; if (options) { - for (i = 0; options[i]; i++) - { - if (!strcasecmp(options[i], "synced")) - { - router->bitmask |= (SERVER_JOINED); - router->bitvalue |= SERVER_JOINED; - } - else - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Warning : Unsupported " - "router option \"%s\" " - "for readwritesplit router.", - options[i]))); - } - } + rwsplit_process_options(router, options); } + /** + * Set default value for max_slave_connections and for slave selection + * criteria. If parameter is set in config file max_slave_connections + * will be overwritten. + */ + router->rwsplit_config.rw_max_slave_conn_count = CONFIG_MAX_SLAVE_CONN; + + if (router->rwsplit_config.rw_slave_select_criteria == UNDEFINED_CRITERIA) + { + router->rwsplit_config.rw_slave_select_criteria = DEFAULT_CRITERIA; + } + /** + * Copy all config parameters from service to router instance. + * Finally, copy version number to indicate that configs match. + */ + param = config_get_param(service->svc_config_param, "max_slave_connections"); + + if (param != NULL) + { + refreshInstance(router, param); + router->rwsplit_version = service->svc_config_version; + } + /** * We have completed the creation of the router data, so now * insert this router into the linked list of routers * that have been created with this module. @@ -333,93 +409,192 @@ static void* newSession( ROUTER* router_inst, SESSION* session) { - BACKEND* local_backend[BE_COUNT]; - ROUTER_CLIENT_SES* client_rses; + backend_ref_t* backend_ref; /*< array of backend references (DCB,BACKEND,cursor) */ + backend_ref_t* master_ref = NULL; /*< pointer to selected master */ + BACKEND** b; + ROUTER_CLIENT_SES* client_rses = NULL; ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_inst; bool succp; + int router_nservers = 0; /*< # of servers in total */ + int max_nslaves; /*< max # of slaves used in this session */ + int conf_max_nslaves; /*< value from configuration file */ + int i; + const int min_nservers = 1; /*< hard-coded for now */ + static uint64_t router_client_ses_seq; /*< ID for client session */ - client_rses = - (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); + client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); if (client_rses == NULL) { ss_dassert(false); - return NULL; + goto return_rses; } - memset(local_backend, 0, BE_COUNT*sizeof(void*)); - spinlock_init(&client_rses->rses_lock); #if defined(SS_DEBUG) client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; -#endif - /** store pointers to sescmd list to both cursors */ - client_rses->rses_cursor[BE_MASTER].scmd_cur_rses = client_rses; - client_rses->rses_cursor[BE_MASTER].scmd_cur_active = false; - client_rses->rses_cursor[BE_MASTER].scmd_cur_ptr_property = - &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - client_rses->rses_cursor[BE_MASTER].scmd_cur_cmd = NULL; - client_rses->rses_cursor[BE_MASTER].scmd_cur_be_type = BE_MASTER; - - client_rses->rses_cursor[BE_SLAVE].scmd_cur_rses = client_rses; - client_rses->rses_cursor[BE_SLAVE].scmd_cur_active = false; - client_rses->rses_cursor[BE_SLAVE].scmd_cur_ptr_property = - &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - client_rses->rses_cursor[BE_SLAVE].scmd_cur_cmd = NULL; - client_rses->rses_cursor[BE_SLAVE].scmd_cur_be_type = BE_SLAVE; - +#endif /** - * Find a backend server to connect to. This is the extent of the - * load balancing algorithm we need to implement for this simple - * connection router. + * If service config has been changed, reload config from service to + * router instance first. */ - succp = search_backend_servers(&local_backend[BE_MASTER], - &local_backend[BE_SLAVE], - router); + spinlock_acquire(&router->lock); + if (router->service->svc_config_version > router->rwsplit_version) + { + CONFIG_PARAMETER* param = router->service->svc_config_param; + + while (param != NULL) + { + refreshInstance(router, param); + param = param->next; + } + router->rwsplit_version = router->service->svc_config_version; + /** Read options */ + rwsplit_process_options(router, router->service->routerOptions); + } + /** Copy config struct from router instance */ + client_rses->rses_config = router->rwsplit_config; + /** Create ID for the new client (router_client_ses) session */ + client_rses->rses_id = router_client_ses_seq += 1; + + spinlock_release(&router->lock); + /** + * Set defaults to session variables. + */ + client_rses->rses_autocommit_enabled = true; + client_rses->rses_transaction_active = false; + + /** count servers */ + b = router->servers; + while (*(b++) != NULL) router_nservers++; + + /** With too few servers session is not created */ + if (router_nservers < min_nservers || + MAX(client_rses->rses_config.rw_max_slave_conn_count, + (router_nservers*client_rses->rses_config.rw_max_slave_conn_percent)/100) + < min_nservers) + { + if (router_nservers < min_nservers) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers available. Found %d " + "when %d is required.", + router->service->name, + router_nservers, + min_nservers))); + } + else + { + double pct = client_rses->rses_config.rw_max_slave_conn_percent/100; + double nservers = (double)router_nservers*pct; - /** Both Master and Slave must be found */ - if (!succp) { + if (client_rses->rses_config.rw_max_slave_conn_count < + min_nservers) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers configured in " + "MaxScale.cnf. Found %d when %d is required.", + router->service->name, + client_rses->rses_config.rw_max_slave_conn_count, + min_nservers))); + } + if (nservers < min_nservers) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers configured in " + "MaxScale.cnf. Found %d%% when at least %.0f%% " + "would be required.", + router->service->name, + client_rses->rses_config.rw_max_slave_conn_percent, + min_nservers/(((double)router_nservers)/100)))); + } + } free(client_rses); - return NULL; + client_rses = NULL; + goto return_rses; } /** - * Open the slave connection. + * Create backend reference objects for this session. */ - client_rses->rses_dcb[BE_SLAVE] = dcb_connect( - local_backend[BE_SLAVE]->backend_server, - session, - local_backend[BE_SLAVE]->backend_server->protocol); + backend_ref = (backend_ref_t *)calloc (1, router_nservers*sizeof(backend_ref_t)); - if (client_rses->rses_dcb[BE_SLAVE] == NULL) { - ss_dassert(session->refcount == 1); + if (backend_ref == NULL) + { + /** log this */ free(client_rses); - return NULL; + free(backend_ref); + client_rses = NULL; + goto return_rses; } /** - * Open the master connection. + * Initialize backend references with BACKEND ptr. + * Initialize session command cursors for each backend reference. */ - client_rses->rses_dcb[BE_MASTER] = dcb_connect( - local_backend[BE_MASTER]->backend_server, - session, - local_backend[BE_MASTER]->backend_server->protocol); - if (client_rses->rses_dcb[BE_MASTER] == NULL) + for (i=0; i< router_nservers; i++) { - /** Close slave connection first. */ - client_rses->rses_dcb[BE_SLAVE]->func.close(client_rses->rses_dcb[BE_SLAVE]); - free(client_rses); - return NULL; - } - /** - * We now have a master and a slave server with the least connections. - * Bump the connection counts for these servers. +#if defined(SS_DEBUG) + backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; + backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; +#endif + backend_ref[i].bref_backend = router->servers[i]; + /** store pointers to sescmd list to both cursors */ + backend_ref[i].bref_sescmd_cur.scmd_cur_rses = client_rses; + backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; + backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = + &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; + backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; + } + /** + * Find out the number of read backend servers. + * Depending on the configuration value type, either copy direct count + * of slave connections or calculate the count from percentage value. */ - atomic_add(&local_backend[BE_SLAVE]->backend_conn_count, 1); - atomic_add(&local_backend[BE_MASTER]->backend_conn_count, 1); + if (client_rses->rses_config.rw_max_slave_conn_count > 0) + { + conf_max_nslaves = client_rses->rses_config.rw_max_slave_conn_count; + } + else + { + conf_max_nslaves = + (router_nservers*client_rses->rses_config.rw_max_slave_conn_percent)/100; + } + max_nslaves = MIN(router_nservers-1, MAX(1, conf_max_nslaves)); + + spinlock_init(&client_rses->rses_lock); + client_rses->rses_backend_ref = backend_ref; - client_rses->rses_backend[BE_SLAVE] = local_backend[BE_SLAVE]; - client_rses->rses_backend[BE_MASTER] = local_backend[BE_MASTER]; + /** + * Find a backend servers to connect to. + */ + succp = select_connect_backend_servers(&master_ref, + backend_ref, + router_nservers, + max_nslaves, + client_rses->rses_config.rw_slave_select_criteria, + session, + router); + + /** Both Master and at least 1 slave must be found */ + if (!succp) { + free(client_rses->rses_backend_ref); + free(client_rses); + client_rses = NULL; + goto return_rses; + } + /** Copy backend pointers to router session. */ + client_rses->rses_master_ref = master_ref; + client_rses->rses_backend_ref = backend_ref; + client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ + client_rses->rses_capabilities = RCAP_TYPE_STMT_INPUT; router->stats.n_sessions += 1; - client_rses->rses_capabilities = RCAP_TYPE_STMT_INPUT; /** * Version is bigger than zero once initialized. */ @@ -433,8 +608,13 @@ static void* newSession( router->connections = client_rses; spinlock_release(&router->lock); +return_rses: +#if defined(SS_DEBUG) + if (client_rses != NULL) + { CHK_CLIENT_RSES(client_rses); - + } +#endif return (void *)client_rses; } @@ -452,41 +632,53 @@ static void closeSession( void* router_session) { ROUTER_CLIENT_SES* router_cli_ses; - DCB* slave_dcb; - DCB* master_dcb; + backend_ref_t* backend_ref; router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); + + backend_ref = router_cli_ses->rses_backend_ref; /** * Lock router client session for secure read and update. */ - if (rses_begin_locked_router_action(router_cli_ses)) + if (!router_cli_ses->rses_closed && + rses_begin_locked_router_action(router_cli_ses)) { - /** decrease server current connection counters */ - atomic_add(&router_cli_ses->rses_backend[BE_SLAVE]->backend_server->stats.n_current, -1); - atomic_add(&router_cli_ses->rses_backend[BE_MASTER]->backend_server->stats.n_current, -1); - - slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; - router_cli_ses->rses_dcb[BE_SLAVE] = NULL; - master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; - router_cli_ses->rses_dcb[BE_MASTER] = NULL; + int i = 0; + /** + * session must be moved to SESSION_STATE_STOPPING state before + * router session is closed. + */ +#if defined(SS_DEBUG) + SESSION* ses = get_session_by_router_ses((void*)router_cli_ses); - router_cli_ses->rses_closed = true; - /** Unlock */ - rses_end_locked_router_action(router_cli_ses); + ss_dassert(ses != NULL); + ss_dassert(ses->state == SESSION_STATE_STOPPING); +#endif /** - * Close the backend server connections + * This sets router closed. Nobody is allowed to use router + * whithout checking this first. */ - if (slave_dcb != NULL) { - CHK_DCB(slave_dcb); - slave_dcb->func.close(slave_dcb); + router_cli_ses->rses_closed = true; + + for (i=0; irses_nbackends; i++) + { + DCB* dcb = backend_ref[i].bref_dcb; + + /** Close those which had been connected */ + if (dcb != NULL) + { + CHK_DCB(dcb); + backend_ref[i].bref_dcb = NULL; /*< prevent new uses of DCB */ + dcb->func.close(dcb); + /** decrease server current connection counters */ + atomic_add(&backend_ref[i].bref_backend->backend_server->stats.n_current, -1); + atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); } - - if (master_dcb != NULL) { - master_dcb->func.close(master_dcb); - CHK_DCB(master_dcb); } + /** Unlock */ + rses_end_locked_router_action(router_cli_ses); } } @@ -497,13 +689,21 @@ static void freeSession( ROUTER_CLIENT_SES* router_cli_ses; ROUTER_INSTANCE* router; int i; + backend_ref_t* backend_ref; router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; router = (ROUTER_INSTANCE *)router_instance; + backend_ref = router_cli_ses->rses_backend_ref; - atomic_add(&router_cli_ses->rses_backend[BE_SLAVE]->backend_conn_count, -1); - atomic_add(&router_cli_ses->rses_backend[BE_MASTER]->backend_conn_count, -1); - + for (i=0; irses_nbackends; i++) + { + if (backend_ref[i].bref_dcb == NULL) + { + continue; + } + ss_dassert(backend_ref[i].bref_backend->backend_conn_count > 0); + atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); + } spinlock_acquire(&router->lock); if (router->connections == router_cli_ses) { @@ -542,10 +742,91 @@ static void freeSession( * all the memory and other resources associated * to the client session. */ + free(router_cli_ses->rses_backend_ref); free(router_cli_ses); return; } + +static bool get_dcb( + DCB** p_dcb, + ROUTER_CLIENT_SES* rses, + backend_type_t btype) +{ + backend_ref_t* backend_ref; + int smallest_nconn = -1; + int i; + bool succp = false; + + CHK_CLIENT_RSES(rses); + ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); + + if (p_dcb == NULL) + { + goto return_succp; + } + backend_ref = rses->rses_backend_ref; + + if (btype == BE_SLAVE) + { + for (i=0; irses_nbackends; i++) + { + BACKEND* b = backend_ref[i].bref_backend; + + if (backend_ref[i].bref_dcb != NULL && + SERVER_IS_SLAVE(b->backend_server) && + (smallest_nconn == -1 || + b->backend_conn_count < smallest_nconn)) + { + *p_dcb = backend_ref[i].bref_dcb; + smallest_nconn = b->backend_conn_count; + succp = true; + } + } + + if (!succp) + { + backend_ref = rses->rses_master_ref; + + if (backend_ref->bref_dcb != NULL) + { + *p_dcb = backend_ref->bref_dcb; + succp = true; + + ss_dassert( + SERVER_IS_MASTER(backend_ref->bref_backend->backend_server) && + smallest_nconn == -1); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning : No slaves connected nor " + "available. Choosing master %s:%d " + "instead.", + backend_ref->bref_backend->backend_server->name, + backend_ref->bref_backend->backend_server->port))); + } + } + ss_dassert(succp); + } + else if (btype == BE_MASTER) + { + for (i=0; irses_nbackends; i++) + { + BACKEND* b = backend_ref[i].bref_backend; + + if (backend_ref[i].bref_dcb != NULL && + (SERVER_IS_MASTER(b->backend_server))) + { + *p_dcb = backend_ref[i].bref_dcb; + succp = true; + goto return_succp; + } + } + } +return_succp: + return succp; +} + /** * The main routing entry, this is called with every packet that is * received and has to be forwarded to the backend database. @@ -579,13 +860,8 @@ static int routeQuery( DCB* slave_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - bool rses_is_closed; - rses_property_t* prop; + bool rses_is_closed = false; size_t len; - /** if false everything goes to master and session commands to slave too */ - static bool autocommit_enabled = true; - /** if true everything goes to master and session commands to slave too */ - static bool transaction_active = false; CHK_CLIENT_RSES(router_cli_ses); @@ -594,27 +870,13 @@ static int routeQuery( { rses_is_closed = true; } - else - { - /*< Lock router client session for secure read of DCBs */ - rses_is_closed = - !(rses_begin_locked_router_action(router_cli_ses)); - } - - if (!rses_is_closed) - { - master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; - slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; - /** unlock */ - rses_end_locked_router_action(router_cli_ses); - } - packet = GWBUF_DATA(querybuf); packet_type = packet[4]; - if (rses_is_closed || (master_dcb == NULL && slave_dcb == NULL)) + if (rses_is_closed) { - LOGIF(LE, (skygw_log_write_flush( + LOGIF(LE, + (skygw_log_write_flush( LOGFILE_ERROR, "Error: Failed to route %s:%s:\"%s\" to " "backend server. %s.", @@ -629,6 +891,9 @@ static int routeQuery( inst->stats.n_queries++; startpos = (char *)&packet[5]; + master_dcb = router_cli_ses->rses_master_ref->bref_dcb; + CHK_DCB(master_dcb); + switch(packet_type) { case COM_QUIT: /**< 1 QUIT will close all sessions */ case COM_INIT_DB: /**< 2 DDL must go to the master */ @@ -654,10 +919,11 @@ static int routeQuery( memset(&querystr[len], 0, 1); // querystr = (char *)GWBUF_DATA(plainsqlbuf); /* - querystr = master_dcb->func.getquerystr( - (void *) gwbuf_clone(querybuf), - &querystr_is_copy); + * querystr = master_dcb->func.getquerystr( + * (void *) gwbuf_clone(querybuf), + * &querystr_is_copy); */ + qtype = skygw_query_classifier_get_type(querystr, 0); break; @@ -672,13 +938,23 @@ static int routeQuery( default: break; } /**< switch by packet type */ - +#if 0 LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "String\t\"%s\"", querystr == NULL ? "(empty)" : querystr))); LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Packet type\t%s", STRPACKETTYPE(packet_type)))); +#endif +#if defined(AUTOCOMMIT_OPT) + if ((QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) && + !router_cli_ses->rses_autocommit_enabled) || + (QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) && + router_cli_ses->rses_autocommit_enabled)) + { + /** reply directly to client */ + } +#endif /** * If autocommit is disabled or transaction is explicitly started * transaction becomes active and master gets all statements until @@ -742,12 +1018,36 @@ static int routeQuery( LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, - "Read-only query, routing to Slave."))); + "[%s.%d]\tRead-only query, routing to Slave.", + inst->service->name, + router_cli_ses->rses_id))); ss_dassert(QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)); - ret = slave_dcb->func.write(slave_dcb, querybuf); - atomic_add(&inst->stats.n_slave, 1); + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + goto return_ret; + } + succp = get_dcb(&slave_dcb, router_cli_ses, BE_SLAVE); + + if (succp) + { + if ((ret = slave_dcb->func.write(slave_dcb, querybuf)) == 1) + { + atomic_add(&inst->stats.n_slave, 1); + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing query \"%s\" failed.", + querystr))); + } + } + rses_end_locked_router_action(router_cli_ses); + + ss_dassert(succp); goto return_ret; } else @@ -770,12 +1070,35 @@ static int routeQuery( "routing to Master."))); } } - ret = master_dcb->func.write(master_dcb, querybuf); - atomic_add(&inst->stats.n_master, 1); + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + goto return_ret; + } - goto return_ret; + if (master_dcb == NULL) + { + succp = get_dcb(&master_dcb, router_cli_ses, BE_MASTER); + } + if (succp) + { + + if ((ret = master_dcb->func.write(master_dcb, querybuf)) == 1) + { + atomic_add(&inst->stats.n_master, 1); + } + } + rses_end_locked_router_action(router_cli_ses); + + ss_dassert(succp); + + if (ret == 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing to master failed."))); + } } - return_ret: if (plainsqlbuf != NULL) { @@ -912,12 +1235,11 @@ static void clientReply( GWBUF* writebuf, DCB* backend_dcb) { - DCB* master_dcb; - DCB* slave_dcb; DCB* client_dcb; ROUTER_CLIENT_SES* router_cli_ses; sescmd_cursor_t* scur = NULL; - backend_type_t be_type = BE_UNDEFINED; + backend_ref_t* backend_ref; + int i; router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); @@ -933,9 +1255,6 @@ static void clientReply( GWBUF_LENGTH(writebuf))) != NULL); goto lock_failed; } - master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; - slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; - /** Holding lock ensures that router session remains open */ ss_dassert(backend_dcb->session != NULL); client_dcb = backend_dcb->session->client; @@ -957,29 +1276,31 @@ static void clientReply( writebuf, GWBUF_LENGTH(writebuf))) != NULL); /** Log that client was closed before reply */ - return; + goto lock_failed; } - - if (backend_dcb == master_dcb) - { - be_type = BE_MASTER; - } - else if (backend_dcb == slave_dcb) - { - be_type = BE_SLAVE; - } - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "reply_by_statement", - backend_dcb, - gwbuf_clone(writebuf))); /** Lock router session */ if (!rses_begin_locked_router_action(router_cli_ses)) { /** Log to debug that router was closed */ goto lock_failed; } + backend_ref = router_cli_ses->rses_backend_ref; - scur = rses_get_sescmd_cursor(router_cli_ses, be_type); + /** find backend_dcb's corresponding BACKEND */ + i = 0; + while (irses_nbackends && + backend_ref[i].bref_dcb != backend_dcb) + { + i++; + } + ss_dassert(backend_ref[i].bref_dcb == backend_dcb); + + LOGIF(LT, tracelog_routed_query(router_cli_ses, + "reply_by_statement", + &backend_ref[i], + gwbuf_clone(writebuf))); + + scur = &backend_ref[i].bref_sescmd_cur; /** * Active cursor means that reply is from session command * execution. Majority of the time there are no session commands @@ -1012,181 +1333,418 @@ lock_failed: return; } + +int bref_cmp_router_conn( + const void* bref1, + const void* bref2) +{ + BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; + BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + + return ((b1->backend_conn_count < b2->backend_conn_count) ? -1 : + ((b1->backend_conn_count > b2->backend_conn_count) ? 1 : 0)); +} + +int bref_cmp_global_conn( + const void* bref1, + const void* bref2) +{ + BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; + BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + + return ((b1->backend_server->stats.n_current < b2->backend_server->stats.n_current) ? -1 : + ((b1->backend_server->stats.n_current > b2->backend_server->stats.n_current) ? 1 : 0)); +} + + +int bref_cmp_behind_master( + const void* bref1, + const void* bref2) +{ + return 1; +} + /** - * @node Search suitable backend server from those of router instance. + * @node Search suitable backend servers from those of router instance. * * Parameters: - * @param p_master - in, use, out - * Pointer to location where master's address is to be stored. - * If NULL, then master is not searched. + * @param p_master_ref - in, use, out + * Pointer to location where master's backend reference is to be stored. + * NULL is not allowed. * - * @param p_slave - in, use, out - * Pointer to location where slave's address is to be stored. - * if NULL, then slave is not searched. + * @param backend_ref - in, use, out + * Pointer to backend server reference object array. + * NULL is not allowed. * - * @param inst - in, use - * Pointer to router instance + * @param router_nservers - in, use + * Number of backend server pointers pointed to by b. * - * @return true, if all what what requested found, false if the request - * was not satisfied or was partially satisfied. + * @param max_nslaves - in, use + * Upper limit for the number of slaves. Configuration parameter or default. + * + * @param session - in, use + * MaxScale session pointer used when connection to backend is established. + * + * @param router - in, use + * Pointer to router instance. Used when server states are qualified. + * + * @return true, if at least one master and one slave was found. * * * @details It is assumed that there is only one master among servers of - * a router instance. As a result, thr first master is always chosen. + * a router instance. As a result, the first master found is chosen. */ -static bool search_backend_servers( - BACKEND** p_master, - BACKEND** p_slave, +static bool select_connect_backend_servers( + backend_ref_t** p_master_ref, + backend_ref_t* backend_ref, + int router_nservers, + int max_nslaves, + select_criteria_t select_criteria, + SESSION* session, ROUTER_INSTANCE* router) { - BACKEND* local_backend[BE_COUNT] = {NULL,NULL}; + bool succp = true; + bool master_found = false; + bool master_connected = false; + int slaves_found = 0; + int slaves_connected = 0; int i; - bool succp = true; - - /* - * Loop over all the servers and find any that have fewer connections - * than current candidate server. - * - * If a server has less connections than the current candidate it is - * chosen to a new candidate. - * - * If a server has the same number of connections currently as the - * candidate and has had less connections over time than the candidate - * it will also become the new candidate. This has the effect of - * spreading the connections over different servers during periods of - * very low load. - * - * If master is searched for, the first master found is chosen. + const int min_nslaves = 0; /*< not configurable at the time */ + bool is_synced_master; + int (*p)(const void *, const void *); + + if (p_master_ref == NULL || backend_ref == NULL) + { + ss_dassert(FALSE); + succp = false; + goto return_succp; + } + /** Check slave selection criteria and set compare function */ + p = criteria_cmpfun[select_criteria]; + + if (p == NULL) + { + succp = false; + goto return_succp; + } + + if (router->bitvalue != 0) /*< 'synced' is the only bitvalue in rwsplit */ + { + is_synced_master = true; + } + else + { + is_synced_master = false; + } + +#if 0 + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Servers and conns before ordering:"))); + for (i=0; ibackend_server->name, + b->backend_server->port, + b->backend_conn_count))); + } +#endif + /** + * Sort the pointer list to servers according to connection counts. As + * a consequence those backends having least connections are in the + * beginning of the list. */ - for (i = 0; router->servers[i] != NULL; i++) { - BACKEND* be = router->servers[i]; + qsort((void *)backend_ref, (size_t)router_nservers, sizeof(backend_ref_t), p); + + if (LOG_IS_ENABLED(LOGFILE_TRACE)) + { + if (select_criteria == LEAST_GLOBAL_CONNECTIONS || + select_criteria == LEAST_ROUTER_CONNECTIONS) + { + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, + "Servers and %s connection counts:", + select_criteria == LEAST_GLOBAL_CONNECTIONS ? + "all MaxScale" : "router"))); + + for (i=0; ibackend_server->name, + b->backend_server->port, + b->backend_server->stats.n_current))); + break; + + case LEAST_ROUTER_CONNECTIONS: + LOGIF(LT, (skygw_log_write_flush(LOGFILE_TRACE, + "%s %d:%d", + b->backend_server->name, + b->backend_server->port, + b->backend_conn_count))); + break; + + default: + break; + } + } + } + } + /** + * Choose at least 1+1 (master and slave) and at most 1+max_nslaves + * servers from the sorted list. First master found is selected. + */ + for (i=0; + ibitvalue is %d", pthread_self(), - be->backend_server->name, - be->backend_server->port, - be->backend_conn_count, - be->backend_server->status, + b->backend_server->name, + b->backend_server->port, + b->backend_conn_count, + b->backend_server->status, router->bitmask))); - } - if (be != NULL && - SERVER_IS_RUNNING(be->backend_server) && - (be->backend_server->status & router->bitmask) == - router->bitvalue) + if (SERVER_IS_RUNNING(b->backend_server) && + ((b->backend_server->status & router->bitmask) == + router->bitvalue)) + { + if (slaves_found < max_nslaves && + SERVER_IS_SLAVE(b->backend_server)) { - if (SERVER_IS_SLAVE(be->backend_server) && - p_slave != NULL) + slaves_found += 1; + backend_ref[i].bref_dcb = dcb_connect( + b->backend_server, + session, + b->backend_server->protocol); + + if (backend_ref[i].bref_dcb != NULL) { - /** - * If no candidate set, set first running - * server as an initial candidate server. - */ - if (local_backend[BE_SLAVE] == NULL) - { - local_backend[BE_SLAVE] = be; - } - else if (be->backend_conn_count < - local_backend[BE_SLAVE]->backend_conn_count) - { - /** - * This running server has fewer - * connections, set it as a new - * candidate. + slaves_connected += 1; + /** + * Increase backend connection counter. + * Server's stats are _increased_ in + * dcb.c:dcb_alloc ! + * But decreased in the calling function + * of dcb_close. */ - local_backend[BE_SLAVE] = be; + atomic_add(&b->backend_conn_count, 1); } - else if (be->backend_conn_count == - local_backend[BE_SLAVE]->backend_conn_count && - be->backend_server->stats.n_connections < - local_backend[BE_SLAVE]->backend_server->stats.n_connections) + else { - /** - * This running server has the same - * number of connections currently - * as the candidate but has had - * fewer connections over time - * than candidate, set this server - * to candidate. - */ - local_backend[BE_SLAVE] = be; - } - } - else if (p_master != NULL && - local_backend[BE_MASTER] == NULL && - SERVER_IS_MASTER(be->backend_server)) - { - local_backend[BE_MASTER] = be; - } - else if (p_master != NULL && - local_backend[BE_JOINED] == NULL && - SERVER_IS_JOINED(be->backend_server)) - { - local_backend[BE_JOINED] = be; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to establish " + "connection with slave %s:%d", + b->backend_server->name, + b->backend_server->port))); + /* handle connect error */ } + } + else if (!master_connected && + (SERVER_IS_MASTER(b->backend_server))) + { + master_found = true; + + backend_ref[i].bref_dcb = dcb_connect( + b->backend_server, + session, + b->backend_server->protocol); + + if (backend_ref[i].bref_dcb != NULL) + { + master_connected = true; + *p_master_ref = &backend_ref[i]; + /** Increase backend connection counter */ + /** Increase backend connection counter */ + atomic_add(&b->backend_server->stats.n_current, 1); + atomic_add(&b->backend_server->stats.n_connections, 1); + atomic_add(&b->backend_conn_count, 1); + } + else + { + succp = false; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to establish " + "connection with master %s:%d", + b->backend_server->name, + b->backend_server->port))); + /* handle connect error */ + } + } } - } + } /*< for */ - if (router->bitvalue != 0 && - p_master != NULL && - local_backend[BE_JOINED] == NULL) + /** + * Successful cases + */ + if (master_connected && + slaves_connected >= min_nslaves && + slaves_connected <= max_nslaves) + { + succp = true; + + if (slaves_connected == 0 && slaves_found > 0) { - succp = false; - LOGIF(LE, (skygw_log_write_flush( + LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, - "Error : Couldn't find a Joined Galera node from %d " - "candidates.", - i))); - goto return_succp; - } + "Warning : Couldn't connect to any of the %d " + "slaves. Routing to %s only.", + slaves_found, + (is_synced_master ? "Galera nodes" : "Master")))); - if (p_slave != NULL && local_backend[BE_SLAVE] == NULL) { - succp = false; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Couldn't find suitable Slave from %d " - "candidates.", - i))); - } - - if (p_master != NULL && local_backend[BE_MASTER] == NULL) { - succp = false; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Couldn't find suitable Master from %d " - "candidates.", - i))); + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : Couldn't connect to any of the %d " + "slaves. Routing to %s only.", + slaves_found, + (is_synced_master ? "Galera nodes" : "Master")))); } - - if (local_backend[BE_SLAVE] != NULL) { - *p_slave = local_backend[BE_SLAVE]; + else if (slaves_found == 0) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Warning : Couldn't find any slaves from existing " + "%d servers. Routing to %s only.", + router_nservers, + (is_synced_master ? "Galera nodes" : "Master")))); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : Couldn't find any slaves from existing " + "%d servers. Routing to %s only.", + router_nservers, + (is_synced_master ? "Galera nodes" : "Master")))); + } + else if (slaves_connected < max_nslaves) + { + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "Note : Couldn't connect to maximum number of " + "slaves. Connected successfully to %d slaves " + "of %d of them.", + slaves_connected, + slaves_found))); + } + + if (LOG_IS_ENABLED(LT)) + { + for (i=0; ibackend_server->name, + b->backend_server->port))); + } + } /* for */ + } + } + /** + * Failure cases + */ + else + { + if (!master_found) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Error : Couldn't find suitable %s from %d " + "candidates.", + (is_synced_master ? "Galera node" : "Master"), + router_nservers))); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Error : Couldn't find suitable %s from %d " + "candidates.", + (is_synced_master ? "Galera node" : "Master"), + router_nservers))); + LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, - "%lu [readwritesplit:search_backend_servers] Selected " - "Slave %s:%d from %d candidates.", - pthread_self(), - local_backend[BE_SLAVE]->backend_server->name, - local_backend[BE_SLAVE]->backend_server->port, - i))); + "Error : Couldn't find suitable %s from %d " + "candidates.", + (is_synced_master ? "Galera node" : "Master"), + router_nservers))); } - if (local_backend[BE_MASTER] != NULL) { - *p_master = local_backend[BE_MASTER]; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "%lu [readwritesplit:search_backend_servers] Selected " - "Master %s:%d " - "from %d candidates.", - pthread_self(), - local_backend[BE_MASTER]->backend_server->name, - local_backend[BE_MASTER]->backend_server->port, - i))); + else if (!master_connected) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Error : Couldn't connect to any %s although " + "there exists at least one %s node in the " + "cluster.", + (is_synced_master ? "Galera node" : "Master"), + (is_synced_master ? "Galera node" : "Master")))); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Error : Couldn't connect to any %s although " + "there exists at least one %s node in the " + "cluster.", + (is_synced_master ? "Galera node" : "Master"), + (is_synced_master ? "Galera node" : "Master")))); + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Couldn't connect to any %s although " + "there exists at least one %s node in the " + "cluster.", + (is_synced_master ? "Galera node" : "Master"), + (is_synced_master ? "Galera node" : "Master")))); + } + + if (slaves_connected < min_nslaves) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Error : Couldn't establish required amount of " + "slave connections for router session."))); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "*Error : Couldn't establish required amount of " + "slave connections for router session."))); + } + + /** Clean up connections */ + for (i=0; ibackend_conn_count > 0); + /** disconnect opened connections */ + backend_ref[i].bref_dcb->func.close(backend_ref[i].bref_dcb); + atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); + } + } + master_connected = false; + slaves_connected = 0; } return_succp: + return succp; } @@ -1402,7 +1960,7 @@ static GWBUF* sescmd_cursor_process_replies( packet = (uint8_t *)GWBUF_DATA(replybuf); packetlen = packet[0]+packet[1]*256+packet[2]*256*256; replybuf = gwbuf_consume(replybuf, packetlen+headerlen); - +/* LOGIF(LT, (skygw_log_write_flush( LOGFILE_TRACE, "%lu [sescmd_cursor_process_replies] cmd %p " @@ -1412,11 +1970,13 @@ static GWBUF* sescmd_cursor_process_replies( scmd, packetlen+headerlen, STRBETYPE(scur->scmd_cur_be_type)))); + */ } else { /** Mark the rest session commands as replied */ scmd->my_sescmd_is_replied = true; + /* LOGIF(LT, (skygw_log_write_flush( LOGFILE_TRACE, "%lu [sescmd_cursor_process_replies] Marked " @@ -1425,6 +1985,7 @@ static GWBUF* sescmd_cursor_process_replies( pthread_self(), scmd, STRBETYPE(scur->scmd_cur_be_type)))); + */ } if (sescmd_cursor_next(scur)) @@ -1464,17 +2025,6 @@ static mysql_sescmd_t* sescmd_cursor_get_command( return scmd; } -/** router must be locked */ -static sescmd_cursor_t* rses_get_sescmd_cursor( - ROUTER_CLIENT_SES* rses, - backend_type_t be_type) -{ - CHK_CLIENT_RSES(rses); - ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); - - return &rses->rses_cursor[be_type]; -} - /** router must be locked */ static bool sescmd_cursor_is_active( sescmd_cursor_t* sescmd_cursor) @@ -1524,24 +2074,26 @@ static GWBUF* sescmd_cursor_clone_querybuf( * Router session must be locked. */ static bool execute_sescmd_in_backend( - ROUTER_CLIENT_SES* rses, - backend_type_t be_type) + backend_ref_t* backend_ref) { DCB* dcb; bool succp = true; int rc = 0; sescmd_cursor_t* scur; - dcb = rses->rses_dcb[be_type]; + if (backend_ref->bref_dcb == NULL) + { + goto return_succp; + } + dcb = backend_ref->bref_dcb; CHK_DCB(dcb); - CHK_CLIENT_RSES(rses); - ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); + CHK_BACKEND_REF(backend_ref); /** * Get cursor pointer and copy of command buffer to cursor. */ - scur = rses_get_sescmd_cursor(rses, be_type); + scur = &backend_ref->bref_sescmd_cur; /** Return if there are no pending ses commands */ if (sescmd_cursor_get_command(scur) == NULL) @@ -1555,9 +2107,10 @@ static bool execute_sescmd_in_backend( /** Cursor is left active when function returns. */ sescmd_cursor_set_active(scur, true); } - LOGIF(LT, tracelog_routed_query(rses, + + LOGIF(LT, tracelog_routed_query(scur->scmd_cur_rses, "execute_sescmd_in_backend", - dcb, + backend_ref, sescmd_cursor_clone_querybuf(scur))); switch (scur->scmd_cur_cmd->my_sescmd_packet_type) { @@ -1569,7 +2122,6 @@ static bool execute_sescmd_in_backend( sescmd_cursor_clone_querybuf(scur)); break; - case COM_QUIT: case COM_QUERY: case COM_INIT_DB: default: @@ -1674,7 +2226,7 @@ static rses_property_t* mysql_sescmd_get_property( static void tracelog_routed_query( ROUTER_CLIENT_SES* rses, char* funcname, - DCB* dcb, + backend_ref_t* bref, GWBUF* buf) { uint8_t* packet = GWBUF_DATA(buf); @@ -1683,20 +2235,18 @@ static void tracelog_routed_query( size_t buflen = GWBUF_LENGTH(buf); char* querystr; char* startpos = (char *)&packet[5]; + BACKEND* b; backend_type_t be_type; + DCB* dcb; + + CHK_BACKEND_REF(bref); + b = bref->bref_backend; + CHK_BACKEND(b); + dcb = bref->bref_dcb; + CHK_DCB(dcb); + + be_type = BACKEND_TYPE(b); - if (rses->rses_dcb[BE_MASTER] == dcb) - { - be_type = BE_MASTER; - } - else if (rses->rses_dcb[BE_SLAVE] == dcb) - { - be_type = BE_SLAVE; - } - else - { - be_type = BE_UNDEFINED; - } if (GWBUF_TYPE(buf) == GWBUF_TYPE_MYSQL) { len = packet[0]; @@ -1715,16 +2265,8 @@ static void tracelog_routed_query( funcname, buflen, querystr, - (be_type == BE_MASTER ? - rses->rses_backend[BE_MASTER]->backend_server->name : - (be_type == BE_SLAVE ? - rses->rses_backend[BE_SLAVE]->backend_server->name : - "Target DCB is neither of the backends. This is error")), - (be_type == BE_MASTER ? - rses->rses_backend[BE_MASTER]->backend_server->port : - (be_type == BE_SLAVE ? - rses->rses_backend[BE_SLAVE]->backend_server->port : - -1)), + b->backend_server->name, + b->backend_server->port, STRBETYPE(be_type), dcb))); free(querystr); @@ -1778,20 +2320,19 @@ static bool route_session_write( skygw_query_type_t qtype) { bool succp; - DCB* master_dcb; - DCB* slave_dcb; rses_property_t* prop; + backend_ref_t* backend_ref; + int i; - master_dcb = router_cli_ses->rses_dcb[BE_MASTER]; - slave_dcb = router_cli_ses->rses_dcb[BE_SLAVE]; - CHK_DCB(master_dcb); - CHK_DCB(slave_dcb); LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "Session write, query type\t%s, packet type %s, " "routing to all servers.", STRQTYPE(qtype), STRPACKETTYPE(packet_type)))); + + backend_ref = router_cli_ses->rses_backend_ref; + /** * COM_QUIT is one-way message. Server doesn't respond to that. * Therefore reply processing is unnecessary and session @@ -1801,15 +2342,32 @@ static bool route_session_write( if (packet_type == COM_QUIT) { int rc; - int rc2; - rc = master_dcb->func.write(master_dcb, gwbuf_clone(querybuf)); - rc2 = slave_dcb->func.write(slave_dcb, querybuf); - - if (rc == 1 && rc == rc2) - { - succp = true; + succp = true; + + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + succp = false; + goto return_succp; } + + for (i=0; irses_nbackends; i++) + { + DCB* dcb = backend_ref[i].bref_dcb; + + if (dcb != NULL) + { + rc = dcb->func.write(dcb, gwbuf_clone(querybuf)); + + if (rc != 1) + { + succp = false; + } + } + } + rses_end_locked_router_action(router_cli_ses); + gwbuf_free(querybuf); goto return_succp; } prop = rses_property_init(RSES_PROP_TYPE_SESCMD); @@ -1830,8 +2388,9 @@ static bool route_session_write( /** Add sescmd property to router client session */ rses_property_add(router_cli_ses, prop); - /** Execute session command in master */ - succp = execute_sescmd_in_backend(router_cli_ses, BE_MASTER); + for (i=0; irses_nbackends; i++) + { + succp = execute_sescmd_in_backend(&backend_ref[i]); if (!succp) { @@ -1839,13 +2398,6 @@ static bool route_session_write( rses_end_locked_router_action(router_cli_ses); goto return_succp; } - /** Execute session command in slave */ - succp = execute_sescmd_in_backend(router_cli_ses, BE_SLAVE); - if (!succp) - { - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - goto return_succp; } /** Unlock router session */ rses_end_locked_router_action(router_cli_ses); @@ -1856,3 +2408,52 @@ return_succp: return succp; } +static void rwsplit_process_options( + ROUTER_INSTANCE* router, + char** options) +{ + int i; + char* value; + select_criteria_t c; + + for (i = 0; options[i]; i++) + { + if ((value = strchr(options[i], '=')) == NULL) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, "Warning : Unsupported " + "router option \"%s\" for " + "readwritesplit router.", + options[i]))); + } + else + { + *value = 0; + value++; + if (strcmp(options[i], "slave_selection_criteria") == 0) + { + c = GET_SELECT_CRITERIA(value); + ss_dassert( + c == LEAST_GLOBAL_CONNECTIONS || + c == LEAST_ROUTER_CONNECTIONS || + c == LEAST_BEHIND_MASTER || + c == UNDEFINED_CRITERIA); + + if (c == UNDEFINED_CRITERIA) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, "Warning : Unknown " + "slave selection criteria \"%s\". " + "Allowed values are \"LEAST_GLOBAL_CONNECTIONS\", " + "LEAST_ROUTER_CONNECTIONS, " + "and \"LEAST_ROUTER_CONNECTIONS\".", + STRCRITERIA(router->rwsplit_config.rw_slave_select_criteria)))); + } + else + { + router->rwsplit_config.rw_slave_select_criteria = c; + } + } + } + } /*< for */ +} diff --git a/server/modules/routing/readwritesplit/test/rwsplit.sh b/server/modules/routing/readwritesplit/test/rwsplit.sh index d64fcf495..6c5268d46 100755 --- a/server/modules/routing/readwritesplit/test/rwsplit.sh +++ b/server/modules/routing/readwritesplit/test/rwsplit.sh @@ -42,7 +42,7 @@ fi TINPUT=test_transaction_routing3.sql TRETVAL=2 a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TMASTER_ID" ]; then +if [ "$a" = "$TMASTER_ID" ]; then echo "$TINPUT FAILED, return value $a when one of the slave IDs was expected">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -51,7 +51,7 @@ fi TINPUT=test_transaction_routing3b.sql TRETVAL=2 a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TMASTER_ID" ]; then +if [ "$a" = "$TMASTER_ID" ]; then echo "$TINPUT FAILED, return value $a when one of the slave IDs was expected">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -91,7 +91,7 @@ TINPUT=test_implicit_commit1.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -100,7 +100,7 @@ fi TINPUT=test_implicit_commit2.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -109,7 +109,7 @@ fi TINPUT=test_implicit_commit3.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -127,7 +127,7 @@ fi TINPUT=test_implicit_commit5.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -136,7 +136,7 @@ fi TINPUT=test_implicit_commit6.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -145,7 +145,7 @@ fi TINPUT=test_implicit_commit7.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; @@ -185,7 +185,7 @@ TINPUT=set_autocommit_disabled.sql TINPUT=test_after_autocommit_disabled.sql TRETVAL=$TMASTER_ID a=`$RUNCMD < ./$TINPUT` -if [ "$a" == "$TRETVAL" ]; then +if [ "$a" = "$TRETVAL" ]; then echo "$TINPUT FAILED, return value $a when it was not accetable">>$TLOG; else echo "$TINPUT PASSED">>$TLOG ; diff --git a/server/modules/routing/readwritesplit/test/test_autocommit_disabled3.sql b/server/modules/routing/readwritesplit/test/test_autocommit_disabled3.sql new file mode 100644 index 000000000..04be4024e --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_autocommit_disabled3.sql @@ -0,0 +1,9 @@ +use test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; -- open transaction +begin; +insert into t1 values(1); -- write to master +commit; +select count(*) from t1; -- read from master since autocommit is disabled +drop table t1; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql index 2b10bdcbb..85a4cabf2 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit1.sql @@ -3,6 +3,6 @@ SET autocommit=1; BEGIN; CREATE DATABASE FOO; -- implicit commit SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP DATABASE If EXISTS FOO; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql index fbca8b34e..42b0a8b56 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit2.sql @@ -9,7 +9,7 @@ ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR DO UPDATE t1 SET id = id + 1; SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP TABLE IF EXISTS T1; DROP EVENT IF EXISTS myevent; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql index c8c433c21..32d1d3311 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit3.sql @@ -4,6 +4,6 @@ SET autocommit=1; BEGIN; CREATE TABLE T1 (id integer); -- implicit commit SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP TABLE IF EXISTS T1; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit4.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit4.sql index f7fe048be..414f59bfd 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit4.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit4.sql @@ -4,6 +4,6 @@ SET autocommit=0; BEGIN; CREATE TEMPORARY TABLE T1 (id integer); -- NO implicit commit SELECT (@@server_id) INTO @a; -SELECT @a; --should read from master +SELECT @a; -- should read from master DROP TABLE IF EXISTS T1; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql index 50b175b37..9358fe86a 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit5.sql @@ -9,6 +9,6 @@ BEGIN END // DELIMITER ; SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP PROCEDURE IF EXISTS simpleproc; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql index 18f1c39ef..e5b3cde17 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit6.sql @@ -6,6 +6,6 @@ CREATE FUNCTION hello (s CHAR(20)) RETURNS CHAR(50) DETERMINISTIC RETURN CONCAT('Hello, ',s,'!'); -- implicit COMMIT SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP FUNCTION IF EXISTS hello; COMMIT; diff --git a/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql b/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql index f45b8c516..6e80d329d 100644 --- a/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql +++ b/server/modules/routing/readwritesplit/test/test_implicit_commit7.sql @@ -5,6 +5,6 @@ SET autocommit=1; BEGIN; CREATE INDEX foo_t1 on T1 (id); -- implicit commit SELECT (@@server_id) INTO @a; -SELECT @a; --should read from slave +SELECT @a; -- should read from slave DROP TABLE IF EXISTS T1; COMMIT; diff --git a/server/modules/routing/test/makefile b/server/modules/routing/test/makefile new file mode 100644 index 000000000..039f2910e --- /dev/null +++ b/server/modules/routing/test/makefile @@ -0,0 +1,41 @@ +# cleantests - clean local and subdirectories' tests +# buildtests - build all local and subdirectories' tests +# runtests - run all local tests +# testall - clean, build and run local and subdirectories' tests + +include ../../../../build_gateway.inc +include $(ROOT_PATH)/makefile.inc +include $(ROOT_PATH)/test.inc + +CC=cc +TESTLOG := $(shell pwd)/testrouting.log +RET := -1 + +cleantests: + - $(DEL) *.o + - $(DEL) *~ + + +testall: + -$(MAKE) cleantests + -$(MAKE) DEBUG=Y buildtests + -$(MAKE) runtests + @echo "" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Test Read/Write Split Router" >> $(TESTLOG) + $(MAKE) -C $(ROOT_PATH)/server/modules/routing/readwritesplit testall + + +buildtests: + $(MAKE) -C $(ROOT_PATH)/server/modules/routing/readwritesplit buildtests + + +runtests: + @echo "" > $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Test routing" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo "Nothing to run here so far" >> $(TESTLOG) + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/modules/routing/testroute.c b/server/modules/routing/testroute.c index c0bd73fee..ce2ce2ca9 100644 --- a/server/modules/routing/testroute.c +++ b/server/modules/routing/testroute.c @@ -17,9 +17,17 @@ */ #include #include +#include static char *version_str = "V1.0.0"; +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_IN_DEVELOPMENT, + ROUTER_VERSION, + "A test router - not for use in real systems" +}; + static ROUTER *createInstance(SERVICE *service, char **options); static void *newSession(ROUTER *instance, SESSION *session); static void closeSession(ROUTER *instance, void *session); @@ -145,4 +153,4 @@ static uint8_t getCapabilities( void* router_session) { return 0; -} \ No newline at end of file +} diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 8c11d9782..3910429a2 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -119,7 +119,10 @@ typedef enum skygw_chk_t { CHK_NUM_SESSION, CHK_NUM_ROUTER_SES, CHK_NUM_MY_SESCMD, - CHK_NUM_ROUTER_PROPERTY + CHK_NUM_ROUTER_PROPERTY, + CHK_NUM_SESCMD_CUR, + CHK_NUM_BACKEND, + CHK_NUM_BACKEND_REF } skygw_chk_t; # define STRBOOL(b) ((b) ? "true" : "false") @@ -221,6 +224,17 @@ typedef enum skygw_chk_t { ((t) == BE_UNDEFINED ? "BE_UNDEFINED" : \ "Unknown backend tpe"))) +#define STRCRITERIA(c) ((c) == UNDEFINED_CRITERIA ? "UNDEFINED_CRITERIA" : \ + ((c) == LEAST_GLOBAL_CONNECTIONS ? "LEAST_GLOBAL_CONNECTIONS" : \ + ((c) == LEAST_ROUTER_CONNECTIONS ? "LEAST_ROUTER_CONNECTIONS" : \ + ((c) == LEAST_BEHIND_MASTER ? "LEAST_BEHIND_MASTER" : "Unknown criteria")))) + +#define STRSRVSTATUS(s) ((SERVER_IS_RUNNING(s) && SERVER_IS_MASTER(s)) ? "RUNNING MASTER" : \ + ((SERVER_IS_RUNNING(s) && SERVER_IS_SLAVE(s)) ? "RUNNING SLAVE" : \ + ((SERVER_IS_RUNNING(s) && SERVER_IS_JOINED(s)) ? "RUNNING JOINED" : \ + ((SERVER_IS_RUNNING(s) && SERVER_IN_MAINT(s)) ? "RUNNING MAINTENANCE" : \ + (SERVER_IS_RUNNING(s) ? "RUNNING (only)" : "NO STATUS"))))) + #define CHK_MLIST(l) { \ ss_info_dassert((l->mlist_chk_top == CHK_NUM_MLIST && \ l->mlist_chk_tail == CHK_NUM_MLIST), \ @@ -446,7 +460,25 @@ typedef enum skygw_chk_t { "Session command has invalid check fields"); \ } +#define CHK_SESCMD_CUR(c) { \ + ss_info_dassert((c)->scmd_cur_chk_top == CHK_NUM_SESCMD_CUR && \ + (c)->scmd_cur_chk_tail == CHK_NUM_SESCMD_CUR, \ + "Session command cursor has invalid check fields"); \ + } +#define CHK_BACKEND(b) { \ + ss_info_dassert((b)->be_chk_top == CHK_NUM_BACKEND && \ + (b)->be_chk_tail == CHK_NUM_BACKEND, \ + "BACKEND has invalid check fields"); \ +} + +#define CHK_BACKEND_REF(r) { \ + ss_info_dassert((r)->bref_chk_top == CHK_NUM_BACKEND_REF && \ + (r)->bref_chk_tail == CHK_NUM_BACKEND_REF, \ + "Backend reference has invalid check fields"); \ +} + + #if defined(SS_DEBUG) bool conn_open[10240]; #endif