diff --git a/Documentation/Debug And Diagnostic Support.pdf b/Documentation/Debug And Diagnostic Support.pdf deleted file mode 100644 index d7518bbee..000000000 Binary files a/Documentation/Debug And Diagnostic Support.pdf and /dev/null differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios-Z2.pdf b/Documentation/MaxScale Configuration And Usage Scenarios-Z2.pdf new file mode 100644 index 000000000..68b282079 Binary files /dev/null and b/Documentation/MaxScale Configuration And Usage Scenarios-Z2.pdf differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios.pdf b/Documentation/MaxScale Configuration And Usage Scenarios.pdf deleted file mode 100644 index 7e588e2c9..000000000 Binary files a/Documentation/MaxScale Configuration And Usage Scenarios.pdf and /dev/null differ diff --git a/Documentation/MaxScale Debug And Diagnostic Support-Z2.pdf b/Documentation/MaxScale Debug And Diagnostic Support-Z2.pdf new file mode 100644 index 000000000..1b1b3f6c0 Binary files /dev/null and b/Documentation/MaxScale Debug And Diagnostic Support-Z2.pdf differ diff --git a/Documentation/MaxScale MySQL Cluster setup-Z2.pdf b/Documentation/MaxScale MySQL Cluster setup-Z2.pdf new file mode 100644 index 000000000..5d4490fd6 Binary files /dev/null and b/Documentation/MaxScale MySQL Cluster setup-Z2.pdf differ diff --git a/Documentation/internal/hint_syntax.pdf b/Documentation/internal/hint_syntax.pdf new file mode 100644 index 000000000..f90b80acc Binary files /dev/null and b/Documentation/internal/hint_syntax.pdf differ diff --git a/gcov.diff b/gcov.diff deleted file mode 100644 index c310d1b08..000000000 --- a/gcov.diff +++ /dev/null @@ -1,138 +0,0 @@ -diff --git a/makefile.inc b/makefile.inc -index f2d93bf..c7dbffa 100644 ---- a/makefile.inc -+++ b/makefile.inc -@@ -24,8 +24,8 @@ endif - - # -O2 -g -pipe -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector --param=ssp-buffer-size=4 -fPIC - --CFLAGS := $(CFLAGS) -Wall --LDLIBS := $(LDLIBS) -pthread -+CFLAGS := $(CFLAGS) -Wall -fprofile-arcs -ftest-coverage -+LDLIBS := $(LDLIBS) -pthread -lgcov - LDMYSQL := -lmysqld - CPP_LDLIBS := -lstdc++ - -diff --git a/server/core/Makefile b/server/core/Makefile -index 9bf650c..9df75a7 100644 ---- a/server/core/Makefile -+++ b/server/core/Makefile -@@ -75,7 +75,7 @@ POBJS=maxpasswd.o secrets.o utils.o - LIBS=-L$(EMBEDDED_LIB) \ - -lmysqld \ - -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ -- -L../inih/extra -linih -lssl -lstdc++ -+ -L../inih/extra -linih -lssl -lstdc++ -lgcov - - all: maxscale maxkeys maxpasswd - -diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile -index 931c35a..d5dcca9 100644 ---- a/server/modules/filter/Makefile -+++ b/server/modules/filter/Makefile -@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -44,7 +44,7 @@ TEESRCS=tee.c - TEEOBJ=$(TEESRCS:.c=.o) - SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS) - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov - MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so - - -diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile -index 7fdbc58..bca01de 100644 ---- a/server/modules/monitor/Makefile -+++ b/server/modules/monitor/Makefile -@@ -28,7 +28,7 @@ CFLAGS=-c -fPIC -I. -I/usr/include -I../include -I../../include -I$(LOGPATH) \ - - LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \ - -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \ -- -Wl,-rpath,$(EMBEDDED_LIB) -+ -Wl,-rpath,$(EMBEDDED_LIB) -fprofile-arcs -ftest-coverage - - - -@@ -39,7 +39,7 @@ GALERAOBJ=$(GALERASRCS:.c=.o) - SRCS=$(MYSQLSRCS) - OBJ=$(SRCS:.c=.o) - LIBS=$(UTILSPATH)/skygw_utils.o -llog_manager \ -- -L$(EMBEDDED_LIB) -lmysqld -+ -L$(EMBEDDED_LIB) -lmysqld -lgcov - MODULES=libmysqlmon.so libgaleramon.so - - -diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile -index 54a8f8c..c8913ab 100644 ---- a/server/modules/protocol/Makefile -+++ b/server/modules/protocol/Makefile -@@ -31,7 +31,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -51,7 +51,7 @@ MAXSCALEDOBJ=$(MAXSCALEDSRCS:.c=.o) - SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) \ - $(MAXSCALEDSRCS) - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -+LIBS=$(UTILSPATH)/skygw_utils.o -lgcov - MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so \ - libmaxscaled.so - -diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile -index 4feac68..afd1da7 100644 ---- a/server/modules/routing/Makefile -+++ b/server/modules/routing/Makefile -@@ -29,7 +29,7 @@ UTILSPATH := $(ROOT_PATH)/utils - - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ -- -I$(UTILSPATH) -Wall -g -+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../makefile.inc - -@@ -46,7 +46,7 @@ CLISRCS=cli.c debugcmd.c - CLIOBJ=$(CLISRCS:.c=.o) - SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c - OBJ=$(SRCS:.c=.o) --LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov - MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so - - -diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile -index c60f2ff..a3a643e 100644 ---- a/server/modules/routing/readwritesplit/Makefile -+++ b/server/modules/routing/readwritesplit/Makefile -@@ -27,7 +27,7 @@ QCLASSPATH := $(ROOT_PATH)/query_classifier - CC=cc - CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include \ - -I$(LOGPATH) -I$(UTILSPATH) -I$(QCLASSPATH) \ -- $(MYSQL_HEADERS) -Wall -g -+ $(MYSQL_HEADERS) -Wall -g -fprofile-arcs -ftest-coverage - - include ../../../../makefile.inc - -@@ -38,7 +38,7 @@ LDFLAGS=-shared -L$(LOGPATH) -L$(QCLASSPATH) -L$(EMBEDDED_LIB) \ - - SRCS=readwritesplit.c - OBJ=$(SRCS:.c=.o) --LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld -+LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld -lgcov - MODULES=libreadwritesplit.so - - all: $(MODULES) diff --git a/server/core/Makefile b/server/core/Makefile index 9a807a925..21ce90ee5 100644 --- a/server/core/Makefile +++ b/server/core/Makefile @@ -32,8 +32,9 @@ # are behind SS_DEBUG macros. # 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 +# 24/07/13 Mark Riddoch Addition of encryption routines +# 30/05/14 Mark Riddoch Filter API added +# 25/07/14 Mark Riddoch Addition of hints include ../../build_gateway.inc @@ -57,7 +58,7 @@ 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 filter.c modutil.c + monitor.c adminusers.c secrets.c filter.c modutil.c hint.c HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.h \ ../include/gw.h ../modules/include/mysql_client_server_protocol.h \ @@ -65,7 +66,7 @@ HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.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/filter.h modutil.h + ../include/filter.h modutil.h hint.h OBJ=$(SRCS:.c=.o) diff --git a/server/core/buffer.c b/server/core/buffer.c index 9fc66c06e..1e827f29f 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -32,6 +32,7 @@ * 11/07/13 Mark Riddoch Add reference count mechanism * 16/07/2013 Massimiliano Pinto Added command type to gwbuf struct * 24/06/2014 Mark Riddoch Addition of gwbuf_trim + * 15/07/2014 Mark Riddoch Addition of properties * * @endverbatim */ @@ -88,6 +89,8 @@ SHARED_BUF *sbuf; sbuf->refcount = 1; rval->sbuf = sbuf; rval->next = NULL; + rval->hint = NULL; + rval->properties = NULL; rval->gwbuf_type = GWBUF_TYPE_UNDEFINED; rval->gwbuf_info = GWBUF_INFO_NONE; rval->gwbuf_bufobj = NULL; @@ -103,6 +106,8 @@ SHARED_BUF *sbuf; void gwbuf_free(GWBUF *buf) { +BUF_PROPERTY *prop; + buffer_object_t* bo; CHK_GWBUF(buf); @@ -110,12 +115,28 @@ gwbuf_free(GWBUF *buf) { free(buf->sbuf->data); free(buf->sbuf); - bo = buf->gwbuf_bufobj; - + bo = buf->gwbuf_bufobj; + while (bo != NULL) { bo = gwbuf_remove_buffer_object(buf, bo); } + + } + while (buf->properties) + { + prop = buf->properties; + buf->properties = prop->next; + free(prop->name); + free(prop->value); + free(prop); + } + /** Release the hint */ + while (buf->hint) + { + HINT* h = buf->hint; + buf->hint = buf->hint->next; + hint_free(h); } free(buf); } @@ -145,6 +166,8 @@ GWBUF *rval; rval->start = buf->start; rval->end = buf->end; rval->gwbuf_type = buf->gwbuf_type; + rval->properties = NULL; + rval->hint = NULL; rval->gwbuf_info = buf->gwbuf_info; rval->gwbuf_bufobj = buf->gwbuf_bufobj; rval->next = NULL; @@ -173,6 +196,8 @@ GWBUF *gwbuf_clone_portion( clonebuf->start = (void *)((char*)buf->start)+start_offset; clonebuf->end = (void *)((char *)clonebuf->start)+length; clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone the type for now */ + clonebuf->properties = NULL; + clonebuf->hint = NULL; clonebuf->gwbuf_info = buf->gwbuf_info; clonebuf->gwbuf_bufobj = buf->gwbuf_bufobj; clonebuf->next = NULL; @@ -424,20 +449,125 @@ void* gwbuf_get_buffer_object_data( return bo->bo_data; } - /** * @return pointer to next buffer object or NULL */ static buffer_object_t* gwbuf_remove_buffer_object( - GWBUF* buf, - buffer_object_t* bufobj) + GWBUF* buf, + buffer_object_t* bufobj) { - buffer_object_t* next; - - next = bufobj->bo_next; - /** Call corresponding clean-up function to clean buffer object's data */ - bufobj->bo_donefun_fp(bufobj->bo_data); - free(bufobj); - return next; + buffer_object_t* next; + + next = bufobj->bo_next; + /** Call corresponding clean-up function to clean buffer object's data */ + bufobj->bo_donefun_fp(bufobj->bo_data); + free(bufobj); + return next; +} + + + +/** + * Add a property to a buffer. + * + * @param buf The buffer to add the property to + * @param name The property name + * @param value The property value + * @return Non-zero on success + */ +int +gwbuf_add_property(GWBUF *buf, char *name, char *value) +{ +BUF_PROPERTY *prop; + + if ((prop = malloc(sizeof(BUF_PROPERTY))) == NULL) + return 0; + + prop->name = strdup(name); + prop->value = strdup(value); + spinlock_acquire(&buf->gwbuf_lock); + prop->next = buf->properties; + buf->properties = prop; + spinlock_release(&buf->gwbuf_lock); + return 1; } +/** + * Return the value of a buffer property + * @param buf The buffer itself + * @param name The name of the property to return + * @return The property value or NULL if the property was not found. + */ +char * +gwbuf_get_property(GWBUF *buf, char *name) +{ +BUF_PROPERTY *prop; + + spinlock_acquire(&buf->gwbuf_lock); + prop = buf->properties; + while (prop && strcmp(prop->name, name) != 0) + prop = prop->next; + spinlock_release(&buf->gwbuf_lock); + if (prop) + return prop->value; + return NULL; +} + + +/** + * Convert a chain of GWBUF structures into a single GWBUF structure + * + * @param orig The chain to convert + * @return The contiguous buffer + */ +GWBUF * +gwbuf_make_contiguous(GWBUF *orig) +{ +GWBUF *newbuf; +char *ptr; +int len; + + if (orig->next == NULL) + return orig; + + if ((newbuf = gwbuf_alloc(gwbuf_length(orig))) != NULL) + { + ptr = GWBUF_DATA(newbuf); + while (orig) + { + len = GWBUF_LENGTH(orig); + memcpy(ptr, GWBUF_DATA(orig), len); + ptr += len; + orig = gwbuf_consume(orig, len); + } + } + return newbuf; +} + +/** + * Add hint to a buffer. + * + * @param buf The buffer to add the hint to + * @param hint The hint itself + * @return Non-zero on success + */ +int +gwbuf_add_hint(GWBUF *buf, HINT *hint) +{ +HINT *ptr; + + spinlock_acquire(&buf->gwbuf_lock); + if (buf->hint) + { + ptr = buf->hint; + while (ptr->next) + ptr = ptr->next; + ptr->next = hint; + } + else + { + buf->hint = hint; + } + spinlock_release(&buf->gwbuf_lock); + return 1; +} diff --git a/server/core/hint.c b/server/core/hint.c new file mode 100644 index 000000000..2a463585e --- /dev/null +++ b/server/core/hint.c @@ -0,0 +1,153 @@ +/* + * 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 + */ +#include +#include +#include + +/** + * @file hint.c generic support routines for hints. + * + * @verbatim + * Revision History + * + * Date Who Description + * 25/07/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ + + +/** + * Duplicate a list of hints + * + * @param hint The hint list to duplicate + * @return A duplicate of the list + */ +HINT * +hint_dup(HINT *hint) +{ +HINT *nlhead = NULL, *nltail = NULL, *ptr1, *ptr2; + + ptr1 = hint; + while (ptr1) + { + if ((ptr2 = (HINT *)malloc(sizeof(HINT))) == NULL) + return nlhead; + ptr2->type = ptr1->type; + if (ptr1->data) + ptr2->data = strdup(ptr1->data); + else + ptr2->data = NULL; + if (ptr1->value) + ptr2->value = strdup(ptr1->value); + else + ptr2->value = NULL; + ptr2->next = NULL; + if (nltail) + { + nltail->next = ptr2; + nltail = ptr2; + } + else + { + nlhead = ptr2; + nltail = ptr2; + } + ptr1 = ptr1->next; + } + return nlhead; +} + +/** + * Create a ROUTE TO type hint + * + * @param head The current hint list + * @param type The HINT_TYPE + * @param data Data may be NULL or the name of a server to route to + * @return The result hint list + */ +HINT * +hint_create_route(HINT *head, HINT_TYPE type, char *data) +{ +HINT *hint; + + if ((hint = (HINT *)malloc(sizeof(HINT))) == NULL) + return head; + hint->next = head; + hint->type = type; + if (data) + hint->data = strdup(data); + else + hint->data = NULL; + hint->value = NULL; + return hint; +} + +/** + * Create name/value parameter hint + * + * @param head The current hint list + * @param pname The parameter name + * @param value The parameter value + * @return The result hint list + */ +HINT * +hint_create_parameter(HINT *head, char *pname, char *value) +{ +HINT *hint; + + if ((hint = (HINT *)malloc(sizeof(HINT))) == NULL) + return head; + hint->next = head; + hint->type = HINT_PARAMETER; + hint->data = pname; + hint->value = strdup(value); + return hint; +} + +/** + * free_hint - free a hint + * + * @param hint The hint to free + */ +void +hint_free(HINT *hint) +{ + if (hint->data) + free(hint->data); + if (hint->value) + free(hint->value); + free(hint); +} + +bool hint_exists( + HINT** p_hint, + HINT_TYPE type) +{ + bool succp = false; + + while (*p_hint != NULL) + { + if ((*p_hint)->type == type) + { + succp = true; + } + p_hint = &(*p_hint)->next; + } + return succp; +} \ No newline at end of file diff --git a/server/core/modutil.c b/server/core/modutil.c index 781c55763..e2800b849 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -79,7 +79,7 @@ unsigned char *ptr; *length += (*ptr++ << 8); ptr += 2; // Skip sequence id and COM_QUERY byte *length = *length - 1; - *sql = (char *) ptr; + *sql = (char *)ptr; return 1; } diff --git a/server/core/server.c b/server/core/server.c index 5a75756ed..e0e2a41d3 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -416,6 +416,8 @@ char *status = NULL; strcat(status, "Slave, "); if (server->status & SERVER_JOINED) strcat(status, "Synced, "); + if (server->status & SERVER_NDB) + strcat(status, "NDB, "); if (server->status & SERVER_RUNNING) strcat(status, "Running"); else diff --git a/server/core/test/makefile b/server/core/test/makefile index 446027948..44c5d08a6 100644 --- a/server/core/test/makefile +++ b/server/core/test/makefile @@ -62,4 +62,4 @@ runtests: $(TESTS) @echo "-------------------------------" >> $(TESTLOG) $(foreach var,$(TESTS),./runtest.sh $(var) $(TESTLOG);) @echo "" >> $(TESTLOG) - @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file diff --git a/server/include/buffer.h b/server/include/buffer.h index 8f6dcf864..c248a770d 100644 --- a/server/include/buffer.h +++ b/server/include/buffer.h @@ -38,15 +38,30 @@ * 10/06/2013 Mark Riddoch Initial implementation * 11/07/2013 Mark Riddoch Addition of reference count in the gwbuf * 16/07/2013 Massimiliano Pinto Added command type for the queue + * 10/07/2014 Mark Riddoch Addition of hints + * 15/07/2014 Mark Riddoch Added buffer properties * * @endverbatim */ +#include #include +#include #include EXTERN_C_BLOCK_BEGIN +/** + * Buffer properties - used to store properties related to the buffer + * contents. This may be added at any point during the processing of the + * data, especially in the protocol stage of the processing. + */ +typedef struct buf_property { + char *name; + char *value; + struct buf_property *next; +} BUF_PROPERTY; + typedef enum { GWBUF_TYPE_UNDEFINED = 0x00, @@ -55,7 +70,8 @@ typedef enum GWBUF_TYPE_SINGLE_STMT = 0x04, GWBUF_TYPE_SESCMD_RESPONSE = 0x08, GWBUF_TYPE_RESPONSE_END = 0x10, - GWBUF_TYPE_SESCMD = 0x20 + GWBUF_TYPE_SESCMD = 0x20, + GWBUF_TYPE_HTTP = 0x40 } gwbuf_type_t; #define GWBUF_IS_TYPE_UNDEFINED(b) (b->gwbuf_type == 0) @@ -122,6 +138,8 @@ typedef struct gwbuf { buffer_object_t *gwbuf_bufobj; /*< List of objects referred to by GWBUF */ gwbuf_info_t gwbuf_info; /*< Info bits */ gwbuf_type_t gwbuf_type; /*< buffer's data type information */ + HINT *hint; /*< Hint data for this buffer */ + BUF_PROPERTY *properties; /*< Buffer properties */ } GWBUF; /*< @@ -155,6 +173,10 @@ extern unsigned int gwbuf_length(GWBUF *head); extern GWBUF *gwbuf_clone_portion(GWBUF *head, size_t offset, size_t len); extern GWBUF *gwbuf_clone_transform(GWBUF *head, gwbuf_type_t type); extern void gwbuf_set_type(GWBUF *head, gwbuf_type_t type); +extern int gwbuf_add_property(GWBUF *buf, char *name, char *value); +extern char *gwbuf_get_property(GWBUF *buf, char *name); +extern GWBUF *gwbuf_make_contiguous(GWBUF *); +extern int gwbuf_add_hint(GWBUF *, HINT *); void gwbuf_add_buffer_object(GWBUF* buf, bufobj_id_t id, diff --git a/server/include/config.h b/server/include/config.h index 59cb096e8..ca7d1ac5e 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -46,6 +46,8 @@ typedef enum { BOOL_TYPE = 0x08 } config_param_type_t; +enum {MAX_RLAG_NOT_AVAILABLE=-1, MAX_RLAG_UNDEFINED=-2}; + #define PARAM_IS_TYPE(p,t) ((p) & (t)) /** diff --git a/server/include/hint.h b/server/include/hint.h new file mode 100644 index 000000000..03c319142 --- /dev/null +++ b/server/include/hint.h @@ -0,0 +1,69 @@ +#ifndef _HINT_H +#define _HINT_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 hint.h The generic hint data that may be attached to buffers + * + * @verbatim + * Revision History + * + * Date Who Description + * 10/07/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include + + +/** + * The types of hint that are supported by the generic hinting mechanism. + */ +typedef enum { + HINT_ROUTE_TO_MASTER = 1, + HINT_ROUTE_TO_SLAVE, + HINT_ROUTE_TO_NAMED_SERVER, + HINT_ROUTE_TO_UPTODATE_SERVER, + HINT_ROUTE_TO_ALL, /*< not implemented yet */ + HINT_PARAMETER +} HINT_TYPE; + +/** + * A generic hint. + * + * A hint has a type associated with it and may optionally have hint + * specific data. + * Multiple hints may be attached to a single buffer. + */ +typedef struct hint { + HINT_TYPE type; /*< The Type of hint */ + void *data; /*< Type specific data */ + void *value; /*< Parameter value for hint */ + unsigned int dsize; /*< Size of the hint data */ + struct hint *next; /*< Another hint for this buffer */ +} HINT; + +extern HINT *hint_alloc(HINT_TYPE, void *, unsigned int); +extern HINT *hint_create_parameter(HINT *, char *, char *); +extern HINT *hint_create_route(HINT *, HINT_TYPE, char *); +extern void hint_free(HINT *); +extern HINT *hint_dup(HINT *); +bool hint_exists(HINT **, HINT_TYPE); +#endif diff --git a/server/include/server.h b/server/include/server.h index e747c298d..c558418bc 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -38,6 +38,7 @@ * 03/06/14 Mark Riddoch Addition of maintainance mode * 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields * 26/06/14 Mark Riddoch Adidtion of server parameters + * 30/07/14 Massimiliano Pinto Addition of NDB status for MySQL Cluster * * @endverbatim */ @@ -99,6 +100,7 @@ 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_NDB 0x0010 /**<< The server is part of a MySQL cluster setup */ #define SERVER_MAINT 0x1000 /**<< Server is in maintenance mode */ #define SERVER_SLAVE_OF_EXTERNAL_MASTER 0x0080 /**<< Server is slave of a Master outside the provided replication topology */ @@ -131,15 +133,21 @@ typedef struct server { #define SERVER_IS_JOINED(server) \ (((server)->status & (SERVER_RUNNING|SERVER_JOINED|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_JOINED)) +/** + * Is the server a SQL node in MySQL Cluster? The server must be running and with NDB status + */ +#define SERVER_IS_NDB(server) \ + (((server)->status & (SERVER_RUNNING|SERVER_NDB|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_NDB)) + /** * Is the server in maintenance mode. */ #define SERVER_IN_MAINT(server) ((server)->status & SERVER_MAINT) /** server is not master, slave or joined */ -#define SERVER_NOT_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) == 0) +#define SERVER_NOT_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED|SERVER_NDB)) == 0) -#define SERVER_IS_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) != 0) +#define SERVER_IS_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED|SERVER_NDB)) != 0) #define SERVER_IS_RELAY_SERVER(server) \ (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile index 8e6036e91..31b41ae93 100644 --- a/server/modules/filter/Makefile +++ b/server/modules/filter/Makefile @@ -45,7 +45,7 @@ TEEOBJ=$(TEESRCS:.c=.o) SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS) OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so +MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so libhintfilter.so all: $(MODULES) @@ -65,18 +65,25 @@ libtopfilter.so: $(TOPNOBJ) libtee.so: $(TEEOBJ) $(CC) $(LDFLAGS) $(TEEOBJ) $(LIBS) -o $@ +libhintfilter.so: + (cd hint; touch depend.mk ; make; cp $@ ..) + + .c.o: $(CC) $(CFLAGS) $< -o $@ clean: - $(DEL) $(OBJ) $(MODULES) + rm -f $(OBJ) $(MODULES) + (cd hint; touch depend.mk; make clean) tags: ctags $(SRCS) $(HDRS) + (cd hint; touch depend.mk; make tags) depend: @$(DEL) depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk + (cd hint; touch depend.mk; make depend) install: $(MODULES) install -D $(MODULES) $(DEST)/modules diff --git a/server/modules/filter/hint/Makefile b/server/modules/filter/hint/Makefile new file mode 100644 index 000000000..4f2194739 --- /dev/null +++ b/server/modules/filter/hint/Makefile @@ -0,0 +1,70 @@ +# 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 +# 21/07/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) + +SRCS= hintfilter.c hintparser.c +OBJ=$(SRCS:.c=.o) +LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager + +libhintfilter.so: $(OBJ) + $(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@ + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -f $(OBJ) libhintfilter.so + +tags: + ctags $(SRCS) $(HDRS) + +depend: + @rm -f depend.mk + cc -M $(CFLAGS) $(SRCS) > depend.mk + +install: $(MODULES) + install -D $(MODULES) $(DEST)/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/hint/hintfilter.c b/server/modules/filter/hint/hintfilter.c new file mode 100644 index 000000000..e54319d5d --- /dev/null +++ b/server/modules/filter/hint/hintfilter.c @@ -0,0 +1,272 @@ +/* + * 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 + +/** + * hintfilter.c - a filter to parse the MaxScale hint syntax and attach those + * hints to the buffers that carry the requests. + * + */ + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A hint parsing 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, + NULL, // No upstream requirement + routeQuery, + NULL, + diagnostic, +}; + +/** + * 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) +{ +HINT_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(HINT_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) +{ +HINT_INSTANCE *my_instance = (HINT_INSTANCE *)instance; +HINT_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(HINT_SESSION))) != NULL) + { + my_session->query_len = 0; + my_session->request = NULL; + my_session->stack = NULL; + my_session->named_hints = NULL; + } + + 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) +{ +HINT_SESSION *my_session = (HINT_SESSION *)session; +NAMEDHINTS* named_hints; +HINTSTACK* hint_stack; + + if (my_session->request) + gwbuf_free(my_session->request); + + + /** Free named hints */ + named_hints = my_session->named_hints; + + while ((named_hints = free_named_hint(named_hints)) != NULL) + ; + /** Free stacked hints */ + hint_stack = my_session->stack; + + while ((hint_stack = free_hint_stack(hint_stack)) != NULL) + ; +} + +/** + * 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) +{ +HINT_SESSION *my_session = (HINT_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) +{ +HINT_SESSION *my_session = (HINT_SESSION *)session; +char *ptr; +int rval, len, residual; +HINT *hint; + + if (my_session->request == NULL) + { + /* + * No stored buffer, so this must be the first + * buffer of a new request. + */ + if (modutil_MySQL_Query(queue, &ptr, &len, &residual) == 0) + { + return my_session->down.routeQuery( + my_session->down.instance, + my_session->down.session, queue); + } + my_session->request = queue; + my_session->query_len = len; + } + else + { + gwbuf_append(my_session->request, queue); + } + + if (gwbuf_length(my_session->request) < my_session->query_len) + { + /* + * We have not got the entire SQL text, buffer and wait for + * the remainder. + */ + return 1; + } + /* We have the entire SQL text, parse for hints and attach to the + * buffer at the head of the queue. + */ + queue = my_session->request; + my_session->request = NULL; + my_session->query_len = 0; + hint = hint_parser(my_session, queue); + queue->hint = hint; + + /* Now process the request */ + rval = my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); + return rval; +} + +/** + * 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) +{ +HINT_INSTANCE *my_instance = (HINT_INSTANCE *)instance; +HINT_SESSION *my_session = (HINT_SESSION *)fsession; + +} diff --git a/server/modules/filter/hint/hintparser.c b/server/modules/filter/hint/hintparser.c new file mode 100644 index 000000000..2c6624328 --- /dev/null +++ b/server/modules/filter/hint/hintparser.c @@ -0,0 +1,761 @@ +/* + * 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 +#include +#include +#include + +extern int lm_enabled_logfiles_bitmask; + +/** + * hintparser.c - Find any comment in the SQL packet and look for MAXSCALE + * hints in that comment. + */ + +/** + * The keywords in the hint syntax + */ +struct { + char *keyword; + TOKEN_VALUE token; +} keywords[] = { + { "maxscale", TOK_MAXSCALE }, + { "prepare", TOK_PREPARE }, + { "start", TOK_START }, + { "begin", TOK_START }, + { "stop", TOK_STOP }, + { "end", TOK_STOP }, + { "=", TOK_EQUAL }, + { "route", TOK_ROUTE }, + { "to", TOK_TO }, + { "master", TOK_MASTER }, + { "slave", TOK_SLAVE }, + { "server", TOK_SERVER }, + { NULL, 0 } +}; +/** +HINT_TOKEN kwords[] = { + { TOK_MAXSCALE, "maxscale" }, + { TOK_PREPARE, "prepare" }, + { TOK_START, "start" }, + { TOK_START, "begin" }, + { TOK_STOP, "stop" }, + { TOK_STOP, "end" }, + { TOK_EQUAL, "=" }, + { TOK_ROUTE, "route" }, + { TOK_TO, "to" }, + { TOK_MASTER, "master" }, + { TOK_SLAVE, "slave" }, + { TOK_SERVER, "server" }, + { 0, NULL} +}; +*/ + +static HINT_TOKEN *hint_next_token(GWBUF **buf, char **ptr); +static void hint_pop(HINT_SESSION *); +static HINT *lookup_named_hint(HINT_SESSION *, char *); +static void create_named_hint(HINT_SESSION *, char *, HINT *); +static void hint_push(HINT_SESSION *, HINT *); +static const char* token_get_keyword (HINT_TOKEN* token); +static void token_free(HINT_TOKEN* token); + +typedef enum { HM_EXECUTE, HM_START, HM_PREPARE } HINT_MODE; + +void token_free(HINT_TOKEN* token) +{ + if (token->value != NULL) + { + free(token->value); + } + free(token); +} + + +static const char* token_get_keyword ( + HINT_TOKEN* token) +{ + switch (token->token) { + case TOK_EOL: + return "End of line"; + break; + + case TOK_STRING: + return token->value; + break; + + default: + { + int i = 0; + while (i < TOK_EOL && keywords[i].token != token->token) + i++; + + ss_dassert(i != TOK_EOL); + + if (i == TOK_EOL) + { + return "Unknown token"; + } + else + { + return keywords[i].keyword; + } + } + break; + } +} +/** + * Parse the hint comments in the MySQL statement passed in request. + * Add any hints to the buffer for later processing. + * + * @param session The filter session + * @param request The MySQL request buffer + * @return The hints parsed in this statement or active on the + * stack + */ +HINT * +hint_parser(HINT_SESSION *session, GWBUF *request) +{ +char *ptr, lastch = ' '; +int len, residual, state; +int found, escape, quoted, squoted; +HINT *rval = NULL; +char *pname, *lvalue, *hintname = NULL; +GWBUF *buf; +HINT_TOKEN *tok; +HINT_MODE mode = HM_EXECUTE; + + /* First look for any comment in the SQL */ + modutil_MySQL_Query(request, &ptr, &len, &residual); + buf = request; + found = 0; + escape = 0; + quoted = 0; + squoted = 0; + do { + while (len--) + { + if (*ptr == '\\') + escape = 1; + else if (*ptr == '\"' && quoted) + quoted = 0; + else if (*ptr == '\"' && quoted == 0) + quoted = 0; + else if (*ptr == '\'' && squoted) + squoted = 0; + else if (*ptr == '\"' && squoted == 0) + squoted = 0; + else if (quoted || squoted) + ; + else if (escape) + escape = 0; + else if (*ptr == '#') + { + found = 1; + break; + } + else if (*ptr == '/') + lastch = '/'; + else if (*ptr == '*' && lastch == '/') + { + found = 1; + break; + } + else if (*ptr == '-' && lastch == '-') + { + found = 1; + break; + } + else if (*ptr == '-') + lastch = '-'; + else + lastch = *ptr; + ptr++; + } + if (found) + break; + + buf = buf->next; + if (buf) + { + len = GWBUF_LENGTH(buf); + ptr = GWBUF_DATA(buf); + } + } while (buf); + + if (!found) /* No comment so we need do no more */ + { + goto retblock; + } + + /* + * If we have got here then we have a comment, ptr point to + * the comment character if it is a '#' comment or the second + * character of the comment if it is a -- or /* comment + * + * Move to the next character in the SQL. + */ + ptr++; + if (ptr > (char *)(buf->end)) + { + buf = buf->next; + if (buf) + ptr = GWBUF_DATA(buf); + else + goto retblock; + } + + tok = hint_next_token(&buf, &ptr); + + if (tok == NULL) + { + goto retblock; + } + + if (tok->token != TOK_MAXSCALE) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Invalid hint string '%s'. Hint should start " + "with keyword 'maxscale'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Invalid hint string '%s'. Hint should start " + "with keyword 'maxscale'. Hint ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + token_free(tok); + + state = HS_INIT; + + while ((tok = hint_next_token(&buf, &ptr)) != NULL + && tok->token != TOK_EOL) + { + switch (state) + { + case HS_INIT: + switch (tok->token) + { + case TOK_ROUTE: + state = HS_ROUTE; + break; + case TOK_STRING: + state = HS_NAME; + lvalue = strdup(tok->value); + break; + case TOK_STOP: + /* Action: pop active hint */ + hint_pop(session); + state = HS_INIT; + break; + default: + /* Error: expected hint, name or STOP */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "'route', 'stop' or hint name instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "'route', 'stop' or hint name instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + break; + case HS_ROUTE: + if (tok->token != TOK_TO) + { + /* Error, expect TO */; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "'to' instead of '%s'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "'to' instead of '%s'. Hint ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + state = HS_ROUTE1; + break; + case HS_ROUTE1: + switch (tok->token) + { + case TOK_MASTER: + rval = hint_create_route(rval, + HINT_ROUTE_TO_MASTER, NULL); + break; + case TOK_SLAVE: + rval = hint_create_route(rval, + HINT_ROUTE_TO_SLAVE, NULL); + break; + case TOK_SERVER: + state = HS_ROUTE_SERVER; + break; + default: + /* Error expected MASTER, SLAVE or SERVER */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "'master', 'slave', or 'server' instead " + "of '%s'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "'master', 'slave', or 'server' instead " + "of '%s'. Hint ignored.", + token_get_keyword(tok)))); + + token_free(tok); + goto retblock; + } + break; + case HS_ROUTE_SERVER: + if (tok->token == TOK_STRING) + { + rval = hint_create_route(rval, + HINT_ROUTE_TO_NAMED_SERVER, tok->value); + } + else + { + /* Error: Expected server name */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "server name instead of '%s'. Hint " + "ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "server name instead of '%s'. Hint " + "ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + break; + case HS_NAME: + switch (tok->token) + { + case TOK_EQUAL: + pname = lvalue; + state = HS_PVALUE; + break; + case TOK_PREPARE: + pname = lvalue; + state = HS_PREPARE; + break; + case TOK_START: + /* Action start(lvalue) */ + hintname = lvalue; + mode = HM_START; + state = HS_INIT; + break; + default: + /* Error, token tok->value not expected */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "'=', 'prepare', or 'start' instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "'=', 'prepare', or 'start' instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + break; + case HS_PVALUE: + /* Action: pname = tok->value */ + rval = hint_create_parameter(rval, pname, tok->value); + state = HS_INIT; + break; + case HS_PREPARE: + mode = HM_PREPARE; + hintname = lvalue; + switch (tok->token) + { + case TOK_ROUTE: + state = HS_ROUTE; + break; + case TOK_STRING: + state = HS_NAME; + lvalue = tok->value; + break; + default: + /* Error, token tok->value not expected */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Syntax error in hint. Expected " + "'route' or hint name instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Syntax error in hint. Expected " + "'route' or hint name instead of " + "'%s'. Hint ignored.", + token_get_keyword(tok)))); + token_free(tok); + goto retblock; + } + break; + } + token_free(tok); + } /*< while */ + + if (tok->token == TOK_EOL) + { + token_free(tok); + } + + switch (mode) + { + case HM_START: + /* + * We are starting either a predefined set of hints, + * creating a new set of hints and starting in a single + * operation or starting an annonymous block of hints. + */ + if (hintname == NULL && rval != NULL) + { + /* We are starting an anonymous block of hints */ + hint_push(session, rval); + rval = NULL; + } else if (hintname && rval) + { + /* We are creating and starting a block of hints */ + if (lookup_named_hint(session, hintname) != NULL) + { + /* Error hint with this name already exists */ + } + else + { + create_named_hint(session, hintname, rval); + hint_push(session, hint_dup(rval)); + } + } else if (hintname && rval == NULL) + { + /* We starting an already define set of named hints */ + rval = lookup_named_hint(session, hintname); + hint_push(session, hint_dup(rval)); + free(hintname); + rval = NULL; + } else if (hintname == NULL && rval == NULL) + { + /* Error case */ + } + break; + case HM_PREPARE: + /* + * We are preparing a named set of hints. Note this does + * not trigger the usage of these hints currently. + */ + if (hintname == NULL || rval == NULL) + { + /* Error case, name and hints must be defined */ + } + else + { + create_named_hint(session, hintname, rval); + } + /* We are not starting the hints now, so return an empty + * hint set. + */ + rval = NULL; + break; + case HM_EXECUTE: + /* + * We have a one-off hint for the statement we are + * currently forwarding. + */ + break; + } + +retblock: + if (rval == NULL) + { + /* No new hint parsed in this statement, apply the current + * top of stack if there is one. + */ + if (session->stack) + rval = hint_dup(session->stack->hint); + } + return rval; +} + +/** + * Return the next token in the inout stream + * + * @param buf A pointer to the buffer point, will be updated if a + * new buffer is used. + * @param ptr The pointer within the buffer we are processing + * @return A HINT token + */ +static HINT_TOKEN * +hint_next_token(GWBUF **buf, char **ptr) +{ +char word[100], *dest; +int inword = 0; +char inquote = '\0'; +int i, found; +HINT_TOKEN *tok; + + if ((tok = (HINT_TOKEN *)malloc(sizeof(HINT_TOKEN))) == NULL) + return NULL; + tok->value = NULL; + dest = word; + while (*ptr < (char *)((*buf)->end) || (*buf)->next) + { + /** word ends, don't move ptr but return with read word */ + if (inword && inquote == '\0' && + (isspace(**ptr) || **ptr == '=')) + { + inword = 0; + break; + } + /** found '=', move ptr and return with '=' */ + else if (!inword && inquote == '\0' && **ptr == '=') + { + *dest = **ptr; + *dest++; + (*ptr)++; + break; + } + else if (**ptr == '\'' && inquote == '\'') + inquote = '\0'; + else if (**ptr == '\'') + inquote = **ptr; + /** Any other character which belongs to the word, move ahead */ + else if (inword || (isspace(**ptr) == 0)) + { + *dest++ = **ptr; + inword = 1; + } + (*ptr)++; + + if (*ptr > (char *)((*buf)->end) && (*buf)->next) + { + *buf = (*buf)->next; + *ptr = (*buf)->start; + } + + if (dest - word > 98) + break; + } /*< while */ + *dest = 0; + + /* We now have a word in the local word, check to see if it is a + * token we recognise. + */ + if (word[0] == '\0') + { + tok->token = TOK_EOL; + return tok; + } + found = 0; + for (i = 0; keywords[i].keyword; i++) + { + if (strcasecmp(word, keywords[i].keyword) == 0) + { + tok->token = keywords[i].token; + found = 1; + break; + } + } + if (found == 0) + { + tok->token = TOK_STRING; + tok->value = strdup(word); + } + + return tok; +} + + +/** + * hint_pop - pop the hint off the top of the stack if it is not empty + * + * @param session The filter session. + */ +void +hint_pop(HINT_SESSION *session) +{ +HINTSTACK *ptr; +HINT *hint; + + if ((ptr = session->stack) != NULL) + { + session->stack = ptr->next; + while (ptr->hint) + { + hint = ptr->hint; + ptr->hint = hint->next; + hint_free(hint); + } + free(ptr); + } +} + +/** + * Push a hint onto the stack of actie hints + * + * @param session The filter session + * @param hint The hint to push, the hint ownership is retained + * by the stack and should not be freed by the caller + */ +static void +hint_push(HINT_SESSION *session, HINT *hint) +{ +HINTSTACK *item; + + if ((item = (HINTSTACK *)malloc(sizeof(HINTSTACK))) == NULL) + return; + item->hint = hint; + item->next = session->stack; + session->stack = item; +} + +/** + * Search for a hint block that already exists with this name + * + * @param session The filter session + * @param name The name to lookup + * @return the HINT or NULL if the name was not found. + */ +static HINT * +lookup_named_hint(HINT_SESSION *session, char *name) +{ +NAMEDHINTS *ptr = session->named_hints; + + while (ptr) + { + if (strcmp(ptr->name, name) == 0) + return ptr->hints; + ptr = ptr->next; + } + return NULL; +} + +/** + * Create a named hint block + * + * @param session The filter session + * @param name The name of the block to ceate + * @param hint The hints themselves + */ +static void +create_named_hint(HINT_SESSION *session, char *name, HINT *hint) +{ +NAMEDHINTS *block; + + if ((block = (NAMEDHINTS *)malloc(sizeof(NAMEDHINTS))) == NULL) + return; + + block->name = name; + block->hints = hint_dup(hint); + block->next = session->named_hints; + session->named_hints = block; +} + +/** + * Release the given NAMEDHINTS struct and all included hints. + * + * @param named_hint NAMEDHINTS struct to be released + * + * @return pointer to next NAMEDHINTS struct. + */ +NAMEDHINTS* free_named_hint( + NAMEDHINTS* named_hint) +{ + NAMEDHINTS* next; + + if (named_hint != NULL) + { + HINT* hint; + + next = named_hint->next; + + while (named_hint->hints != NULL) + { + hint = named_hint->hints->next; + hint_free(named_hint->hints); + named_hint->hints = hint; + } + free(named_hint->name); + free(named_hint); + return next; + } + else + { + return NULL; + } +} + +/** + * Release the given HINTSTACK struct and all included hints. + * + * @param hint_stack HINTSTACK struct to be released + * + * @return pointer to next HINTSTACK struct. + */ +HINTSTACK* free_hint_stack( + HINTSTACK* hint_stack) +{ + HINTSTACK* next; + + if (hint_stack != NULL) + { + HINT* hint; + + next = hint_stack->next; + + while (hint_stack->hint != NULL) + { + hint = hint_stack->hint->next; + hint_free(hint_stack->hint); + hint_stack->hint = hint; + } + free(hint_stack); + return next; + } + else + { + return NULL; + } +} \ No newline at end of file diff --git a/server/modules/include/mysqlhint.h b/server/modules/include/mysqlhint.h new file mode 100644 index 000000000..3e936b847 --- /dev/null +++ b/server/modules/include/mysqlhint.h @@ -0,0 +1,114 @@ +#ifndef _MYSQLHINT_H +#define _MYSQLHINT_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 2013 + */ + +/* + * Revision History + * + * Date Who Description + * 17-07-2014 Mark Riddoch Initial implementation + */ +#include + +/* Parser tokens for the hint parser */ +typedef enum { + TOK_MAXSCALE = 1, + TOK_PREPARE, + TOK_START, + TOK_STOP, + TOK_EQUAL, + TOK_STRING, + TOK_ROUTE, + TOK_TO, + TOK_MASTER, + TOK_SLAVE, + TOK_SERVER, + TOK_EOL +} TOKEN_VALUE; + +/* The tokenising return type */ +typedef struct { + TOKEN_VALUE token; // The token itself + char *value; // The string version of the token +} HINT_TOKEN; + +/** + * A named hint set. + * + * The hint "MaxScale name PREPARE ..." can be used to defined a named set + * of hints that can be later applied. + */ +typedef struct namedhints { + char *name; /*< Hintsets name */ + HINT *hints; + struct namedhints + *next; /*< Next named hint */ +} NAMEDHINTS; + +/** + * A session meaintains a stack of hints, the hints BEGIN and STOP are used + * push hints on and off the stack. The current top of the stack is added to + * any statement that does not explicitly define a hint for that signle + * statement. + */ +typedef struct hintstack { + HINT *hint; + struct hintstack + *next; +} HINTSTACK; + +/** + * The hint instance structure + */ +typedef struct { + int sessions; +} HINT_INSTANCE; + +/** + * A hint parser session structure + */ +typedef struct { + DOWNSTREAM down; + GWBUF *request; + int query_len; + HINTSTACK *stack; + NAMEDHINTS *named_hints; /* The named hints defined in this session */ +} HINT_SESSION; + +/* Some useful macros */ +#define CURRENT_HINT(session) ((session)->stack ? \ + (session)->stack->hints : NULL) + +/* Hint Parser State Machine */ +#define HS_INIT 0 +#define HS_ROUTE 1 +#define HS_ROUTE1 2 +#define HS_ROUTE_SERVER 3 +#define HS_NAME 4 +#define HS_PVALUE 5 +#define HS_PREPARE 6 + + +extern HINT *hint_parser(HINT_SESSION *session, GWBUF *request); +NAMEDHINTS* free_named_hint(NAMEDHINTS* named_hint); +HINTSTACK* free_hint_stack(HINTSTACK* hint_stack); + + + +#endif diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 6fc639005..1a8936c74 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -67,10 +67,24 @@ typedef enum backend_type_t { BE_UNDEFINED=-1, BE_MASTER, BE_JOINED = BE_MASTER, - BE_SLAVE, + BE_SLAVE, BE_COUNT } backend_type_t; +typedef enum { + TARGET_MASTER = 0x01, + TARGET_SLAVE = 0x02, + TARGET_NAMED_SERVER = 0x04, + TARGET_ALL = 0x08, + TARGET_RLAG_MAX = 0x10 +} route_target_t; + +#define TARGET_IS_MASTER(t) (t & TARGET_MASTER) +#define TARGET_IS_SLAVE(t) (t & TARGET_SLAVE) +#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER) +#define TARGET_IS_ALL(t) (t & TARGET_ALL) +#define TARGET_IS_RLAG_MAX(t) (t & TARGET_RLAG_MAX) + typedef struct rses_property_st rses_property_t; typedef struct router_client_session ROUTER_CLIENT_SES; diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile index 8b60824ea..0f3bc5867 100644 --- a/server/modules/monitor/Makefile +++ b/server/modules/monitor/Makefile @@ -17,6 +17,7 @@ # Revision History # Date Who Description # 08/07/13 Mark Riddoch Initial implementation +# 28/07/14 Massimiliano Pinto new monitor ndbcluster added include ../../../build_gateway.inc include ../../../makefile.inc @@ -38,11 +39,13 @@ MYSQLSRCS=mysql_mon.c MYSQLOBJ=$(MYSQLSRCS:.c=.o) GALERASRCS=galera_mon.c GALERAOBJ=$(GALERASRCS:.c=.o) -SRCS=$(MYSQLSRCS) +NDBCLUSTERSRCS=ndbcluster_mon.c +NDBCLUSTEROBJ=$(NDBCLUSTERSRCS:.c=.o) +SRCS=$(MYSQLSRCS) $(GALERASRCS) $(NDBCLUSTERSRCS) OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -llog_manager \ -L$(EMBEDDED_LIB) -lmysqld -MODULES=libmysqlmon.so libgaleramon.so +MODULES=libmysqlmon.so libgaleramon.so libndbclustermon.so all: $(MODULES) @@ -53,6 +56,9 @@ libmysqlmon.so: $(MYSQLOBJ) libgaleramon.so: $(GALERAOBJ) $(CC) $(LDFLAGS) $(GALERAOBJ) $(LIBS) -o $@ +libndbclustermon.so: $(NDBCLUSTEROBJ) + $(CC) $(LDFLAGS) $(NDBCLUSTEROBJ) $(LIBS) -o $@ + .c.o: $(CC) $(CFLAGS) $< -o $@ diff --git a/server/modules/monitor/ndbcluster_mon.c b/server/modules/monitor/ndbcluster_mon.c new file mode 100644 index 000000000..12cc181e3 --- /dev/null +++ b/server/modules/monitor/ndbcluster_mon.c @@ -0,0 +1,461 @@ +/* + * 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 2013 + */ + +/** + * @file ndbcluster_mon.c - A MySQL cluster SQL node monitor + * + * @verbatim + * Revision History + * + * Date Who Description + * 25/07/14 Massimiliano Pinto Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int lm_enabled_logfiles_bitmask; + +static void monitorMain(void *); + +static char *version_str = "V1.0.0"; + +MODULE_INFO info = { + MODULE_API_MONITOR, + MODULE_BETA_RELEASE, + MONITOR_VERSION, + "A MySQL cluster SQL node monitor" +}; + +static void *startMonitor(void *); +static void stopMonitor(void *); +static void registerServer(void *, SERVER *); +static void unregisterServer(void *, SERVER *); +static void defaultUsers(void *, char *, char *); +static void diagnostics(DCB *, void *); +static void setInterval(void *, unsigned long); + +static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics, setInterval, NULL, NULL }; + +/** + * 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() +{ + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Initialise the MySQL Cluster Monitor module %s.\n", + version_str))); +} + +/** + * 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 + */ +MONITOR_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Start the instance of the monitor, returning a handle on the monitor. + * + * This function creates a thread to execute the actual monitoring. + * + * @return A handle to use when interacting with the monitor + */ +static void * +startMonitor(void *arg) +{ +MYSQL_MONITOR *handle; + + if (arg != NULL) + { + handle = (MYSQL_MONITOR *)arg; + handle->shutdown = 0; + } + else + { + if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL) + return NULL; + handle->databases = NULL; + handle->shutdown = 0; + handle->defaultUser = NULL; + handle->defaultPasswd = NULL; + handle->id = MONITOR_DEFAULT_ID; + handle->interval = MONITOR_INTERVAL; + spinlock_init(&handle->lock); + } + handle->tid = (THREAD)thread_start(monitorMain, handle); + return handle; +} + +/** + * Stop a running monitor + * + * @param arg Handle on thr running monior + */ +static void +stopMonitor(void *arg) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + + handle->shutdown = 1; + thread_wait((void *)handle->tid); +} + +/** + * Register a server that must be added to the monitored servers for + * a monitoring module. + * + * @param arg A handle on the running monitor module + * @param server The server to add + */ +static void +registerServer(void *arg, SERVER *server) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MONITOR_SERVERS *ptr, *db; + + if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL) + return; + db->server = server; + db->con = NULL; + db->next = NULL; + spinlock_acquire(&handle->lock); + if (handle->databases == NULL) + handle->databases = db; + else + { + ptr = handle->databases; + while (ptr->next != NULL) + ptr = ptr->next; + ptr->next = db; + } + spinlock_release(&handle->lock); +} + +/** + * Remove a server from those being monitored by a monitoring module + * + * @param arg A handle on the running monitor module + * @param server The server to remove + */ +static void +unregisterServer(void *arg, SERVER *server) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MONITOR_SERVERS *ptr, *lptr; + + spinlock_acquire(&handle->lock); + if (handle->databases == NULL) + { + spinlock_release(&handle->lock); + return; + } + if (handle->databases->server == server) + { + ptr = handle->databases; + handle->databases = handle->databases->next; + free(ptr); + } + else + { + ptr = handle->databases; + while (ptr->next != NULL && ptr->next->server != server) + ptr = ptr->next; + if (ptr->next) + { + lptr = ptr->next; + ptr->next = ptr->next->next; + free(lptr); + } + } + spinlock_release(&handle->lock); +} + +/** + * Diagnostic interface + * + * @param dcb DCB to send output + * @param arg The monitor handle + */ +static void +diagnostics(DCB *dcb, void *arg) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MONITOR_SERVERS *db; +char *sep; + + switch (handle->status) + { + case MONITOR_RUNNING: + dcb_printf(dcb, "\tMonitor running\n"); + break; + case MONITOR_STOPPING: + dcb_printf(dcb, "\tMonitor stopping\n"); + break; + case MONITOR_STOPPED: + dcb_printf(dcb, "\tMonitor stopped\n"); + break; + } + + dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval); + dcb_printf(dcb, "\tMonitored servers: "); + + db = handle->databases; + sep = ""; + while (db) + { + dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port); + sep = ", "; + db = db->next; + } + dcb_printf(dcb, "\n"); +} + +/** + * Set the default username and password to use to monitor if the server does not + * override this. + * + * @param arg The handle allocated by startMonitor + * @param uname The default user name + * @param passwd The default password + */ +static void +defaultUsers(void *arg, char *uname, char *passwd) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; + + if (handle->defaultUser) + free(handle->defaultUser); + if (handle->defaultPasswd) + free(handle->defaultPasswd); + handle->defaultUser = strdup(uname); + handle->defaultPasswd = strdup(passwd); +} + +/** + * Monitor an individual server + * + * @param database The database to probe + */ +static void +monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd) +{ +MYSQL_ROW row; +MYSQL_RES *result; +int num_fields; +int isjoined = 0; +char *uname = defaultUser, *passwd = defaultPasswd; +unsigned long int server_version = 0; +char *server_string; + + if (database->server->monuser != NULL) + { + uname = database->server->monuser; + passwd = database->server->monpw; + } + 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; + } + free(dpwd); + } + + /* 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 SQL node is able to contact one or more data nodes */ + if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + if (atoi(row[1]) > 0) + isjoined = 1; + } + mysql_free_result(result); + } + + /* Check the the SQL node id in the MySQL cluster */ + if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_cluster_node_id'") == 0 + && (result = mysql_store_result(database->con)) != NULL) + { + long cluster_node_id = -1; + num_fields = mysql_num_fields(result); + while ((row = mysql_fetch_row(result))) + { + cluster_node_id = strtol(row[1], NULL, 10); + if ((errno == ERANGE && (cluster_node_id == LONG_MAX + || cluster_node_id == LONG_MIN)) || (errno != 0 && cluster_node_id == 0)) + { + cluster_node_id = -1; + } + database->server->node_id = cluster_node_id; + } + mysql_free_result(result); + } + + if (isjoined) { + server_set_status(database->server, SERVER_NDB); + database->server->depth = 0; + } else { + server_clear_status(database->server, SERVER_NDB); + database->server->depth = -1; + } +} + +/** + * The entry point for the monitoring module thread + * + * @param arg The handle of the monitor + */ +static void +monitorMain(void *arg) +{ +MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; +MONITOR_SERVERS *ptr; +long master_id; + + if (mysql_thread_init()) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Fatal : mysql_thread_init failed in monitor " + "module. Exiting.\n"))); + return; + } + handle->status = MONITOR_RUNNING; + while (1) + { + master_id = -1; + + if (handle->shutdown) + { + handle->status = MONITOR_STOPPING; + mysql_thread_end(); + handle->status = MONITOR_STOPPED; + return; + } + + ptr = handle->databases; + + while (ptr) + { + unsigned int prev_status = ptr->server->status; + monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd); + + 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(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/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index ce7b6ef97..d79d7a044 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -422,7 +422,6 @@ static int gw_read_backend_event(DCB *dcb) { GWBUF *read_buffer = NULL; ROUTER_OBJECT *router = NULL; ROUTER *router_instance = NULL; - void *rsession = NULL; SESSION *session = dcb->session; int nbytes_read = 0; diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 6ffe4e56f..8454516e2 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -1420,7 +1420,6 @@ static int route_by_statement( int rc = -1; GWBUF* packetbuf; #if defined(SS_DEBUG) - gwbuf_type_t prevtype; GWBUF* tmpbuf; tmpbuf = *p_readbuf; diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 8287bdaea..f3d772a36 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -88,19 +88,15 @@ install: $(MODULES) (cd readwritesplit; make DEST=$(DEST) install) cleantests: - $(MAKE) -C readwritesplit/test cleantests $(MAKE) -C test cleantests - + buildtests: - $(MAKE) -C readwritesplit/test DEBUG=Y buildtests $(MAKE) -C test DEBUG=Y buildtests - + runtests: $(MAKE) -C test runtests - $(MAKE) -C readwritesplit runtests - + testall: $(MAKE) -C test testall - $(MAKE) -C readwritesplit testall - + include depend.mk diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index a9a5da12a..d5d249c2b 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -831,6 +831,7 @@ static struct { { "master", SERVER_MASTER }, { "slave", SERVER_SLAVE }, { "synced", SERVER_JOINED }, + { "ndb", SERVER_NDB }, { "maintenance", SERVER_MAINT }, { "maint", SERVER_MAINT }, { NULL, 0 } diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index 0fc5fd4ce..a5c648c67 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -311,6 +311,11 @@ char *weightby; inst->bitmask |= (SERVER_JOINED); inst->bitvalue |= SERVER_JOINED; } + else if (!strcasecmp(options[i], "ndb")) + { + inst->bitmask |= (SERVER_NDB); + inst->bitvalue |= SERVER_NDB; + } else { LOGIF(LM, (skygw_log_write( @@ -318,7 +323,7 @@ char *weightby; "* Warning : Unsupported router " "option \'%s\' for readconnroute. " "Expected router options are " - "[slave|master|synced]", + "[slave|master|synced|ndb]", options[i]))); } } diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index f89f68562..210feaac2 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -98,6 +98,12 @@ static int rses_get_max_slavecount(ROUTER_CLIENT_SES* rses, int router_nservers static int rses_get_max_replication_lag(ROUTER_CLIENT_SES* rses); static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb); +static route_target_t get_route_target ( + skygw_query_type_t qtype, + bool trx_active, + HINT* hint); + + static uint8_t getCapabilities (ROUTER* inst, void* router_session); #if defined(NOT_USED) @@ -154,7 +160,9 @@ static bool select_connect_backend_servers( static bool get_dcb( DCB** dcb, ROUTER_CLIENT_SES* rses, - backend_type_t btype); + backend_type_t btype, + char* name, + int max_rlag); static void rwsplit_process_router_options( ROUTER_INSTANCE* router, @@ -913,19 +921,29 @@ static void freeSession( } /** - * Provide a pointer to a suitable backend dcb. + * Provide the router with a pointer to a suitable backend dcb. * Detect failures in server statuses and reselect backends if necessary. + * If name is specified, server name becomes primary selection criteria. + * + * @param p_dcb Address of the pointer to the resulting DCB + * @param rses Pointer to router client session + * @param btype Backend type + * @param name Name of the backend which is primarily searched. May be NULL. + * + * @return True if proper DCB was found, false otherwise. */ static bool get_dcb( DCB** p_dcb, ROUTER_CLIENT_SES* rses, - backend_type_t btype) + backend_type_t btype, + char* name, + int max_rlag) { backend_ref_t* backend_ref; int smallest_nconn = -1; int i; bool succp = false; - BACKEND *master_host = NULL; + BACKEND* master_host; CHK_CLIENT_RSES(rses); ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); @@ -936,55 +954,95 @@ static bool get_dcb( } backend_ref = rses->rses_backend_ref; - /* get root master from availbal servers */ + /** get root master from available servers */ master_host = get_root_master(backend_ref, rses->rses_nbackends); if (btype == BE_SLAVE) { - for (i=0; irses_nbackends; i++) + if (name != NULL) /*< Choose backend by name (hint) */ { - BACKEND* b = backend_ref[i].bref_backend; - /* check slave bit, also for relay servers (Master & Servers) */ - if (BREF_IS_IN_USE((&backend_ref[i])) && - (SERVER_IS_SLAVE(b->backend_server) || SERVER_IS_RELAY_SERVER(b->backend_server)) && - (master_host != NULL && b->backend_server != master_host->backend_server) && - (smallest_nconn == -1 || - b->backend_conn_count < smallest_nconn)) + for (i=0; irses_nbackends; i++) { - *p_dcb = backend_ref[i].bref_dcb; - smallest_nconn = b->backend_conn_count; - succp = true; - ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); + BACKEND* b = backend_ref[i].bref_backend; + + /** + * To become chosen: + * backend must be in use, name must match, + * root master node must be found, + * backend's role must be either slave, relay + * server, or master. + */ + if (BREF_IS_IN_USE((&backend_ref[i])) && + (strncasecmp( + name, + b->backend_server->unique_name, + MIN(strlen(b->backend_server->unique_name), PATH_MAX)) == 0) && + master_host != NULL && +#if 0 + (max_rlag == MAX_RLAG_UNDEFINED || + (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->backend_server->rlag <= max_rlag)) && +#endif + (SERVER_IS_SLAVE(b->backend_server) || + SERVER_IS_RELAY_SERVER(b->backend_server) || + SERVER_IS_MASTER(b->backend_server))) + { + *p_dcb = backend_ref[i].bref_dcb; + succp = true; + ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); + break; + } } } - if (!succp) + if (!succp) /*< No hints or finding named backend failed */ { - backend_ref = rses->rses_master_ref; - - if (BREF_IS_IN_USE(backend_ref)) + for (i=0; irses_nbackends; i++) { - *p_dcb = backend_ref->bref_dcb; - succp = true; - - ss_dassert(backend_ref->bref_dcb->state != DCB_STATE_ZOMBIE); - - ss_dassert( - (master_host && (backend_ref->bref_backend->backend_server == master_host->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))); + BACKEND* b = backend_ref[i].bref_backend; + /** + * To become chosen: + * backend must be in use, + * root master node must be found, + * backend is not allowed to be the master, + * backend's role can be either slave or relay + * server and it must have least connections + * at the moment. + */ + if (BREF_IS_IN_USE((&backend_ref[i])) && + master_host != NULL && + b->backend_server != master_host->backend_server && + (max_rlag == MAX_RLAG_UNDEFINED || + (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && + b->backend_server->rlag <= max_rlag)) && + (SERVER_IS_SLAVE(b->backend_server) || + SERVER_IS_RELAY_SERVER(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; + ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); + } } - } - ss_dassert(succp); + } + + if (!succp) /*< No valid slave was found, search master next */ + { + btype = BE_MASTER; + + 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))); + } } - else if (btype == BE_MASTER) + + if (btype == BE_MASTER) { for (i=0; irses_nbackends; i++) { @@ -999,10 +1057,107 @@ static bool get_dcb( } } } + return_succp: return succp; } +/** + * Examine the query type, transaction state and routing hints. Find out the + * target for query routing. + * + * @param qtype Type of query + * @param trx_active Is transacation active or not + * @param hint Pointer to list of hints attached to the query buffer + * + * @return bitfield including the routing target, or the target server name + * if the query would otherwise be routed to slave. + */ +static route_target_t get_route_target ( + skygw_query_type_t qtype, + bool trx_active, + HINT* hint) +{ + route_target_t target; + + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)) + { + /** hints don't affect on routing */ + target = TARGET_ALL; + } + else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && !trx_active) + { + target = TARGET_SLAVE; + + /** process routing hints */ + while (hint != NULL) + { + if (hint->type == HINT_ROUTE_TO_MASTER) + { + target = TARGET_MASTER; /*< override */ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: route to master."))); + break; + } + else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) + { + target |= TARGET_NAMED_SERVER; /*< add */ + } + else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER) + { + /** not implemented */ + } + else if (hint->type == HINT_ROUTE_TO_ALL) + { + /** not implemented */ + } + else if (hint->type == HINT_PARAMETER) + { + if (strncasecmp( + (char *)hint->data, + "max_slave_replication_lag", + strlen("max_slave_replication_lag")) == 0) + { + target |= TARGET_RLAG_MAX; + } + else + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Error : Unknown hint parameter " + "'%s' when 'max_slave_replication_lag' " + "was expected.", + (char *)hint->data))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unknown hint parameter " + "'%s' when 'max_slave_replication_lag' " + "was expected.", + (char *)hint->data))); + } + } + else if (hint->type == HINT_ROUTE_TO_SLAVE) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: route to slave."))); + } + hint = hint->next; + } /*< while (hint != NULL) */ + } + else + { + /** hints don't affect on routing */ + target = TARGET_MASTER; + } + + return target; +} + + /** * The main routing entry, this is called with every packet that is * received and has to be forwarded to the backend database. @@ -1032,14 +1187,20 @@ static int routeQuery( GWBUF* querybuf) { skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; + GWBUF* plainsqlbuf = NULL; + char* querystr = NULL; + char* startpos; mysql_server_cmd_t packet_type; uint8_t* packet; int ret = 0; DCB* master_dcb = NULL; - DCB* slave_dcb = NULL; + DCB* target_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; bool rses_is_closed = false; + size_t len; + MYSQL* mysql = NULL; + route_target_t route_target; CHK_CLIENT_RSES(router_cli_ses); @@ -1164,12 +1325,20 @@ static int routeQuery( router_cli_ses->rses_autocommit_enabled = true; router_cli_ses->rses_transaction_active = false; } - /** - * Session update is always routed in the same way. + /** + * Find out where to route the query. Result may not be clear; it is + * possible to have a hint for routing to a named server which can + * be either slave or master. + * If query would otherwise be routed to slave then the hint determines + * actual target server if it exists. + * + * route_target is a bitfield and may include multiple values. */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)) + route_target = get_route_target(qtype, + router_cli_ses->rses_transaction_active, + querybuf->hint); + + if (TARGET_IS_ALL(route_target)) { /** * It is not sure if the session command in question requires @@ -1188,110 +1357,121 @@ static int routeQuery( } goto return_ret; } - else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && - !router_cli_ses->rses_transaction_active) + /** + * Handle routing to master and to slave + */ + else { - bool succp; + bool succp = true; + HINT* hint; + char* named_server = NULL; + int rlag_max = MAX_RLAG_UNDEFINED; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "[%s]\tRead-only query, routing to Slave.", - inst->service->name))); - ss_dassert(QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)); + if (router_cli_ses->rses_transaction_active) /*< all to master */ + { + route_target = TARGET_MASTER; /*< override old value */ + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Transaction is active, routing to Master."))); + } + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s", STRQTYPE(qtype)))); /** 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 (TARGET_IS_SLAVE(route_target)) + { + if (TARGET_IS_NAMED_SERVER(route_target) || + TARGET_IS_RLAG_MAX(route_target)) + { + hint = querybuf->hint; + + while (hint != NULL) + { + if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) + { + named_server = hint->data; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: route to server " + "'%s'", + named_server))); + + } + else if (hint->type == HINT_PARAMETER && + (strncasecmp( + (char *)hint->data, + "max_slave_replication_lag", + strlen("max_slave_replication_lag")) == 0)) + { + int val = (int) strtol((char *)hint->value, + (char **)NULL, 10); + + if (val != 0 || errno == 0) + { + rlag_max = val; + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Hint: " + "max_slave_replication_lag=%d", + rlag_max))); + } + } + hint = hint->next; + } + } + + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ + { + rlag_max = rses_get_max_replication_lag(router_cli_ses); + } + + succp = get_dcb(&target_dcb, + router_cli_ses, + BE_SLAVE, + named_server, + rlag_max); + } + else if (TARGET_IS_MASTER(route_target)) + { + if (master_dcb == NULL) + { + succp = get_dcb(&master_dcb, + router_cli_ses, + BE_MASTER, + NULL, + MAX_RLAG_UNDEFINED); + } + target_dcb = master_dcb; + } - if (succp) + if (succp) /*< Have DCB of the target backend */ { - if ((ret = slave_dcb->func.write(slave_dcb, gwbuf_clone(querybuf))) == 1) + if ((ret = target_dcb->func.write(target_dcb, querybuf)) == 1) { backend_ref_t* bref; atomic_add(&inst->stats.n_slave, 1); /** - * Add one query response waiter to backend reference - */ - bref = get_bref_from_dcb(router_cli_ses, slave_dcb); + * Add one query response waiter to backend reference + */ + bref = get_bref_from_dcb(router_cli_ses, target_dcb); bref_set_state(bref, BREF_QUERY_ACTIVE); bref_set_state(bref, BREF_WAITING_RESULT); } else { - char* query_str = modutil_get_query(querybuf); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Routing query \"%s\" failed.", - (query_str == NULL ? "not available" : query_str)))); - free(query_str); + querystr))); } } rses_end_locked_router_action(router_cli_ses); - - ss_dassert(succp); - goto return_ret; - } - else - { - bool succp = true; - - if (LOG_IS_ENABLED(LOGFILE_TRACE)) - { - if (router_cli_ses->rses_transaction_active) /*< all to master */ - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Transaction is active, routing to Master."))); - } - else - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Begin transaction, write or unspecified type, " - "routing to Master."))); - } - } - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - 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, gwbuf_clone(querybuf))) == 1) - { - backend_ref_t* bref; - - atomic_add(&inst->stats.n_master, 1); - - /** - * Add one write response waiter to backend reference - */ - bref = get_bref_from_dcb(router_cli_ses, master_dcb); - bref_set_state(bref, BREF_QUERY_ACTIVE); - bref_set_state(bref, BREF_WAITING_RESULT); - } - } - 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 defined(SS_DEBUG) @@ -1315,7 +1495,7 @@ return_ret: } -/** to be inline'd */ + /** * @node Acquires lock to router client session if it is not closed. * @@ -1520,7 +1700,6 @@ static void clientReply ( if (LOG_IS_ENABLED(LOGFILE_ERROR) && MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(writebuf)))) { - SESSION* ses = backend_dcb->session; uint8_t* buf = (uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf)); size_t len = MYSQL_GET_PACKET_LEN(buf); @@ -1845,7 +2024,9 @@ static bool select_connect_backend_servers( #endif /* assert with master_host */ ss_dassert(!master_connected || - (master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && SERVER_MASTER)); + (master_host && + ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && + SERVER_MASTER)); /** * Sort the pointer list to servers according to connection counts. As * a consequence those backends having least connections are in the @@ -1923,8 +2104,8 @@ static bool select_connect_backend_servers( { /* check also for relay servers and don't take the master_host */ if (slaves_found < max_nslaves && - (max_slave_rlag == -2 || - (b->backend_server->rlag != -1 && /*< information currently not available */ + (max_slave_rlag == MAX_RLAG_UNDEFINED || + (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && b->backend_server->rlag <= max_slave_rlag)) && (SERVER_IS_SLAVE(b->backend_server) || SERVER_IS_RELAY_SERVER(b->backend_server)) && (master_host != NULL && (b->backend_server != master_host->backend_server))) @@ -2002,7 +2183,7 @@ static bool select_connect_backend_servers( session, b->backend_server->protocol); - if (backend_ref[i].bref_dcb != NULL) + if (backend_ref[i].bref_dcb != NULL) { master_connected = true; /** @@ -2054,7 +2235,8 @@ static bool select_connect_backend_servers( } /* assert with master_host */ ss_dassert(!master_connected || - (master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && SERVER_MASTER)); + (master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && + SERVER_MASTER)); #endif /** @@ -2116,15 +2298,11 @@ static bool select_connect_backend_servers( BACKEND* b = backend_ref[i].bref_backend; if (BREF_IS_IN_USE((&backend_ref[i]))) - { - backend_type_t btype = BACKEND_TYPE(b); - + { LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "Selected %s in \t%s:%d", - (btype == BE_MASTER ? "master" : - (btype == BE_SLAVE ? "slave" : - "unknown node type")), + STRSRVSTATUS(b->backend_server), b->backend_server->name, b->backend_server->port))); } @@ -2943,7 +3121,6 @@ static bool route_session_write( /** Lock router session */ if (!rses_begin_locked_router_action(router_cli_ses)) { - rses_property_done(prop); succp = false; goto return_succp; } @@ -3013,6 +3190,7 @@ return_succp: } #if defined(NOT_USED) + static bool router_option_configured( ROUTER_INSTANCE* router, const char* optionstr, @@ -3189,6 +3367,11 @@ static bool handle_error_reply_client( CHK_DCB(client_dcb); client_dcb->func.write(client_dcb, errmsg); } + else + { + while ((errmsg=gwbuf_consume(errmsg, GWBUF_LENGTH(errmsg))) != NULL) + ; + } succp = false; /** false because new servers aren's selected. */ return succp; @@ -3243,6 +3426,11 @@ static bool handle_error_new_connection( client_dcb->func.write(client_dcb, errmsg); bref_clear_state(bref, BREF_WAITING_RESULT); } + else + { + while ((errmsg=gwbuf_consume(errmsg, GWBUF_LENGTH(errmsg))) != NULL) + ; + } bref_clear_state(bref, BREF_IN_USE); bref_set_state(bref, BREF_CLOSED); /** diff --git a/server/modules/routing/readwritesplit/test/makefile b/server/modules/routing/readwritesplit/test/makefile index 87d22363d..b63030608 100644 --- a/server/modules/routing/readwritesplit/test/makefile +++ b/server/modules/routing/readwritesplit/test/makefile @@ -20,6 +20,7 @@ testall: -$(MAKE) cleantests -$(MAKE) DEBUG=Y buildtests -$(MAKE) runtests + -$(MAKE) -C test_hints testall buildtests: @@ -32,4 +33,4 @@ runtests: @echo "-------------------------------" >> $(TESTLOG) ./rwsplit.sh $(TESTLOG) $(THOST) $(TPORT_RW) $(TMASTER_ID) $(TUSER) $(TPWD) @echo "" >> $(TESTLOG) - @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/modules/routing/readwritesplit/test/test_hints/Makefile b/server/modules/routing/readwritesplit/test/test_hints/Makefile new file mode 100644 index 000000000..f4b2d209d --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/Makefile @@ -0,0 +1,53 @@ +# 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 + +ARGS=6 + +CC=cc +TESTLOG := $(shell pwd)/testrwsplit_hints.log +RET := -1 + +cleantests: + - $(DEL) *.o + - $(DEL) *~ + - $(DEL) *.sql + - $(DEL) *.output + - $(DEL) *.log + +testall: + -$(MAKE) cleantests + -$(MAKE) DEBUG=Y buildtests + -$(MAKE) runtests + +buildtests: + + +runtests: + @echo "" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Test Read/Write split router - hint routing" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo "Running simple tests" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + ./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) simple_tests + @echo "" >> $(TESTLOG) + @echo "Running syntax error tests" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + ./syntax_check.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) error_tests + @echo "" >> $(TESTLOG) + @echo "Running complex tests" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + ./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) complex_tests + @echo "" >> $(TESTLOG) + @echo "Running stack tests" >> $(TESTLOG) + @echo "" >> $(TESTLOG) + ./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) stack_tests + @echo "" >> $(TESTLOG) + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/modules/routing/readwritesplit/test/test_hints/complex_tests b/server/modules/routing/readwritesplit/test/test_hints/complex_tests new file mode 100644 index 000000000..a8ac640f9 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/complex_tests @@ -0,0 +1,48 @@ +select @@server_id; -- maxscale begin route to master:3000 +select @@server_id;:3000 +select @@server_id; -- maxscale route to server server3:3002 +select @@server_id;:3000 +select @@server_id; -- maxscale end: +select @@server_id; -- maxscale named1 prepare route to master: +select @@server_id; -- maxscale named1 begin:3000 +select @@server_id;:3000 +select @@server_id; -- maxscale route to server server3:3002 +select @@server_id;:3000 +select @@server_id; -- maxscale end: +select @@server_id; -- maxscale shorthand1 begin route to server server2:3001 +select @@server_id;:3001 +select @@server_id; -- maxscale route to server server3:3002 +select @@server_id;:3001 +select @@server_id; -- maxscale end: +select @@server_id; # maxscale begin route to master:3000 +select @@server_id;:3000 +select @@server_id; # maxscale route to server server3:3002 +select @@server_id;:3000 +select @@server_id; # maxscale end: +select @@server_id; # maxscale named2 prepare route to master: +select @@server_id; # maxscale named2 begin:3000 +select @@server_id;:3000 +select @@server_id; # maxscale route to server server3:3002 +select @@server_id;:3000 +select @@server_id; # maxscale end: +select @@server_id; # maxscale shorthand2 begin route to server server2:3001 +select @@server_id;:3001 +select @@server_id; # maxscale route to server server3:3002 +select @@server_id;:3001 +select @@server_id; # maxscale end: +select @@server_id/* maxscale begin route to master */;:3000 +select @@server_id;:3000 +select @@server_id/* maxscale route to server server3 */;:3002 +select @@server_id;:3000 +select @@server_id/* maxscale end */;: +select @@server_id/* maxscale named3 prepare route to master */;: +select @@server_id/* maxscale named3 begin */;:3000 +select @@server_id;:3000 +select @@server_id/* maxscale route to server server3 */;:3002 +select @@server_id;:3000 +select @@server_id/* maxscale end */;: +select @@server_id/* maxscale shorthand3 begin route to server server2 */; :3001 +select @@server_id;:3001 +select @@server_id/* maxscale route to server server3 */;:3002 +select @@server_id;:3001 +select @@server_id/* maxscale end */;: diff --git a/server/modules/routing/readwritesplit/test/test_hints/error_tests b/server/modules/routing/readwritesplit/test/test_hints/error_tests new file mode 100644 index 000000000..2decad100 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/error_tests @@ -0,0 +1,39 @@ +select @@server_id; -- maxscalemaxscale route to master: +select @@server_id; -- master to route maxscale: +select @@server_id; -- route to master: +select @@server_id; -- maxscale to master: +select @@server_id; -- maxscale route master: +select @@server_id; -- maxscale route to: +select @@server_id; -- maxscale begin master: +select @@server_id; -- maxscale master route to master: +select @@server_id; -- maxscale route to maxscale route to master: +select @@server_id; -- maxscale maxscale route to master: +select @@server_id; -- maxscale route to to server =): +select @@server_id; -- maxscale route to maxscale server server1: +select @@server_id; -- maxscale route to server1: +select @@server_id; # maxscalemaxscale route to master: +select @@server_id; # master to route maxscale: +select @@server_id; # route to master: +select @@server_id; # maxscale to master: +select @@server_id; # maxscale route master: +select @@server_id; # maxscale route to: +select @@server_id; # maxscale begin master: +select @@server_id; # maxscale master route to master: +select @@server_id; # maxscale route to maxscale route to master: +select @@server_id; # maxscale maxscale route to master: +select @@server_id; # maxscale route to to server =): +select @@server_id; # maxscale route to maxscale server server1: +select @@server_id; # maxscale route to server1: +select @@server_id; /* maxscalemaxscale route to master */;: +select @@server_id; /* master to route maxscale */;: +select @@server_id; /* route to master */;: +select @@server_id; /* maxscale to master */;: +select @@server_id; /* maxscale route master */;: +select @@server_id; /* maxscale route to */;: +select @@server_id; /* maxscale begin master */;: +select @@server_id; /* maxscale master route to master */;: +select @@server_id; /* maxscale route to maxscale route to master */;: +select @@server_id; /* maxscale maxscale route to master */;: +select @@server_id; /* maxscale route to to server =) */;: +select @@server_id; /* maxscale route to maxscale server server1 */;: +select @@server_id; /* maxscale route to server1 */;: \ No newline at end of file diff --git a/server/modules/routing/readwritesplit/test/test_hints/hints.txt b/server/modules/routing/readwritesplit/test/test_hints/hints.txt new file mode 100644 index 000000000..34196ca36 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/hints.txt @@ -0,0 +1,9 @@ +-- maxscale route to master:3000 +-- maxscale route to server server1:3000 +-- maxscale route to server server2:3001 +-- maxscale route to server server3:3002 +-- maxscale route to server server4:3003 +-- maxscale max_slave_replication_lag = 100: +-- maxscale max_slave_replication_lag= 100: +-- maxscale max_slave_replication_lag =100: +-- maxscale max_slave_replication_lag=100: diff --git a/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh b/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh new file mode 100755 index 000000000..cdbfb335b --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh @@ -0,0 +1,65 @@ +#!/bin/bash +NARGS=7 +TLOG=$1 +THOST=$2 +TPORT=$3 +TMASTER_ID=$4 +TUSER=$5 +TPWD=$6 +TESTINPUT=$7 + +if [ $# != $NARGS ] ; +then +echo"" +echo "Wrong number of arguments, gave "$#" but "$NARGS" is required" +echo "" +echo "Usage :" +echo " rwsplit_hints.sh " +echo "" +exit 1 +fi + + +RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --disable-reconnect\ --silent\ --comment +i=0 + +while read -r LINE +do +TINPUT[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[1]}'` +TRETVAL[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[2]}'` +echo "${TINPUT[i]}" >> $TESTINPUT.sql +i=$((i+1)) +done < $TESTINPUT + +`$RUNCMD < $TESTINPUT.sql > $TESTINPUT.output` + +x=0 +crash=1 +all_passed=1 + +while read -r TOUTPUT +do +crash=0 +if [ "$TOUTPUT" != "${TRETVAL[x]}" -a "${TRETVAL[x]}" != "" ] +then + all_passed=0 + echo "$TESTINPUT:$((x + 1)): ${TINPUT[x]} FAILED, return value $TOUTPUT when ${TRETVAL[x]} was expected">>$TLOG; +fi +x=$((x+1)) +done < $TESTINPUT.output + +if [ $crash -eq 1 ] +then + all_passed=0 + for ((v=0;v<$i;v++)) + do + echo "${TINPUT[v]} FAILED, nothing was returned">>$TLOG; + done +fi + +if [ $all_passed -eq 1 ] +then + echo "Test set: PASSED">>$TLOG; +else + echo "Test set: FAILED">>$TLOG; +fi diff --git a/server/modules/routing/readwritesplit/test/test_hints/simple_tests b/server/modules/routing/readwritesplit/test/test_hints/simple_tests new file mode 100644 index 000000000..67dd1f57e --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/simple_tests @@ -0,0 +1,18 @@ +select @@server_id; -- maxscale route to master:3000 +select @@server_id; -- maxscale route to slave: +select @@server_id; -- maxscale route to server server1:3000 +select @@server_id; -- maxscale route to server server2:3001 +select @@server_id; -- maxscale route to server server3:3002 +select @@server_id; -- maxscale route to server server4:3003 +select @@server_id; # maxscale route to master:3000 +select @@server_id; # maxscale route to slave: +select @@server_id; # maxscale route to server server1:3000 +select @@server_id; # maxscale route to server server2:3001 +select @@server_id; # maxscale route to server server3:3002 +select @@server_id; # maxscale route to server server4:3003 +select @@server_id/* maxscale route to master */;:3000 +select @@server_id/* maxscale route to slave */;: +select @@server_id/* maxscale route to server server1 */;:3000 +select @@server_id/* maxscale route to server server2 */;:3001 +select @@server_id/* maxscale route to server server3 */;:3002 +select @@server_id/* maxscale route to server server4 */;:3003 diff --git a/server/modules/routing/readwritesplit/test/test_hints/stack_tests b/server/modules/routing/readwritesplit/test/test_hints/stack_tests new file mode 100644 index 000000000..0d4d51e05 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/stack_tests @@ -0,0 +1,50 @@ +select @@server_id; -- maxscale stack_named1 prepare route to server server1: +select @@server_id; -- maxscale stack_named2 prepare route to server server2: +select @@server_id; -- maxscale stack_named3 prepare route to server server3: +select @@server_id; -- maxscale stack_named4 prepare route to server server4: +select @@server_id; -- maxscale stack_named1 begin:3000 +select @@server_id;:3000 +select @@server_id; -- maxscale stack_named2 begin:3001 +select @@server_id;:3001 +select @@server_id; -- maxscale stack_named3 begin:3002 +select @@server_id;:3002 +select @@server_id; -- maxscale stack_named4 begin:3003 +select @@server_id;:3003 +select @@server_id; -- maxscale stack_shorthand1 begin route to server server1:3000 +select @@server_id;:3000 +select @@server_id; -- maxscale stack_shorthand2 begin route to server server2:3001 +select @@server_id;:3001 +select @@server_id; -- maxscale stack_shorthand3 begin route to server server3:3002 +select @@server_id;:3002 +select @@server_id; -- maxscale stack_shorthand4 begin route to server server4:3003 +select @@server_id;:3003 +select @@server_id; -- maxscale end:3002 +select @@server_id;:3002 +select @@server_id; -- maxscale end:3001 +select @@server_id;:3001 +select @@server_id; -- maxscale end:3000 +select @@server_id;:3000 +select @@server_id; -- maxscale end:3003 +select @@server_id;:3003 +select @@server_id; -- maxscale end:3002 +select @@server_id;:3002 +select @@server_id; -- maxscale end:3001 +select @@server_id;:3001 +select @@server_id; -- maxscale end:3000 +select @@server_id; -- maxscale end: +select @@server_id; -- maxscale stack_shorthand1 begin:3000 +select @@server_id; -- maxscale stack_shorthand2 begin:3001 +select @@server_id; -- maxscale stack_shorthand3 begin:3002 +select @@server_id; -- maxscale stack_shorthand4 begin:3003 +select @@server_id; -- maxscale stack_named1 begin:3000 +select @@server_id; -- maxscale stack_named2 begin:3001 +select @@server_id; -- maxscale stack_named3 begin:3002 +select @@server_id; -- maxscale stack_named4 begin:3003 +select @@server_id; -- maxscale end:3002 +select @@server_id; -- maxscale end:3001 +select @@server_id; -- maxscale end:3000 +select @@server_id; -- maxscale end:3003 +select @@server_id; -- maxscale end:3002 +select @@server_id; -- maxscale end:3001 +select @@server_id; -- maxscale end:3000 +select @@server_id; -- maxscale end: \ No newline at end of file diff --git a/server/modules/routing/readwritesplit/test/test_hints/syntax_check.sh b/server/modules/routing/readwritesplit/test/test_hints/syntax_check.sh new file mode 100755 index 000000000..8c09d7348 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_hints/syntax_check.sh @@ -0,0 +1,33 @@ +#! /bin/bash +NARGS=7 +TLOG=$1 +THOST=$2 +TPORT=$3 +TMASTER_ID=$4 +TUSER=$5 +TPWD=$6 +TESTINPUT=$7 + +if [ $# != $NARGS ] ; +then +echo"" +echo "Wrong number of arguments, gave "$#" but "$NARGS" is required" +echo "" +echo "Usage :" +echo " syntax_check.sh " + echo "" +exit 1 +fi + +./rwsplit_hints.sh dummy.log $THOST $TPORT $TMASTER_ID $TUSER $TPWD $TESTINPUT + +exp_count=`cat error_tests|wc -l` +err_count=`tac ../../../../../test/log/skygw_err* | gawk '/enabled/{if(!bg){ bg = 1} else exit 0}{if(bg) print}'|grep -c 'Hint ignored'` + +if [[ $err_count -ge $exp_count ]] +then + echo "Test set: PASSED">>$TLOG; +else + echo "Expected $exp_count ignored hints in the error log but found $err_count instead">>$TLOG + echo "Test set: FAILED">>$TLOG; +fi diff --git a/server/modules/routing/webserver.c b/server/modules/routing/webserver.c new file mode 100644 index 000000000..28102dfd4 --- /dev/null +++ b/server/modules/routing/webserver.c @@ -0,0 +1,616 @@ +/* + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * The instance structure for this router. + */ +typedef struct { + SERVICE *service; +} WEB_INSTANCE; + +/** + * The session structure for this router. + */ +typedef struct { + SESSION *session; +} WEB_SESSION; + +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); +static void freeSession(ROUTER *instance, void *session); +static int routeQuery(ROUTER *instance, void *session, GWBUF *queue); +static void diagnostic(ROUTER *instance, DCB *dcb); +static uint8_t getCapabilities (ROUTER* inst, void* router_session); + + +static ROUTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + routeQuery, + diagnostic, + NULL, + NULL, + getCapabilities +}; + + +static void send_index(WEB_SESSION *); +static void send_css(WEB_SESSION *); +static void send_menu(WEB_SESSION *); +static void send_blank(WEB_SESSION *); +static void send_title(WEB_SESSION *); +static void send_frame1(WEB_SESSION *); +static void send_services(WEB_SESSION *); +static void send_sessions(WEB_SESSION *); +static void send_servers(WEB_SESSION *); +static void send_monitors(WEB_SESSION *); +static void respond_error(WEB_SESSION *, int, char *); + +/** + * A map of URL to function that implements the URL + */ +static struct { + char *page; /* URL */ + void (*fcn)(WEB_SESSION *); /* Function to call */ +} pages[] = { + { "index.html", send_index }, + { "services.html", send_services }, + { "menu.html", send_menu }, + { "sessions.html", send_sessions }, + { "blank.html", send_blank }, + { "title.html", send_title }, + { "frame1.html", send_frame1 }, + { "servers.html", send_servers }, + { "monitors.html", send_monitors }, + { "styles.css", send_css }, + { NULL, NULL } +}; + +/** + * 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 + */ +ROUTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the router for a particular service + * within the gateway. + * + * @param service The service this router is being create for + * @param options The options for this query router + * + * @return The instance data for this new instance + */ +static ROUTER * +createInstance(SERVICE *service, char **options) +{ +WEB_INSTANCE *inst; + + if ((inst = (WEB_INSTANCE *)malloc(sizeof(WEB_INSTANCE))) == NULL) + return NULL; + + inst->service = service; + return (ROUTER *)inst; +} + +/** + * Associate a new session with this instance of the router. + * + * @param instance The router instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(ROUTER *instance, SESSION *session) +{ +WEB_SESSION *wsession; + + if ((wsession = (WEB_SESSION *)malloc(sizeof(WEB_SESSION))) == NULL) + return NULL; + + wsession->session = session; + return wsession; +} + +/** + * Close a session with the router, this is the mechanism + * by which a router may cleanup data structure etc. + * + * @param instance The router instance data + * @param session The session being closed + */ +static void +closeSession(ROUTER *instance, void *session) +{ + free(session); +} + +static void freeSession( + ROUTER* router_instance, + void* router_client_session) +{ + return; +} + +static int +routeQuery(ROUTER *instance, void *session, GWBUF *queue) +{ +WEB_SESSION *wsession = (WEB_SESSION *)session; +char *ptr; +int i, found = 0; +char *url; + + if ((url = gwbuf_get_property(queue, "URL")) == NULL) + { + respond_error(wsession, 404, "No URL available"); + } + + ptr = strrchr(url, '/'); + if (ptr) + ptr++; + else + ptr = url; + for (i = 0; pages[i].page; i++) + { + if (!strcmp(ptr, pages[i].page)) + { + (pages[i].fcn)(wsession); + found = 1; + } + } + if (!found) + respond_error(wsession, 404, "Unrecognised URL received"); + gwbuf_free(queue); + return 0; +} + +/** + * Diagnostics routine + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static void +diagnostic(ROUTER *instance, DCB *dcb) +{ +} + +/** + * Return the router capabilities bitmask + * + * @param inst The router instance + * @param router_session The router session + * @return Router capabilities bitmask + */ +static uint8_t +getCapabilities(ROUTER *inst, void *router_session) +{ + return 0; +} + +/** + * The HTML of the index page. + */ +static char *index_page = +"" +"" +"MaxScale" +"" +"" +"" +"" +"" +""; + +/** + * The HTML of the title page + */ +static char *title_page = +"" +"" +"MaxScale" +"" +"

MaxScale - Status View

" +""; + +/** + * HTML of the main frames, those below the title frame + */ +static char *frame1_page = +"" +"" +"" +"" +"" +""; + +/** + * The menu page HTML + */ +static char *menu_page = +"" +"" +"" +"

Options

" +"

"; + +/** + * A blank page, contents of the display area when we first connect + */ +static char *blank_page = " "; + +/** + * The CSS used for every "page" + */ +static char *css = +"table, td, th { border: 1px solid blue; }\n" +"th { background-color: blue; color: white; padding: 5px }\n" +"td { padding: 5px; }\n" +"table { border-collapse: collapse; }\n" +"a:link { color: #0000FF; }\n" +"a:visted { color: #0000FF; }\n" +"a:hover { color: #FF0000; }\n" +"a:active { color: #0000FF; }\n" +"h1 { color: blue; font-family: serif }\n" +"h2 { color: blue; font-family: serif }\n" +"p { font-family: serif }\n" +"li { font-family: serif }\n"; + +/** + * Send the standard HTTP headers for an HTML file + */ +static void +send_html_header(DCB *dcb) +{ +char date[64] = ""; +const char *fmt = "%a, %d %b %Y %H:%M:%S GMT"; + + time_t httpd_current_time = time(NULL); + + strftime(date, sizeof(date), fmt, localtime(&httpd_current_time)); + + dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: text/html\r\n", date, "MaxScale"); + + dcb_printf(dcb, "\r\n"); +} + +/** + * Send a static HTML page + * + * @param dcb The DCB of the connection to the browser + * @param html The HTML to send + */ +static void +send_static_html(DCB *dcb, char *html) +{ + dcb_printf(dcb, html); +} + +/** + * Send the index page + * + * @param session The router session + */ +static void +send_index(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, index_page); + dcb_close(dcb); +} + +/** + * Send the CSS + * + * @param session The router session + */ +static void +send_css(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, css); + dcb_close(dcb); +} + +/** + * Send the title page + * + * @param session The router session + */ +static void +send_title(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, title_page); + dcb_close(dcb); +} + +/** + * Send the frame1 page + * + * @param session The router session + */ +static void +send_frame1(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, frame1_page); + dcb_close(dcb); +} + +/** + * Send the menu page + * + * @param session The router session + */ +static void +send_menu(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, menu_page); + dcb_close(dcb); +} + +/** + * Send a blank page + * + * @param session The router session + */ +static void +send_blank(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + send_static_html(dcb, blank_page); + dcb_close(dcb); +} + +/** + * Write a table row for a service. This is called using the service + * iterator function + * + * @param service The service to display + * @param dcb The DCB to print the HTML to + */ +static void +service_row(SERVICE *service, DCB *dcb) +{ + dcb_printf(dcb, "%s%s%d%d\n", + service->name, service->routerModule, + service->stats.n_current, service->stats.n_sessions); +} + +/** + * Send the services page. This produces a table by means of the + * serviceIterate call. + * + * @param session The router session + */ +static void +send_services(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + dcb_printf(dcb, ""); + dcb_printf(dcb, ""); + dcb_printf(dcb, "

Services

"); + dcb_printf(dcb, "\n"); + serviceIterate(service_row, dcb); + dcb_printf(dcb, "
NameRouter"); + dcb_printf(dcb, "Current SessionsTotal Sessions
\n"); + dcb_close(dcb); +} + +/** + * Write a session row for a session. this is called using the session + * iterator function + * + * @param session The session to display + * @param dcb The DCB to send the HTML to + */ +static void +session_row(SESSION *session, DCB *dcb) +{ + dcb_printf(dcb, "%-16p%s%s%s\n", + session, ((session->client && session->client->remote) + ? session->client->remote : ""), + (session->service && session->service->name + ? session->service->name : ""), + session_state(session->state)); +} + +/** + * Send the sessions page. The produces a table of all the current sessions + * display. It makes use of the sessionIterate call to call the function + * session_row() with each session. + * + * @param session The router session + */ +static void +send_sessions(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + dcb_printf(dcb, ""); + dcb_printf(dcb, ""); + dcb_printf(dcb, "

Sessions

"); + dcb_printf(dcb, "\n"); + sessionIterate(session_row, dcb); + dcb_printf(dcb, "
SessionClient"); + dcb_printf(dcb, "ServiceState
\n"); + dcb_close(dcb); +} + +/** + * Display a table row for a particular server. This is called via the + * serverIterate call in send_servers. + * + * @param server The server to print + * @param dcb The DCB to send the HTML to + */ +static void +server_row(SERVER *server, DCB *dcb) +{ + dcb_printf(dcb, "%s%s%d%s%d\n", + server->unique_name, server->name, server->port, + server_status(server), server->stats.n_current); +} + +/** + * Send the servers page + * + * @param session The router session + */ +static void +send_servers(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + dcb_printf(dcb, ""); + dcb_printf(dcb, ""); + dcb_printf(dcb, "

Servers

"); + dcb_printf(dcb, "\n"); + serverIterate(server_row, dcb); + dcb_printf(dcb, "
ServerAddress"); + dcb_printf(dcb, "PortStateConnections
\n"); + dcb_close(dcb); +} + +/** + * Print a table row for the monitors table + * + * @param monitor The monitor to print + * @param dcb The DCB to print to + */ +static void +monitor_row(MONITOR *monitor, DCB *dcb) +{ + dcb_printf(dcb, "%s%s\n", + monitor->name, monitor->state & MONITOR_STATE_RUNNING + ? "Running" : "Stopped"); +} + +/** + * Send the monitors page. This iterates on all the monitors and send + * the rows via the monitor_monitor. + * + * @param session The router session + */ +static void +send_monitors(WEB_SESSION *session) +{ +DCB *dcb = session->session->client; + + send_html_header(dcb); + dcb_printf(dcb, ""); + dcb_printf(dcb, ""); + dcb_printf(dcb, "

Monitors

"); + dcb_printf(dcb, "\n"); + monitorIterate(monitor_row, dcb); + dcb_printf(dcb, "
MonitorState
\n"); + dcb_close(dcb); +} + +/** + * Respond with an HTTP error + * + * @param session The router session + * @param err The HTTP error code to send + * @param msg The message to print + */ +static void +respond_error(WEB_SESSION *session, int err, char *msg) +{ +DCB *dcb = session->session->client; + + dcb_printf(dcb, "HTTP/1.1 %d %s\n", err, msg); + dcb_printf(dcb, "Content-Type: text/html\n"); + dcb_printf(dcb, "\n"); + dcb_printf(dcb, "\n"); + dcb_printf(dcb, "MaxScale webserver plugin unable to satisfy request.\n"); + dcb_printf(dcb, "

Code: %d, %s\n", err, msg); + dcb_printf(dcb, ""); + dcb_close(dcb); +} diff --git a/server/test/MaxScale_test.cnf b/server/test/MaxScale_test.cnf index 89f65b9a0..9fff8fa08 100644 --- a/server/test/MaxScale_test.cnf +++ b/server/test/MaxScale_test.cnf @@ -20,11 +20,13 @@ threads=1 # user = # passwd= +# monitor_interval= [MySQL Monitor] type=monitor module=mysqlmon -servers=server1,server2,server3 +servers=server1,server2,server3,server4 user=maxuser passwd=maxpwd @@ -36,25 +38,42 @@ passwd=maxpwd # servers=,,... # user= # passwd= +# enable_root_user=<0 or 1, default is 0> +# version_string= # # Valid router modules currently are: # readwritesplit, readconnroute and debugcli + [RW Split Router] type=service router=readwritesplit -servers=server1,server2,server3 +servers=server1,server2,server3,server4 +max_slave_connections=90% user=maxuser passwd=maxpwd + +[RW Split Hint Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +filters=Hint + + [Read Connection Router] type=service router=readconnroute -router_options=slave -servers=server1,server2,server3 +router_options=master +servers=server1 user=maxuser passwd=maxpwd + [HTTPD Router] type=service router=testroute @@ -64,6 +83,12 @@ servers=server1,server2,server3 type=service router=debugcli + +[Hint] +type=filter +module=hintfilter + + # Listener definitions for the services # # Valid options are: @@ -71,6 +96,8 @@ router=debugcli # service= # protocol= # port= +# address=

+# socket= [RW Split Listener] type=listener @@ -78,17 +105,25 @@ service=RW Split Router protocol=MySQLClient port=4006 +[RW Split Hint Listener] +type=listener +service=RW Split Hint Router +protocol=MySQLClient +port=4009 + [Read Connection Listener] type=listener service=Read Connection Router protocol=MySQLClient port=4008 +#socket=/tmp/readconn.sock [Debug Listener] type=listener service=Debug Interface protocol=telnetd port=4442 +#address=127.0.0.1 [HTTPD Listener] type=listener @@ -115,3 +150,9 @@ type=server address=127.0.0.1 port=3002 protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3003 +protocol=MySQLBackend diff --git a/test.inc b/test.inc index b020368d8..7c5e5c571 100644 --- a/test.inc +++ b/test.inc @@ -19,6 +19,11 @@ TPORT_RCONN := # TPORT_RW := # +# port of read/write split router module with hints, for example: +# TPORT_RW_HINT := 4009 +# +TPORT_RW_HINT := +# # username of MaxScale user, for example: # TUSER := maxuser # diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 9ffc5e7e2..352d736f9 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -134,7 +134,8 @@ typedef enum skygw_chk_t { ((t) == QUERY_TYPE_SESSION_WRITE ? "QUERY_TYPE_SESSION_WRITE" : \ ((t) == QUERY_TYPE_UNKNOWN ? "QUERY_TYPE_UNKNOWN" : \ ((t) == QUERY_TYPE_LOCAL_READ ? "QUERY_TYPE_LOCAL_READ" : \ - "Unknown query type"))))) + ((t) == QUERY_TYPE_EXEC_STMT ? "QUERY_TYPE_EXEC_STMT" : \ + "Unknown query type")))))) #define STRLOGID(i) ((i) == LOGFILE_TRACE ? "LOGFILE_TRACE" : \ ((i) == LOGFILE_MESSAGE ? "LOGFILE_MESSAGE" : \ @@ -232,11 +233,13 @@ typedef enum skygw_chk_t { ((c) == LEAST_BEHIND_MASTER ? "LEAST_BEHIND_MASTER" : \ ((c) == LEAST_CURRENT_OPERATIONS ? "LEAST_CURRENT_OPERATIONS" : "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" : \ +#define STRSRVSTATUS(s) (SERVER_IS_MASTER(s) ? "RUNNING MASTER" : \ + (SERVER_IS_SLAVE(s) ? "RUNNING SLAVE" : \ + (SERVER_IS_JOINED(s) ? "RUNNING JOINED" : \ + (SERVER_IS_NDB(s) ? "RUNNING NDB" : \ ((SERVER_IS_RUNNING(s) && SERVER_IN_MAINT(s)) ? "RUNNING MAINTENANCE" : \ - (SERVER_IS_RUNNING(s) ? "RUNNING (only)" : "NO STATUS"))))) + (SERVER_IS_RELAY_SERVER(s) ? "RUNNING RELAY" : \ + (SERVER_IS_RUNNING(s) ? "RUNNING (only)" : "NO STATUS"))))))) #define CHK_MLIST(l) { \ ss_info_dassert((l->mlist_chk_top == CHK_NUM_MLIST && \