diff --git a/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf b/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf new file mode 100644 index 000000000..9ccd75b42 Binary files /dev/null and b/Documentation/MaxAdmin The MaxScale Administration And Monitoring Client.pdf differ diff --git a/Documentation/MaxScale 1.0beta Release Notes.pdf b/Documentation/MaxScale 1.0beta Release Notes.pdf new file mode 100644 index 000000000..165a9a1b5 Binary files /dev/null and b/Documentation/MaxScale 1.0beta Release Notes.pdf differ diff --git a/Documentation/MaxScale Configuration And Usage Scenarios.pdf b/Documentation/MaxScale Configuration And Usage Scenarios.pdf index ec8a813f8..7e588e2c9 100644 Binary files a/Documentation/MaxScale Configuration And Usage Scenarios.pdf and b/Documentation/MaxScale Configuration And Usage Scenarios.pdf differ diff --git a/Documentation/MaxScale HA with Corosync and Pacemaker.pdf b/Documentation/MaxScale HA with Corosync and Pacemaker.pdf new file mode 100644 index 000000000..aa0daac7c Binary files /dev/null and b/Documentation/MaxScale HA with Corosync and Pacemaker.pdf differ diff --git a/Documentation/filters/QLA Filter.pdf b/Documentation/filters/QLA Filter.pdf new file mode 100644 index 000000000..94818c426 Binary files /dev/null and b/Documentation/filters/QLA Filter.pdf differ diff --git a/Documentation/filters/Regex Filter.pdf b/Documentation/filters/Regex Filter.pdf new file mode 100644 index 000000000..f45fef043 Binary files /dev/null and b/Documentation/filters/Regex Filter.pdf differ diff --git a/Documentation/filters/Tee Filter.pdf b/Documentation/filters/Tee Filter.pdf new file mode 100644 index 000000000..f8de502ca Binary files /dev/null and b/Documentation/filters/Tee Filter.pdf differ diff --git a/Documentation/filters/Top Filter.pdf b/Documentation/filters/Top Filter.pdf new file mode 100644 index 000000000..a7cb2061d Binary files /dev/null and b/Documentation/filters/Top Filter.pdf differ diff --git a/Documentation/MaxScale 0.7 Release Notes.pdf b/Documentation/history/MaxScale 0.7 Release Notes.pdf similarity index 100% rename from Documentation/MaxScale 0.7 Release Notes.pdf rename to Documentation/history/MaxScale 0.7 Release Notes.pdf diff --git a/Makefile b/Makefile index 67eb0d159..328fa9b7a 100644 --- a/Makefile +++ b/Makefile @@ -39,21 +39,25 @@ all: (cd log_manager; make) (cd query_classifier; make) (cd server; make) + (cd client; make) clean: (cd log_manager; make clean) (cd query_classifier; make clean) (cd server; make clean) + (cd client; touch depend.mk; make clean) depend: (cd log_manager; make depend) (cd query_classifier; make depend) (cd server; make depend) + (cd client; touch depend.mk; make depend) install: (cd server; make DEST=$(DEST) install) (cd log_manager; make DEST=$(DEST) install) (cd query_classifier; make DEST=$(DEST) install) + (cd client; make DEST=$(DEST) install) cleantests: $(MAKE) -C test cleantests diff --git a/VERSION b/VERSION index faef31a43..896b9bc9d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0 +1.0.0-beta diff --git a/build_gateway.inc b/build_gateway.inc index 83ffaf762..56565c1f6 100644 --- a/build_gateway.inc +++ b/build_gateway.inc @@ -51,3 +51,13 @@ endif # Set path to MySQL errors file # ERRMSG := $(HOME)/usr/share/mysql + +# +# Build a binary that produces profile data +# +PROFILE := N + +# +# Build a binary that produces code coverage data +# +GCOV := N diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 000000000..a57518dc2 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,79 @@ +# 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 +# +# Revision History +# Date Who Description +# 13/06/14 Mark Riddoch Initial implementation of MaxScale +# client program +# 18/06/14 Mark Riddoch Addition of conditional for histedit + +ifeq ($(wildcard /usr/include/histedit.h), ) +HISTLIB= +HISTFLAG= +else +HISTLIB=-ledit +HISTFLAG=-DHISTORY +endif + +CC=cc + +CFLAGS=-c -Wall -g $(HISTFLAG) + +SRCS= maxadmin.c + +HDRS= + +OBJ=$(SRCS:.c=.o) + +LIBS=$(HISTLIB) + +all: maxadmin + +cleantests: + $(MAKE) -C test cleantests + +buildtests: + $(MAKE) -C test buildtests + +runtests: + $(MAKE) -C test runtests + +testall: + $(MAKE) -C test testall + +maxadmin: $(OBJ) + $(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@ + + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + + +clean: + rm -f $(OBJ) maxadmin + +tags: + ctags $(SRCS) $(HDRS) + +depend: + @rm -f depend.mk + cc -M $(CFLAGS) $(SRCS) > depend.mk + +install: maxadmin + @mkdir -p $(DEST)/bin + install -D maxadmin $(DEST)/bin + +include depend.mk diff --git a/client/maxadmin.c b/client/maxadmin.c new file mode 100644 index 000000000..5710cc35a --- /dev/null +++ b/client/maxadmin.c @@ -0,0 +1,554 @@ +/* + * 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 maxadmin.c - The MaxScale administration client + * + * @verbatim + * Revision History + * + * Date Who Description + * 13/06/14 Mark Riddoch Initial implementation + * 15/06/14 Mark Riddoch Addition of source command + * 26/06/14 Mark Riddoch Fix issue with final OK split across + * multiple reads + * + * @endverbatim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HISTORY +#include +#endif + +static int connectMaxScale(char *hostname, char *port); +static int setipaddress(struct in_addr *a, char *p); +static int authMaxScale(int so, char *user, char *password); +static int sendCommand(int so, char *cmd); +static void DoSource(int so, char *cmd); +static void DoUsage(); + +#ifdef HISTORY +static char * +prompt(EditLine *el __attribute__((__unused__))) +{ + static char prompt[] = "MaxScale> "; + + return prompt; +} +#endif + +/** + * The main for the maxadmin client + * + * @param argc Number of arguments + * @param argv The command line arguments + */ +int +main(int argc, char **argv) +{ +int i, num, rv, fatal = 0; +#ifdef HISTORY +char *buf; +EditLine *el = NULL; +Tokenizer *tok; +History *hist; +HistEvent ev; +const LineInfo *li; +#else +char buf[1024]; +#endif +char *hostname = "localhost"; +char *port = "6603"; +char *user = "admin"; +char *passwd = NULL; +int so, cmdlen; +char *cmd; +int argno = 0; + + cmd = malloc(1); + *cmd = 0; + cmdlen = 1; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'u': /* User */ + if (argv[i][2]) + user = &(argv[i][2]); + else if (i + 1 < argc) + user = argv[++i]; + else + { + fprintf(stderr, "Missing username" + "in -u option.\n"); + fatal = 1; + } + break; + case 'p': /* Password */ + if (argv[i][2]) + passwd = &(argv[i][2]); + else if (i + 1 < argc) + passwd = argv[++i]; + else + { + fprintf(stderr, "Missing password " + "in -p option.\n"); + fatal = 1; + } + break; + case 'h': /* hostname */ + if (argv[i][2]) + hostname = &(argv[i][2]); + else if (i + 1 < argc) + hostname = argv[++i]; + else + { + fprintf(stderr, "Missing hostname value " + "in -h option.\n"); + fatal = 1; + } + break; + case 'P': /* Port */ + if (argv[i][2]) + port = &(argv[i][2]); + else if (i + 1 < argc) + port = argv[++i]; + else + { + fprintf(stderr, "Missing Port value " + "in -P option.\n"); + fatal = 1; + } + break; + case '-': + { + char *word; + + word = &argv[i][2]; + if (strcmp(word, "help") == 0) + { + DoUsage(); + exit(0); + } + break; + } + } + } + else + { + /* Arguments after the second argument are quoted + * to allow for quoted names on the command line + * to be passed on in quotes. + */ + if (argno++ > 1) + { + cmdlen += strlen(argv[i]) + 3; + cmd = realloc(cmd, cmdlen); + strcat(cmd, "\""); + strcat(cmd, argv[i]); + strcat(cmd, "\" "); + } + else + { + cmdlen += strlen(argv[i]) + 1; + cmd = realloc(cmd, cmdlen); + strcat(cmd, argv[i]); + strcat(cmd, " "); + } + } + } + + if (fatal) + exit(1); + + if (passwd == NULL) + { + struct termios tty_attr; + tcflag_t c_lflag; + + if (tcgetattr(STDIN_FILENO, &tty_attr) < 0) + return -1; + + c_lflag = tty_attr.c_lflag; + tty_attr.c_lflag &= ~ICANON; + tty_attr.c_lflag &= ~ECHO; + + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) < 0) + return -1; + + printf("Password: "); + passwd = malloc(80); + fgets(passwd, 80, stdin); + + tty_attr.c_lflag = c_lflag; + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) < 0) + return -1; + i = strlen(passwd); + if (i > 1) + passwd[i - 1] = '\0'; + printf("\n"); + } + + if ((so = connectMaxScale(hostname, port)) == -1) + exit(1); + if (!authMaxScale(so, user, passwd)) + { + fprintf(stderr, "Failed to connect to MaxScale. " + "Incorrect username or password.\n"); + exit(1); + } + + if (cmdlen > 1) + { + cmd[cmdlen - 2] = '\0'; /* Remove trailing space */ + if (access(cmd, R_OK) == 0) + DoSource(so, cmd); + else + sendCommand(so, cmd); + exit(0); + } + + (void) setlocale(LC_CTYPE, ""); +#ifdef HISTORY + hist = history_init(); /* Init the builtin history */ + /* Remember 100 events */ + history(hist, &ev, H_SETSIZE, 100); + + tok = tok_init(NULL); /* Initialize the tokenizer */ + + /* Initialize editline */ + el = el_init(*argv, stdin, stdout, stderr); + + el_set(el, EL_EDITOR, "vi"); /* Default editor is vi */ + el_set(el, EL_SIGNAL, 1); /* Handle signals gracefully */ + el_set(el, EL_PROMPT, prompt);/* Set the prompt function */ + + /* Tell editline to use this history interface */ + el_set(el, EL_HIST, history, hist); + + /* + * Bind j, k in vi command mode to previous and next line, instead + * of previous and next history. + */ + el_set(el, EL_BIND, "-a", "k", "ed-prev-line", NULL); + el_set(el, EL_BIND, "-a", "j", "ed-next-line", NULL); + + /* + * Source the user's defaults file. + */ + el_source(el, NULL); + + while ((buf = el_gets(el, &num)) != NULL && num != 0) + { +#else + while (printf("MaxScale> ") && fgets(buf, 1024, stdin) != NULL) + { + num = strlen(buf); +#endif + /* Strip trailing \n\r */ + for (i = num - 1; buf[i] == '\r' || buf[i] == '\n'; i--) + buf[i] = 0; + +#ifdef HISTORY + li = el_line(el); + history(hist, &ev, H_ENTER, buf); +#endif + + if (!strcasecmp(buf, "quit")) + { + break; + } + else if (!strcasecmp(buf, "history")) + { +#ifdef HISTORY + for (rv = history(hist, &ev, H_LAST); rv != -1; + rv = history(hist, &ev, H_PREV)) + fprintf(stdout, "%4d %s\n", + ev.num, ev.str); +#else + fprintf(stderr, "History not supported in this version.\n"); +#endif + } + else if (!strncasecmp(buf, "source", 6)) + { + char *ptr; + + /* Find the filename */ + ptr = &buf[strlen("source")]; + while (*ptr && isspace(*ptr)) + ptr++; + + DoSource(so, ptr); + } + else if (*buf) + { + sendCommand(so, buf); + } + } + +#ifdef HISTORY + el_end(el); + tok_end(tok); + history_end(hist); +#endif + close(so); + return 0; +} + +/** + * Connect to the MaxScale server + * + * @param hostname The hostname to connect to + * @param port The port to use for the connection + * @return The connected socket or -1 on error + */ +static int +connectMaxScale(char *hostname, char *port) +{ +struct sockaddr_in addr; +int so; + + if ((so = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + fprintf(stderr, "Unable to create socket: %s\n", + strerror(errno)); + return -1; + } + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + setipaddress(&addr.sin_addr, hostname); + addr.sin_port = htons(atoi(port)); + if (connect(so, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n", + hostname, port, strerror(errno)); + return -1; + } + + return so; +} + + +/** + * Set IP address in socket structure in_addr + * + * @param a Pointer to a struct in_addr into which the address is written + * @param p The hostname to lookup + * @return 1 on success, 0 on failure + */ +static int +setipaddress(struct in_addr *a, char *p) +{ +#ifdef __USE_POSIX + struct addrinfo *ai = NULL, hint; + int rc; + struct sockaddr_in * res_addr; + memset(&hint, 0, sizeof (hint)); + + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_INET; + + if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) { + return 0; + } + + /* take the first one */ + if (ai != NULL) { + res_addr = (struct sockaddr_in *)(ai->ai_addr); + memcpy(a, &res_addr->sin_addr, sizeof(struct in_addr)); + + freeaddrinfo(ai); + + return 1; + } +#else + struct hostent *h; + + spinlock_acquire(&tmplock); + h = gethostbyname(p); + spinlock_release(&tmplock); + + if (h == NULL) { + if ((a->s_addr = inet_addr(p)) == -1) { + return 0; + } + } else { + /* take the first one */ + memcpy(a, h->h_addr, h->h_length); + + return 1; + } +#endif + return 0; +} + +/** + * Perform authentication using the maxscaled protocol conventions + * + * @param so The socket connected to MaxScale + * @param user The username to authenticate + * @param password The password to authenticate with + * @return Non-zero of succesful authentication + */ +static int +authMaxScale(int so, char *user, char *password) +{ +char buf[20]; + + read(so, buf, 4); + write(so, user, strlen(user)); + read(so, buf, 8); + write(so, password, strlen(password)); + read(so, buf, 6); + + return strncmp(buf, "FAILED", 6); +} + +/** + * Send a comamnd using the MaxScaled protocol, display the return data + * on standard output. + * + * Input terminates with a lien containing jsut the text OK + * + * @param so The socket connect to MaxScale + * @param cmd The command to send + * @return 0 if the connection was closed + */ +static int +sendCommand(int so, char *cmd) +{ +char buf[80]; +int i, j, newline = 0; + + write(so, cmd, strlen(cmd)); + while (1) + { + if ((i = read(so, buf, 80)) == -1) + return 0; + for (j = 0; j < i; j++) + { + if (newline == 1 && buf[j] == 'O') + newline = 2; + else if (newline == 2 && buf[j] == 'K' && j == i - 1) + { + return 1; + } + else if (newline == 2) + { + putchar('O'); + putchar(buf[j]); + newline = 0; + } + else if (buf[j] == '\n' || buf[j] == '\r') + { + putchar(buf[j]); + newline = 1; + } + else + { + putchar(buf[j]); + newline = 0; + } + } + } + return 1; +} + +/** + * Read a file of commands and send them to MaxScale + * + * @param so The socket connected to MaxScale + * @param file The filename + */ +static void +DoSource(int so, char *file) +{ +char *ptr, *pe; +char line[132]; +FILE *fp; + + if ((fp = fopen(file, "r")) == NULL) + { + fprintf(stderr, "Unable to open command file '%s'.\n", + file); + return; + } + + while ((ptr = fgets(line, 132, fp)) != NULL) + { + /* Strip tailing newlines */ + pe = &ptr[strlen(ptr)-1]; + while (pe >= ptr && (*pe == '\r' || *pe == '\n')) + { + *pe = '\0'; + pe--; + } + + if (*ptr != '#') /* Comment */ + { + if (! sendCommand(so, ptr)) + { + break; + } + } + } + fclose(fp); + return; +} + +/** + * Display the --help text. + */ +static void +DoUsage() +{ + printf("maxadmin: The MaxScale administrative and monitor client.\n\n"); + printf("Usage: maxadmin [-u user] [-p password] [-h hostname] [-P port] [ | ]\n\n"); + printf(" -u user The user name to use for the connection, default\n"); + printf(" is admin.\n"); + printf(" -p password The user password, if not given the password will\n"); + printf(" be prompted for interactively\n"); + printf(" -h hostname The maxscale host to connecto to. The default is\n"); + printf(" localhost\n"); + printf(" -P port The port to use for the connection, the default\n"); + printf(" port is 6603.\n"); + printf(" --help Print this help text.\n"); + printf("Any remaining arguments are treated as MaxScale commands or a file\n"); + printf("containing commands to execute.\n"); +} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 000000000..e416d995a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,10 @@ +maxscale (1.0-beta) UNRELEASED; urgency=low + + * Beta release + + -- Timofey Turenko Fri, 05 Jul 2014 14:00:00 +0200 +maxscale (0.7-1) UNRELEASED; urgency=low + + * Initial release. (Closes: #XXXXXX) + + -- Timofey Turenko Tue, 11 Mar 2014 22:59:35 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000..45a4fb75d --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..be0a062ec --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: maxscale +Maintainer: Timofey Turenko +Section: misc +Priority: optional +Standards-Version: 3.9.2 +Build-Depends: debhelper (>= 8), gcc, g++, ncurses-dev, bison, build-essential, libssl-dev, libaio-dev, libmariadbclient-dev, libmariadbd-dev, mariadb-server, cmake, perl, make, libtool, + +Package: maxscale +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: MaxScale + The SkySQL MaxScale is an intelligent proxy that allows forwarding of + database statements to one or more database servers using complex rules, + a semantic understanding of the database statements and the roles of + the various servers within the backend cluster of databases. diff --git a/debian/install b/debian/install new file mode 100644 index 000000000..2ac8a182e --- /dev/null +++ b/debian/install @@ -0,0 +1,3 @@ +maxscale.conf etc/ld.so.conf.d/ +etc/ubuntu/init.d/maxscale etc/init.d/ +binaries/* /usr/local/skysql/maxscale/ diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 000000000..59b18bb73 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,4 @@ +#!/bin/bash + +ln -s /lib64/libaio.so.1 /lib64/libaio.so +/sbin/ldconfig diff --git a/debian/rules b/debian/rules new file mode 100755 index 000000000..fd6b52622 --- /dev/null +++ b/debian/rules @@ -0,0 +1,11 @@ +#!/usr/bin/make -f +%: + $(MAKE) ROOT_PATH=$(shell pwd) HOME="" clean + $(MAKE) ROOT_PATH=$(shell pwd) HOME="" depend + $(MAKE) ROOT_PATH=$(shell pwd) HOME="" + $(MAKE) DEST="$(shell pwd)/binaries" ROOT_PATH=$(shell pwd) HOME="" ERRMSG="/usr/share/mysql/english" EMBEDDED_LIB="/usr/lib/x86_64-linux-gnu/" install + dh $@ +override_dh_usrlocal: +override_dh_auto_clean: +override_dh_auto_build: +override_dh_auto_install: diff --git a/etc/init.d/maxscale b/etc/init.d/maxscale old mode 100644 new mode 100755 index a760a5ec8..dde616fb3 --- a/etc/init.d/maxscale +++ b/etc/init.d/maxscale @@ -17,15 +17,34 @@ # database clusters offering different routing, filtering and protocol choices ### END INIT INFO -MAXSCALE_HOME=/usr/local/skysql/maxscale +############################################# +# MaxScale HOME, PIDFILE, LIB +############################################# + +export MAXSCALE_HOME=/usr/local/skysql/maxscale +export MAXSCALE_PIDFILE=$MAXSCALE_HOME/log/maxscale.pid +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MAXSCALE_HOME/lib + +############################### +# LSB Exit codes (non-Status) +############################### +_RETVAL_GENERIC=1 +_RETVAL_NOT_INSTALLED=5 +_RETVAL_NOT_RUNNING=7 + +############################### +# LSB Status action Exit codes +############################### +_RETVAL_STATUS_OK=0 +_RETVAL_STATUS_NOT_RUNNING=3 # Sanity checks. -[ -x $MAXSCALE_HOME/bin/maxscale ] || exit 0 +[ -x $MAXSCALE_HOME/bin/maxscale ] || exit $_RETVAL_NOT_INSTALLED # Source function library. . /etc/rc.d/init.d/functions -# so we can rearrange this easily +# we can rearrange this easily processname=maxscale servicename=maxscale @@ -33,31 +52,55 @@ RETVAL=0 start() { echo -n $"Starting MaxScale: " - if [ -x $MAXSCALE_HOME/bin/maxscale ] ; then - $MAXSCALE_HOME/bin/maxscale + my_check=`status -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale` + CHECK_RET=$? + [ $CHECK_RET -eq 0 ] && echo -n " found $my_check" && success && CHECK_RET=0 + + daemon --pidfile $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale >& /dev/null + + RETVAL=$? + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename + + if [ $CHECK_RET -ne 0 ]; then + sleep 2 + my_check=`status -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale` + CHECK_RET=$? + [ $CHECK_RET -eq 0 ] && echo -n $my_check && success || failure + fi + + # Return rigth code + if [ $RETVAL -ne 0 ]; then + failure + RETVAL=$_RETVAL_NOT_RUNNING fi - daemon --check $servicename $processname --system - RETVAL=$? echo - [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename + + return $RETVAL } stop() { echo -n $"Stopping MaxScale: " + killproc -p $MAXSCALE_PIDFILE -TERM - killproc $servicename -TERM RETVAL=$? + echo - if [ $RETVAL -eq 0 ]; then - rm -f /var/lock/subsys/$servicename + + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$servicename + + # Return rigth code + if [ $RETVAL -ne 0 ]; then + RETVAL=$_RETVAL_NOT_RUNNING fi + + return $RETVAL } reload() { echo -n $"Reloading MaxScale: " - killproc $servicename -HUP + killproc -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale -HUP RETVAL=$? echo } @@ -65,14 +108,33 @@ reload() { # See how we were called. case "$1" in start) - start + start ;; stop) stop ;; status) - status $servicename + # return 0 on success + # return 3 on any error + + echo -n $"Checking MaxScale status: " + status -p $MAXSCALE_PIDFILE 'MaxScale' RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + echo -ne "\033[1A" + [ $RETVAL -eq 1 ] && warning || failure + echo -ne "\033[1B" + + RETVAL=$_RETVAL_STATUS_NOT_RUNNING + else + echo -ne "\033[1A" + success + echo -ne "\033[1B" + RETVAL=$_RETVAL_STATUS_OK + fi + + exit $RETVAL ;; restart) stop diff --git a/etc/ubuntu/init.d/maxscale b/etc/ubuntu/init.d/maxscale new file mode 100755 index 000000000..c81ffb475 --- /dev/null +++ b/etc/ubuntu/init.d/maxscale @@ -0,0 +1,145 @@ +#!/bin/sh +# +# maxscale: The SkySQL MaxScale database proxy +# +# description: MaxScale provides database specific proxy functionality +# +# processname: maxscale +# +### BEGIN INIT INFO +# Provides: maxscale +# Required-Start: $syslog $local_fs +# Required-Stop: $syslog $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The maxscale database proxy +# Description: MaxScale is a database proxy server that can be used to front end +# database clusters offering different routing, filtering and protocol choices +### END INIT INFO + +############################################# +# MaxScale HOME, PIDFILE, LIB +############################################# + +export MAXSCALE_HOME=/usr/local/skysql/maxscale +export MAXSCALE_PIDFILE=$MAXSCALE_HOME/log/maxscale.pid +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MAXSCALE_HOME/lib + +############################### +# LSB Exit codes (non-Status) +############################### +_RETVAL_GENERIC=1 +_RETVAL_NOT_INSTALLED=5 +_RETVAL_NOT_RUNNING=7 + +############################### +# LSB Status action Exit codes +############################### +_RETVAL_STATUS_OK=0 +_RETVAL_STATUS_NOT_RUNNING=3 + +# Sanity checks. +[ -x $MAXSCALE_HOME/bin/maxscale ] || exit $_RETVAL_NOT_INSTALLED + +################################# +# stop/start/status related vars +################################# +NAME=maxscale +DAEMON=$MAXSCALE_HOME/bin/maxscale + +# Source function library. +. /lib/lsb/init-functions + +# we can rearrange this easily +processname=maxscale +servicename=maxscale + +RETVAL=0 + +start() { + log_daemon_msg "Starting MaxScale" + start_daemon -p $MAXSCALE_PIDFILE $DAEMON 2> /dev/null + + sleep 2 + + status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME + + log_end_msg $? +} + +stop() { + log_daemon_msg "Stopping MaxScale" + killproc -p $PIDFILE $DAEMON 2>&1 /dev/null + + maxscale_wait_stop + + log_end_msg $? +} + +reload() { + log_daemon_msg "Reloading MaxScale" + + killproc -p $MAXSCALE_PIDFILE $DAEMON 1 + + log_end_msg $? +} + +maxscale_wait_stop() { + PIDTMP=$(pidofproc -p $MAXSCALE_PIDFILE $MAXSCALE_HOME/bin/maxscale) + kill -TERM "${PIDTMP:-}" 2> /dev/null; + if [ -n "${PIDTMP:-}" ] && kill -0 "${PIDTMP:-}" 2> /dev/null; then + local i=0 + while kill -0 "${PIDTMP:-}" 2> /dev/null; do + if [ $i = '60' ]; then + break + STATUS=2 + fi + [ "$VERBOSE" != no ] && log_progress_msg "." + sleep 1 + i=$(($i+1)) + done + return $STATUS + else + return $STATUS + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + # return 0 on success + # return 3 on any error + + log_daemon_msg "Checking MaxScale" + status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME + RETVAL=$? + + if [ $RETVAL -ne 0 ]; then + [ $RETVAL -eq 1 ] + + RETVAL=$_RETVAL_STATUS_NOT_RUNNING + else + RETVAL=$_RETVAL_STATUS_OK + fi + + log_end_msg $RETVAL + ;; + restart) + stop + start + ;; + reload) + reload + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload}" + ;; +esac +exit $RETVAL diff --git a/makefile.inc b/makefile.inc index 657ebf849..279cea6a4 100644 --- a/makefile.inc +++ b/makefile.inc @@ -40,4 +40,14 @@ endif ifdef PROF CFLAGS := $(CFLAGS) -DSS_PROF -endif \ No newline at end of file +endif + +ifeq "$(PROFILE)" "Y" + CFLAGS += -pg + LDFLAGS += -pg +endif + +ifeq "$(GCOV)" "Y" + CFLAGS += -fprofile-arcs -ftest-coverage + LIBS += -lgcov +endif diff --git a/maxscale.conf b/maxscale.conf index a381124f4..ad7652647 100644 --- a/maxscale.conf +++ b/maxscale.conf @@ -1,2 +1,2 @@ -/usr/local/sbin/MaxScale/modules -/usr/local/sbin/lib +/usr/local/skysql/maxscale/modules +/usr/local/skysql/maxscale/lib diff --git a/maxscale.spec b/maxscale.spec index 9ab55ef41..dcdc16b7b 100644 --- a/maxscale.spec +++ b/maxscale.spec @@ -1,8 +1,8 @@ %define _topdir %(echo $PWD)/ %define name maxscale -%define release ##RELEASE_TAG## -%define version ##VERSION_TAG## -%define install_path /usr/local/sbin/ +%define release beta +%define version 1.0 +%define install_path /usr/local/skysql/maxscale/ BuildRoot: %{buildroot} Summary: maxscale @@ -14,7 +14,20 @@ Source: %{name}-%{version}-%{release}.tar.gz Prefix: / Group: Development/Tools #Requires: -BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool openssl-devel libaio MariaDB-devel MariaDB-server + +%if 0%{?suse_version} +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl make libtool libopenssl-devel libaio libaio-devel mariadb libedit-devel +%else +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool openssl-devel libaio libaio-devel +%if 0%{?rhel} == 6 +BuildRequires: libedit-devel +%endif +%if 0%{?rhel} == 7 +BuildRequires: mariadb-devel mariadb-embedded-devel libedit-devel +%else +BuildRequires: MariaDB-devel MariaDB-server +%endif +%endif %description MaxScale @@ -24,7 +37,7 @@ MaxScale %setup -q %build -ln -s /lib64/libaio.so.1 /lib64/libaio.so +#ln -s /lib64/libaio.so.1 /lib64/libaio.so make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 clean make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 depend make ROOT_PATH=`pwd` HOME="" $DEBUG_FLAG1 $DEBUG_FLAG2 @@ -36,9 +49,11 @@ ln -s /lib64/libaio.so.1 /lib64/libaio.so %install mkdir -p $RPM_BUILD_ROOT/etc/ld.so.conf.d/ +mkdir -p $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT%{install_path} cp -r binaries/* $RPM_BUILD_ROOT%{install_path} cp maxscale.conf $RPM_BUILD_ROOT/etc/ld.so.conf.d/ +cp etc/init.d/maxscale $RPM_BUILD_ROOT/etc/init.d/ %clean @@ -46,5 +61,6 @@ cp maxscale.conf $RPM_BUILD_ROOT/etc/ld.so.conf.d/ %defattr(-,root,root) %{install_path} /etc/ld.so.conf.d/maxscale.conf +/etc/init.d/maxscale %changelog diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index aafd746ce..889940e00 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -101,7 +101,8 @@ static int is_autocommit_stmt( */ skygw_query_type_t skygw_query_classifier_get_type( const char* query, - unsigned long client_flags) + unsigned long client_flags, + MYSQL** p_mysql) { MYSQL* mysql; char* query_str; @@ -116,9 +117,7 @@ skygw_query_type_t skygw_query_classifier_get_type( query_str = const_cast(query); LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, - "%lu [skygw_query_classifier_get_type] Query : \"%s\"", - pthread_self(), - query_str))); + "Query : \"%s\"", query_str))); /** Get server handle */ mysql = mysql_init(NULL); @@ -131,9 +130,13 @@ skygw_query_type_t skygw_query_classifier_get_type( mysql_error(mysql)))); mysql_library_end(); - goto return_without_server; + goto return_qtype; } + if (p_mysql != NULL) + { + *p_mysql = mysql; + } /** Set methods and authentication to mysql */ mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw"); mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); @@ -145,28 +148,42 @@ skygw_query_type_t skygw_query_classifier_get_type( /** Get one or create new THD object to be use in parsing */ thd = get_or_create_thd_for_parsing(mysql, query_str); - if (thd == NULL) { - goto return_with_server_handle; + if (thd == NULL) + { + skygw_query_classifier_free(mysql); + *p_mysql = NULL; + goto return_qtype; } - /** Create parse_tree inside thd */ + /** + * Create parse_tree inside thd. + * thd and even lex are readable even if parser failed so let it + * continue despite failure. + */ failp = create_parse_tree(thd); - - if (failp) { - goto return_with_thd; - } qtype = resolve_query_type(thd); - -return_with_thd: - (*mysql->methods->free_embedded_thd)(mysql); - mysql->thd = 0; -return_with_server_handle: - mysql_close(mysql); - mysql_thread_end(); -return_without_server: + + if (p_mysql == NULL) + { + skygw_query_classifier_free(mysql); + } +return_qtype: return qtype; } +void skygw_query_classifier_free( + MYSQL* mysql) +{ + if (mysql->thd != NULL) + { + (*mysql->methods->free_embedded_thd)(mysql); + mysql->thd = NULL; + } + mysql_close(mysql); + mysql_thread_end(); +} + + /** * @node (write brief function description here) @@ -447,7 +464,7 @@ static skygw_query_type_t resolve_query_type( type |= QUERY_TYPE_DISABLE_AUTOCOMMIT; type |= QUERY_TYPE_BEGIN_TRX; } - /** + /** * REVOKE ALL, ASSIGN_TO_KEYCACHE, * PRELOAD_KEYS, FLUSH, RESET, CREATE|ALTER|DROP SERVER */ @@ -494,6 +511,7 @@ static skygw_query_type_t resolve_query_type( } /**thd))->lex->prepared_stmt_name.str; + +} diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 64727daa9..31f9cf44e 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -19,6 +19,7 @@ Copyright SkySQL Ab /** getpid */ #include +#include #include "../utils/skygw_utils.h" EXTERN_C_BLOCK_BEGIN @@ -29,25 +30,36 @@ EXTERN_C_BLOCK_BEGIN * is modified */ typedef enum { - QUERY_TYPE_UNKNOWN = 0x000, /*< Initial value, can't be tested bitwisely */ - QUERY_TYPE_LOCAL_READ = 0x001, /*< Read non-database data, execute in MaxScale */ - QUERY_TYPE_READ = 0x002, /*< No updates */ - QUERY_TYPE_WRITE = 0x004, /*< Master data will be modified */ - QUERY_TYPE_SESSION_WRITE = 0x008, /*< Session data will be modified */ - QUERY_TYPE_GLOBAL_WRITE = 0x010, /*< Global system variable modification */ - QUERY_TYPE_BEGIN_TRX = 0x020, /*< BEGIN or START TRANSACTION */ - QUERY_TYPE_ENABLE_AUTOCOMMIT = 0x040,/*< SET autocommit=1 */ - QUERY_TYPE_DISABLE_AUTOCOMMIT = 0x080,/*< SET autocommit=0 */ - QUERY_TYPE_ROLLBACK = 0x100, /*< ROLLBACK */ - QUERY_TYPE_COMMIT = 0x200 /*< COMMIT */ + QUERY_TYPE_UNKNOWN = 0x0000, /*< Initial value, can't be tested bitwisely */ + QUERY_TYPE_LOCAL_READ = 0x0001, /*< Read non-database data, execute in MaxScale */ + QUERY_TYPE_READ = 0x0002, /*< No updates */ + QUERY_TYPE_WRITE = 0x0004, /*< Master data will be modified */ + QUERY_TYPE_SESSION_WRITE = 0x0008, /*< Session data will be modified */ + QUERY_TYPE_GLOBAL_WRITE = 0x0010, /*< Global system variable modification */ + QUERY_TYPE_BEGIN_TRX = 0x0020, /*< BEGIN or START TRANSACTION */ + QUERY_TYPE_ENABLE_AUTOCOMMIT = 0x0040, /*< SET autocommit=1 */ + QUERY_TYPE_DISABLE_AUTOCOMMIT = 0x0080, /*< SET autocommit=0 */ + QUERY_TYPE_ROLLBACK = 0x0100, /*< ROLLBACK */ + QUERY_TYPE_COMMIT = 0x0200, /*< COMMIT */ + QUERY_TYPE_PREPARE_NAMED_STMT = 0x0400, /*< Prepared stmt with name from user */ + QUERY_TYPE_PREPARE_STMT = 0x0800, /*< Prepared stmt with id provided by server */ + QUERY_TYPE_EXEC_STMT = 0x1000 /*< Execute prepared statement */ } skygw_query_type_t; #define QUERY_IS_TYPE(mask,type) ((mask & type) == type) +/** + * Create THD and use it for creating parse tree. Examine parse tree and + * classify the query. + */ skygw_query_type_t skygw_query_classifier_get_type( const char* query_str, - unsigned long client_flags); + unsigned long client_flags, + MYSQL** mysql); +/** Free THD context and close MYSQL */ +void skygw_query_classifier_free(MYSQL* mysql); +char* skygw_query_classifier_get_stmtname(MYSQL* mysql); EXTERN_C_BLOCK_END diff --git a/query_classifier/test/makefile b/query_classifier/test/makefile index 9e35c8b0e..d6a5cce52 100644 --- a/query_classifier/test/makefile +++ b/query_classifier/test/makefile @@ -17,10 +17,13 @@ LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager UTILS_PATH := $(ROOT_PATH)/utils TESTAPP = $(TESTPATH)/testmain -testall: +testall:buildtests + +testalllaters: $(MAKE) cleantests $(MAKE) DEBUG=Y DYNLIB=Y buildtests $(MAKE) runtests + cleantests: - $(DEL) testmain.o @@ -28,7 +31,14 @@ cleantests: - $(DEL) data - $(DEL) *~ -buildtests: +buildtests: + @echo "" + @echo "*********************************************************" + @echo "Query classifier test disabled for now. vraa 4.7.2014" + @echo "*********************************************************" + @echo "" + +buildtestslaters: $(CC) $(CFLAGS) \ -L$(QUERY_CLASSIFIER_PATH) \ -L$(LOG_MANAGER_PATH) \ @@ -49,6 +59,16 @@ buildtests: $(LDLIBS) $(LDMYSQL) runtests: + @echo "" > $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo $(shell date) >> $(TESTLOG) + @echo "Test Query Classifier" >> $(TESTLOG) + @echo "-------------------------------" >> $(TESTLOG) + @echo "Query Classifier NOT TESTED due deprecated tests. vraa 4.7.2014" >> $(TESTLOG) + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) + + +runtestslaters: @echo "" > $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG) diff --git a/query_classifier/test/testmain.c b/query_classifier/test/testmain.c index cfb0f6ef4..00000de98 100644 --- a/query_classifier/test/testmain.c +++ b/query_classifier/test/testmain.c @@ -404,7 +404,8 @@ int main(int argc, char** argv) while(succp) { qtest = slcursor_get_case(c); qtest->qt_result_type = - skygw_query_classifier_get_type(qtest->qt_query_str, f); + skygw_query_classifier_get_type(qtest->qt_query_str, f, + &mysql); succp = slcursor_step_ahead(c); } /** diff --git a/server/Makefile b/server/Makefile index d6aba988e..09120da07 100644 --- a/server/Makefile +++ b/server/Makefile @@ -61,14 +61,14 @@ depend: install: @mkdir -p $(DEST) - @mkdir -p $(DEST)/MaxScale - @mkdir -p $(DEST)/MaxScale/modules - @mkdir -p $(DEST)/MaxScale/log - @mkdir -p $(DEST)/MaxScale/etc - @mkdir -p $(DEST)/MaxScale/mysql + @mkdir -p $(DEST) + @mkdir -p $(DEST)/modules + @mkdir -p $(DEST)/log + @mkdir -p $(DEST)/etc + @mkdir -p $(DEST)/mysql @mkdir -p $(DEST)/lib @mkdir -p $(DEST)/Documentation - install -b MaxScale_template.cnf $(DEST)/MaxScale/etc + install -b MaxScale_template.cnf $(DEST)/etc install ../Documentation/*.pdf $(DEST)/Documentation (cd core; make DEST=$(DEST) install) (cd modules/routing; make DEST=$(DEST) install) diff --git a/server/MaxScale_template.cnf b/server/MaxScale_template.cnf index 94981afbc..124fc923d 100644 --- a/server/MaxScale_template.cnf +++ b/server/MaxScale_template.cnf @@ -42,6 +42,15 @@ passwd=maxpwd # version_string= # +# router_options=,,... +# where value=[master|slave|synced] +# +# Read/Write Split Router specific options are: +# +# max_slave_connections= +# max_slave_replication_lag= +# router_options=slave_selection_criteria=[LEAST_CURRENT_OPERATIONS|LEAST_BEHIND_MASTER] +# # Valid router modules currently are: # readwritesplit, readconnroute and debugcli @@ -51,6 +60,10 @@ router=readwritesplit servers=server1,server2,server3 user=maxuser passwd=maxpwd +max_slave_connections=50% +max_slave_replication_lag=30 +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER + [Read Connection Router] type=service diff --git a/server/core/Makefile b/server/core/Makefile index 0a8de1a4a..ec1ebff93 100644 --- a/server/core/Makefile +++ b/server/core/Makefile @@ -47,13 +47,19 @@ CFLAGS=-c -I/usr/include -I../include -I../modules/include -I../inih \ -I$(LOGPATH) -I$(UTILSPATH) \ -Wall -g -include ../../makefile.inc - LDFLAGS=-rdynamic -L$(LOGPATH) \ -Wl,-rpath,$(DEST)/lib \ -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \ -Wl,-rpath,$(EMBEDDED_LIB) + +LIBS=-L$(EMBEDDED_LIB) \ + -lmysqld \ + -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ + -L../inih/extra -linih -lssl -lstdc++ + +include ../../makefile.inc + 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 \ @@ -72,11 +78,6 @@ OBJ=$(SRCS:.c=.o) KOBJS=maxkeys.o secrets.o utils.o POBJS=maxpasswd.o secrets.o utils.o -LIBS=-L../inih/extra -linih -lssl -lstdc++ \ - -L$(EMBEDDED_LIB) \ - -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ - -lmysqld - all: maxscale maxkeys maxpasswd cleantests: @@ -122,6 +123,6 @@ install: maxscale maxkeys maxpasswd @mkdir -p $(DEST)/bin install -D maxscale maxkeys maxpasswd $(DEST)/bin install -D $(EMBEDDED_LIB)/$(LIB) $(DEST)/lib - install -D $(ERRMSG)/errmsg.sys $(DEST)/MaxScale/mysql + install -D $(ERRMSG)/errmsg.sys $(DEST)/mysql include depend.mk diff --git a/server/core/buffer.c b/server/core/buffer.c index d36886c54..8487aaf2e 100644 --- a/server/core/buffer.c +++ b/server/core/buffer.c @@ -31,6 +31,9 @@ * 10/06/13 Mark Riddoch Initial implementation * 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 + * 28/08/2014 Mark Riddoch Adition of tail pointer to speed + * the gwbuf_append process * * @endverbatim */ @@ -81,6 +84,7 @@ SHARED_BUF *sbuf; sbuf->refcount = 1; rval->sbuf = sbuf; rval->next = NULL; + rval->tail = rval; rval->gwbuf_type = GWBUF_TYPE_UNDEFINED; rval->command = 0; CHK_GWBUF(rval); @@ -130,6 +134,7 @@ GWBUF *rval; rval->end = buf->end; rval->gwbuf_type = buf->gwbuf_type; rval->next = NULL; + rval->tail = rval; CHK_GWBUF(rval); return rval; } @@ -151,10 +156,12 @@ GWBUF *gwbuf_clone_portion( } atomic_add(&buf->sbuf->refcount, 1); clonebuf->sbuf = buf->sbuf; + clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone info bits too */ 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->next = NULL; + clonebuf->tail = clonebuf; CHK_GWBUF(clonebuf); return clonebuf; @@ -185,30 +192,28 @@ GWBUF *gwbuf_clone_transform( goto return_clonebuf; } - switch (src_type) + if (GWBUF_IS_TYPE_MYSQL(head)) { - case GWBUF_TYPE_MYSQL: - if (targettype == GWBUF_TYPE_PLAINSQL) - { - /** Crete reference to string part of buffer */ - clonebuf = gwbuf_clone_portion( - head, - 5, - GWBUF_LENGTH(head)-5); - ss_dassert(clonebuf != NULL); - /** Overwrite the type with new format */ - clonebuf->gwbuf_type = targettype; - } - else - { - clonebuf = NULL; - } - break; - - default: + if (GWBUF_TYPE_PLAINSQL == targettype) + { + /** Crete reference to string part of buffer */ + clonebuf = gwbuf_clone_portion( + head, + 5, + GWBUF_LENGTH(head)-5); + ss_dassert(clonebuf != NULL); + /** Overwrite the type with new format */ + gwbuf_set_type(clonebuf, targettype); + } + else + { clonebuf = NULL; - break; - } /*< switch (src_type) */ + } + } + else + { + clonebuf = NULL; + } return_clonebuf: return clonebuf; @@ -233,12 +238,9 @@ GWBUF *ptr = head; if (!head) return tail; CHK_GWBUF(head); - CHK_GWBUF(tail); - while (ptr->next) - { - ptr = ptr->next; - } - ptr->next = tail; + head->tail->next = tail; + head->tail = tail->tail; + return head; } @@ -270,6 +272,8 @@ GWBUF *rval = head; if (GWBUF_EMPTY(head)) { rval = head->next; + if (head->next) + head->next->tail = head->tail; gwbuf_free(head); } @@ -301,26 +305,43 @@ int rval = 0; return rval; } -bool gwbuf_set_type( +/** + * Trim bytes form the end of a GWBUF structure + * + * @param buf The buffer to trim + * @param nbytes The number of bytes to trim off + * @return The buffer chain + */ +GWBUF * +gwbuf_trim(GWBUF *buf, unsigned int n_bytes) +{ + if (GWBUF_LENGTH(buf) <= n_bytes) + { + gwbuf_consume(buf, GWBUF_LENGTH(buf)); + return NULL; + } + buf->end -= n_bytes; + + return buf; +} + +/** + * Set given type to all buffers on the list. + * * + * @param buf The shared buffer + * @param type Type to be added + */ +void gwbuf_set_type( GWBUF* buf, gwbuf_type_t type) { - bool succp; - CHK_GWBUF(buf); - - switch (type) { - case GWBUF_TYPE_MYSQL: - case GWBUF_TYPE_PLAINSQL: - case GWBUF_TYPE_UNDEFINED: - buf->gwbuf_type = type; - succp = true; - break; - default: - succp = false; - break; + /** Set type consistenly to all buffers on the list */ + while (buf != NULL) + { + CHK_GWBUF(buf); + buf->gwbuf_type |= type; + buf=buf->next; } - ss_dassert(succp); - return succp; } diff --git a/server/core/config.c b/server/core/config.c index d53284fce..0fd0f95e0 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -222,6 +222,7 @@ int error_count = 0; if (router) { char* max_slave_conn_str; + char* max_slave_rlag_str; obj->element = service_alloc(obj->object, router); char *user = @@ -230,6 +231,8 @@ int error_count = 0; config_get_value(obj->parameters, "passwd"); char *enable_root_user = config_get_value(obj->parameters, "enable_root_user"); + char *weightby = + config_get_value(obj->parameters, "weightby"); char *version_string = config_get_value(obj->parameters, "version_string"); @@ -252,20 +255,30 @@ int error_count = 0; if (gateway.version_string) ((SERVICE *)(obj->element))->version_string = strdup(gateway.version_string); } - max_slave_conn_str = config_get_value(obj->parameters, "max_slave_connections"); - + + max_slave_rlag_str = + config_get_value(obj->parameters, + "max_slave_replication_lag"); + if (enable_root_user) - serviceEnableRootUser(obj->element, config_truth_value(enable_root_user)); + serviceEnableRootUser( + obj->element, + config_truth_value(enable_root_user)); + if (weightby) + serviceWeightBy(obj->element, weightby); if (!auth) - auth = config_get_value(obj->parameters, "auth"); + auth = config_get_value(obj->parameters, + "auth"); if (obj->element && user && auth) { - serviceSetUser(obj->element, user, auth); + serviceSetUser(obj->element, + user, + auth); } else if (user && auth == NULL) { @@ -276,6 +289,7 @@ int error_count = 0; "corresponding password.", obj->object))); } + /** Read, validate and set max_slave_connections */ if (max_slave_conn_str != NULL) { CONFIG_PARAMETER* param; @@ -284,11 +298,12 @@ int error_count = 0; param = config_get_param(obj->parameters, "max_slave_connections"); - succp = service_set_slave_conn_limit( + succp = service_set_param_value( obj->element, param, max_slave_conn_str, - COUNT_ATMOST); + COUNT_ATMOST, + (COUNT_TYPE|PERCENT_TYPE)); if (!succp) { @@ -305,6 +320,36 @@ int error_count = 0; param->value))); } } + /** Read, validate and set max_slave_replication_lag */ + if (max_slave_rlag_str != NULL) + { + CONFIG_PARAMETER* param; + bool succp; + + param = config_get_param( + obj->parameters, + "max_slave_replication_lag"); + + succp = service_set_param_value( + obj->element, + param, + max_slave_rlag_str, + COUNT_ATMOST, + COUNT_TYPE); + + if (!succp) + { + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : invalid value type " + "for parameter \'%s.%s = %s\'\n\tExpected " + "type is for maximum " + "slave replication lag.", + ((SERVICE*)obj->element)->name, + param->name, + param->value))); + } + } } else { @@ -362,6 +407,30 @@ int error_count = 0; "defined but no corresponding password.", obj->object))); } + if (obj->element) + { + CONFIG_PARAMETER *params = obj->parameters; + while (params) + { + if (strcmp(params->name, "address") + && strcmp(params->name, "port") + && strcmp(params->name, + "protocol") + && strcmp(params->name, + "monitoruser") + && strcmp(params->name, + "monitorpw") + && strcmp(params->name, + "type") + ) + { + serverAddParameter(obj->element, + params->name, + params->value); + } + params = params->next; + } + } } else if (!strcmp(type, "filter")) { @@ -469,7 +538,7 @@ int error_count = 0; s = strtok(NULL, ","); } } - if (filters) + if (filters && obj->element) { serviceSetFilters(obj->element, filters); } @@ -652,7 +721,12 @@ int error_count = 0; } obj = obj->next; - } + } /*< while */ + /** TODO: consistency check function */ + + /** + * error_count += consistency_checks(); + */ if (error_count) { @@ -901,6 +975,7 @@ SERVER *server; char *auth; char *enable_root_user; char* max_slave_conn_str; + char* max_slave_rlag_str; char *version_string; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); @@ -925,24 +1000,27 @@ SERVER *server; auth); if (enable_root_user) serviceEnableRootUser(service, atoi(enable_root_user)); + + /** Read, validate and set max_slave_connections */ max_slave_conn_str = config_get_value( obj->parameters, "max_slave_connections"); - + if (max_slave_conn_str != NULL) { CONFIG_PARAMETER* param; bool succp; param = config_get_param(obj->parameters, - "max_slave_connections"); + "max_slave_connections"); - succp = service_set_slave_conn_limit( - service, - param, - max_slave_conn_str, - COUNT_ATMOST); + succp = service_set_param_value( + service, + param, + max_slave_conn_str, + COUNT_ATMOST, + (PERCENT_TYPE|COUNT_TYPE)); if (!succp) { @@ -959,8 +1037,40 @@ SERVER *server; param->value))); } } - + /** Read, validate and set max_slave_replication_lag */ + max_slave_rlag_str = + config_get_value(obj->parameters, + "max_slave_replication_lag"); + if (max_slave_rlag_str != NULL) + { + CONFIG_PARAMETER* param; + bool succp; + + param = config_get_param( + obj->parameters, + "max_slave_replication_lag"); + + succp = service_set_param_value( + service, + param, + max_slave_rlag_str, + COUNT_ATMOST, + COUNT_TYPE); + + if (!succp) + { + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "* Warning : invalid value type " + "for parameter \'%s.%s = %s\'\n\tExpected " + "type is for maximum " + "slave replication lag.", + ((SERVICE*)obj->element)->name, + param->name, + param->value))); + } + } } obj->element = service; @@ -1109,7 +1219,7 @@ SERVER *server; s = strtok(NULL, ","); } } - if (filters) + if (filters && obj->element) serviceSetFilters(obj->element, filters); } else if (!strcmp(type, "listener")) @@ -1195,6 +1305,7 @@ static char *service_params[] = "passwd", "enable_root_user", "max_slave_connections", + "max_slave_replication_lag", "version_string", "filters", NULL @@ -1257,8 +1368,6 @@ int i; { if (!strcmp(type, "service")) param_set = service_params; - else if (!strcmp(type, "server")) - param_set = server_params; else if (!strcmp(type, "listener")) param_set = listener_params; else if (!strcmp(type, "monitor")) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 1ef765bc2..dd36d683c 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -213,9 +213,9 @@ getUsers(SERVICE *service, struct users *users) "Exiting."))); return -1; } - /* - * Attempt to connect to each database in the service in turn until - * we find one that we can connect to or until we run out of databases + /** + * Attempt to connect to one of the databases database or until we run + * out of databases * to try */ server = service->databases; diff --git a/server/core/dcb.c b/server/core/dcb.c index 5dc11a374..402c34dc5 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -48,6 +48,7 @@ * This fixes a bug with many reads from * backend * 07/05/2014 Mark Riddoch Addition of callback mechanism + * 20/06/2014 Mark Riddoch Addition of dcb_clone * * @endverbatim */ @@ -83,6 +84,10 @@ static bool dcb_set_state_nomutex( const dcb_state_t new_state, dcb_state_t* old_state); static void dcb_call_callback(DCB *dcb, DCB_REASON reason); +static DCB* dcb_get_next (DCB* dcb); +static int dcb_null_write(DCB *dcb, GWBUF *buf); +static int dcb_null_close(DCB *dcb); +static int dcb_null_auth(DCB *dcb, SERVER *server, SESSION *session, GWBUF *buf); /** * Return the pointer to the lsit of zombie DCB's @@ -115,6 +120,7 @@ DCB *rval; #if defined(SS_DEBUG) rval->dcb_chk_top = CHK_NUM_DCB; rval->dcb_chk_tail = CHK_NUM_DCB; + rval->dcb_errhandle_called = false; #endif rval->dcb_role = role; #if 1 @@ -129,8 +135,11 @@ DCB *rval; spinlock_init(&rval->authlock); spinlock_init(&rval->cb_lock); spinlock_init(&rval->pollinlock); + spinlock_init(&rval->polloutlock); rval->pollinbusy = 0; rval->readcheck = 0; + rval->polloutbusy = 0; + rval->writecheck = 0; rval->fd = -1; memset(&rval->stats, 0, sizeof(DCBSTATS)); // Zero the statistics rval->state = DCB_STATE_ALLOC; @@ -141,6 +150,10 @@ DCB *rval; rval->next = NULL; rval->callbacks = NULL; + rval->remote = NULL; + rval->user = NULL; + rval->flags = 0; + spinlock_acquire(&dcbspin); if (allDCBs == NULL) allDCBs = rval; @@ -157,7 +170,7 @@ DCB *rval; /** - * Free a DCB that has not been associated with a decriptor. + * Free a DCB that has not been associated with a descriptor. * * @param dcb The DCB to free */ @@ -252,7 +265,39 @@ dcb_add_to_zombieslist(DCB *dcb) spinlock_release(&zombiespin); } +/* + * Clone a DCB for internal use, mostly used for specialist filters + * to create dummy clients based on real clients. + * + * @param orig The DCB to clone + * @return A DCB that can be used as a client + */ +DCB * +dcb_clone(DCB *orig) +{ +DCB *clone; + if ((clone = dcb_alloc(DCB_ROLE_REQUEST_HANDLER)) == NULL) + { + return NULL; + } + + clone->fd = -1; + clone->flags |= DCBF_CLONE; + clone->state = orig->state; + clone->data = orig->data; + if (orig->remote) + clone->remote = strdup(orig->remote); + if (orig->user) + clone->user = strdup(orig->user); + clone->protocol = orig->protocol; + + clone->func.write = dcb_null_write; + clone->func.close = dcb_null_close; + clone->func.auth = dcb_null_auth; + + return clone; +} /** * Free a DCB and remove it from the chain of all DCBs @@ -268,8 +313,9 @@ dcb_final_free(DCB *dcb) DCB_CALLBACK *cb; CHK_DCB(dcb); - ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED, - "dcb not in DCB_STATE_DISCONNECTED state."); + ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED || + dcb->state == DCB_STATE_ALLOC, + "dcb not in DCB_STATE_DISCONNECTED not in DCB_STATE_ALLOC state."); /*< First remove this DCB from the chain */ spinlock_acquire(&dcbspin); @@ -314,12 +360,14 @@ DCB_CALLBACK *cb; } } - if (dcb->protocol != NULL) + if (dcb->protocol && ((dcb->flags & DCBF_CLONE) ==0)) free(dcb->protocol); - if (dcb->data) + if (dcb->data && ((dcb->flags & DCBF_CLONE) ==0)) free(dcb->data); if (dcb->remote) free(dcb->remote); + if (dcb->user) + free(dcb->user); /* Clear write and read buffers */ if (dcb->delayq) { @@ -568,7 +616,8 @@ int rc; dcb->fd = fd; /** Copy status field to DCB */ dcb->dcb_server_status = server->status; - + ss_debug(dcb->dcb_port = server->port;) + /*< * backend_dcb is connected to backend server, and once backend_dcb * is added to poll set, authentication takes place as part of @@ -603,26 +652,29 @@ int rc; * * @param dcb The DCB to read from * @param head Pointer to linked list to append data to - * @return -1 on error, otherwise the number of read bytes on the last. - * 0 is returned if no data available on the last iteration of while loop. + * @return -1 on error, otherwise the number of read bytes on the last + * iteration of while loop. 0 is returned if no data available. */ -int -dcb_read(DCB *dcb, GWBUF **head) +int dcb_read( + DCB *dcb, + GWBUF **head) { -GWBUF *buffer = NULL; -int b; -int rc; -int n = 0; -int eno = 0; - + GWBUF *buffer = NULL; + int b; + int rc; + int n ; + int nread = 0; + int eno = 0; + CHK_DCB(dcb); while (true) - { - int bufsize; - + { + int bufsize; + rc = ioctl(dcb->fd, FIONREAD, &b); - - if (rc == -1) { + + if (rc == -1) + { eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( @@ -637,19 +689,44 @@ int eno = 0; n = -1; goto return_n; } - /*< Nothing to read - leave */ - if (b == 0) { + + if (b == 0 && nread == 0) + { + /** Handle closed client socket */ + if (dcb_isclient(dcb)) + { + char c; + int l_errno = 0; + int r = -1; + + /* try to read 1 byte, without consuming the socket buffer */ + r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK); + l_errno = errno; + + if (r <= 0 && + l_errno != EAGAIN && + l_errno != EWOULDBLOCK) + { + n = -1; + goto return_n; + } + } + n = 0; + goto return_n; + } + else if (b == 0) + { n = 0; goto return_n; } bufsize = MIN(b, MAX_BUFFER_SIZE); - - if ((buffer = gwbuf_alloc(bufsize)) == NULL) - { + + if ((buffer = gwbuf_alloc(bufsize)) == NULL) + { /*< - * This is a fatal error which should cause shutdown. - * Todo shutdown if memory allocation fails. - */ + * This is a fatal error which should cause shutdown. + * Todo shutdown if memory allocation fails. + */ LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to allocate read buffer " @@ -662,16 +739,17 @@ int eno = 0; n = -1; ss_dassert(buffer != NULL); goto return_n; - } - GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); - dcb->stats.n_reads++); - - if (n <= 0) - { + } + GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); + dcb->stats.n_reads++); + + if (n <= 0) + { int eno = errno; errno = 0; - - if (eno != EAGAIN && eno != EWOULDBLOCK) { + + if (eno != 0 && eno != EAGAIN && eno != EWOULDBLOCK) + { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Read failed, dcb %p in state " @@ -682,18 +760,11 @@ int eno = 0; eno, strerror(eno)))); } - else - { - /*< - * If read would block it means that other thread - * has probably read the data. - */ - n = 0; - } - - gwbuf_free(buffer); + gwbuf_free(buffer); goto return_n; } + nread += n; + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [dcb_read] Read %d bytes from dcb %p in state %s " @@ -703,14 +774,13 @@ int eno = 0; dcb, STRDCBSTATE(dcb->state), dcb->fd))); - /*< Append read data to the gwbuf */ - *head = gwbuf_append(*head, buffer); - } /*< while (true) */ + /*< Append read data to the gwbuf */ + *head = gwbuf_append(*head, buffer); + } /*< while (true) */ return_n: - return n; + return n; } - /** * General purpose routine to write to a DCB * @@ -720,7 +790,7 @@ return_n: int dcb_write(DCB *dcb, GWBUF *queue) { -int w, qlen; +int w; int saved_errno = 0; int below_water; @@ -769,26 +839,26 @@ int below_water; * not have a race condition on the event. */ if (queue) - qlen = gwbuf_length(queue); - else - qlen = 0; - atomic_add(&dcb->writeqlen, qlen); - dcb->writeq = gwbuf_append(dcb->writeq, queue); - dcb->stats.n_buffered++; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Append to writequeue. %d writes " - "buffered for dcb %p in state %s fd %d", - pthread_self(), - dcb->stats.n_buffered, - dcb, - STRDCBSTATE(dcb->state), - dcb->fd))); + { + int qlen; + + qlen = gwbuf_length(queue); + atomic_add(&dcb->writeqlen, qlen); + dcb->writeq = gwbuf_append(dcb->writeq, queue); + dcb->stats.n_buffered++; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Append to writequeue. %d writes " + "buffered for dcb %p in state %s fd %d", + pthread_self(), + dcb->stats.n_buffered, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + } } else { - int len; - /* * Loop over the buffer chain that has been passed to us * from the reading side. @@ -797,6 +867,7 @@ int below_water; */ while (queue != NULL) { + int qlen; #if defined(SS_DEBUG) if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER && dcb->session != NULL) @@ -814,13 +885,13 @@ int below_water; } } #endif /* SS_DEBUG */ - len = GWBUF_LENGTH(queue); + qlen = GWBUF_LENGTH(queue); GW_NOINTR_CALL( w = gw_write( #if defined(SS_DEBUG) dcb, #endif - dcb->fd, GWBUF_DATA(queue), len); + dcb->fd, GWBUF_DATA(queue), qlen); dcb->stats.n_writes++; ); @@ -831,37 +902,39 @@ int below_water; if (LOG_IS_ENABLED(LOGFILE_DEBUG)) { - if (saved_errno == EPIPE) { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [dcb_write] Write to dcb " - "%p in state %s fd %d failed " - "due errno %d, %s", - pthread_self(), - dcb, - STRDCBSTATE(dcb->state), - dcb->fd, - saved_errno, - strerror(saved_errno)))); + if (saved_errno == EPIPE) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due errno %d, %s", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + saved_errno, + strerror(saved_errno)))); } } + if (LOG_IS_ENABLED(LOGFILE_ERROR)) { if (saved_errno != EPIPE && saved_errno != EAGAIN && - saved_errno != EWOULDBLOCK) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Write to dcb %p in " - "state %s fd %d failed due " - "errno %d, %s", - dcb, - STRDCBSTATE(dcb->state), - dcb->fd, - saved_errno, - strerror(saved_errno)))); - } + saved_errno != EWOULDBLOCK) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write to dcb %p in " + "state %s fd %d failed due " + "errno %d, %s", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + saved_errno, + strerror(saved_errno)))); + } } break; } @@ -885,34 +958,45 @@ int below_water; * for suspended write. */ dcb->writeq = queue; - if (queue) + + if (queue) { + int qlen; + qlen = gwbuf_length(queue); - } - else - { - qlen = 0; - } - atomic_add(&dcb->writeqlen, qlen); - - if (queue != NULL) - { - dcb->stats.n_buffered++; - } + atomic_add(&dcb->writeqlen, qlen); + dcb->stats.n_buffered++; + } } /* if (dcb->writeq) */ - if (saved_errno != 0 && - queue != NULL && - saved_errno != EAGAIN && + if (saved_errno != 0 && + queue != NULL && + saved_errno != EAGAIN && saved_errno != EWOULDBLOCK) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Writing to %s socket failed due %d, %s.", - dcb_isclient(dcb) ? "client" : "backend server", - saved_errno, - strerror(saved_errno)))); + bool dolog = true; + /** + * Do not log if writing COM_QUIT to backend failed. + */ + if (GWBUF_IS_TYPE_MYSQL(queue)) + { + uint8_t* data = GWBUF_DATA(queue); + + if (data[4] == 0x01) + { + dolog = false; + } + } + if (dolog) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Writing to %s socket failed due %d, %s.", + dcb_isclient(dcb) ? "client" : "backend server", + saved_errno, + strerror(saved_errno)))); + } spinlock_release(&dcb->writeqlock); return 0; } @@ -946,10 +1030,10 @@ int above_water; above_water = (dcb->low_water && dcb->writeqlen > dcb->low_water) ? 1 : 0; spinlock_acquire(&dcb->writeqlock); - if (dcb->writeq) + + if (dcb->writeq) { int len; - /* * Loop over the buffer chain in the pending writeq * Send as much of the data in that chain as possible and @@ -970,8 +1054,14 @@ int above_water; if (w < 0) { +#if defined(SS_DEBUG) if (saved_errno == EAGAIN || saved_errno == EWOULDBLOCK) +#else + if (saved_errno == EAGAIN || + saved_errno == EWOULDBLOCK || + saved_errno == EPIPE) +#endif { break; } @@ -1005,16 +1095,17 @@ int above_water; } spinlock_release(&dcb->writeqlock); atomic_add(&dcb->writeqlen, -n); - /* The write queue has drained, potentially need to call a callback function */ + + /* The write queue has drained, potentially need to call a callback function */ if (dcb->writeq == NULL) dcb_call_callback(dcb, DCB_REASON_DRAINED); - if (above_water && dcb->writeqlen < dcb->low_water) + + if (above_water && dcb->writeqlen < dcb->low_water) { atomic_add(&dcb->stats.n_low_water, 1); dcb_call_callback(dcb, DCB_REASON_LOW_WATER); } - return n; } @@ -1033,13 +1124,15 @@ void dcb_close(DCB *dcb) { int rc; + CHK_DCB(dcb); /*< * dcb_close may be called for freshly created dcb, in which case * it only needs to be freed. */ - if (dcb->state == DCB_STATE_ALLOC) { + if (dcb->state == DCB_STATE_ALLOC) + { dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL); dcb_final_free(dcb); return; @@ -1050,13 +1143,19 @@ dcb_close(DCB *dcb) dcb->state == DCB_STATE_ZOMBIE); /*< - * Stop dcb's listening and modify state accordingly. - */ + * Stop dcb's listening and modify state accordingly. + */ rc = poll_remove_dcb(dcb); - - ss_dassert(dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE); + ss_dassert(dcb->state == DCB_STATE_NOPOLLING || + dcb->state == DCB_STATE_ZOMBIE); + /** + * close protocol and router session + */ + if (dcb->func.close != NULL) + { + dcb->func.close(dcb); + } dcb_call_callback(dcb, DCB_REASON_CLOSE); if (rc == 0) { @@ -1077,7 +1176,8 @@ dcb_close(DCB *dcb) STRDCBSTATE(dcb->state)))); } - if (dcb->state == DCB_STATE_NOPOLLING) { + if (dcb->state == DCB_STATE_NOPOLLING) + { dcb_add_to_zombieslist(dcb); } } @@ -1095,6 +1195,8 @@ printDCB(DCB *dcb) printf("\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); if (dcb->remote) printf("\tConnected to: %s\n", dcb->remote); + if (dcb->user) + printf("\tUsername to: %s\n", dcb->user); if (dcb->writeq) printf("\tQueued write data: %d\n",gwbuf_length(dcb->writeq)); printf("\tStatistics:\n"); @@ -1111,6 +1213,19 @@ printDCB(DCB *dcb) printf("\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); } +/** + * Display an entry from the spinlock statistics data + * + * @param dcb The DCB to print to + * @param desc Description of the statistic + * @param value The statistic value + */ +static void +spin_reporter(void *dcb, char *desc, int value) +{ + dcb_printf((DCB *)dcb, "\t\t%-35s %d\n", desc, value); +} + /** * Diagnostic to print all DCB allocated in the system @@ -1152,6 +1267,9 @@ DCB *dcb; if (dcb->remote) dcb_printf(pdcb, "\tConnected to: %s\n", dcb->remote); + if (dcb->user) + dcb_printf(pdcb, "\tUsername: %s\n", + dcb->user); if (dcb->writeq) dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); @@ -1161,9 +1279,13 @@ DCB *dcb; dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered); dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); dcb_printf(pdcb, "\t\tNo. of busy polls: %d\n", dcb->stats.n_busypolls); - dcb_printf(pdcb, "\t\tNo. of read rechecks: %d\n", dcb->stats.n_busypolls); + dcb_printf(pdcb, "\t\tNo. of read rechecks: %d\n", dcb->stats.n_readrechecks); + dcb_printf(pdcb, "\t\tNo. of busy write polls: %d\n", dcb->stats.n_busywrpolls); + dcb_printf(pdcb, "\t\tNo. of write rechecks: %d\n", dcb->stats.n_writerechecks); dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); + if (dcb->flags & DCBF_CLONE) + dcb_printf(pdcb, "\t\tDCB is a clone.\n"); dcb = dcb->next; } spinlock_release(&dcbspin); @@ -1181,20 +1303,60 @@ DCB *dcb; spinlock_acquire(&dcbspin); dcb = allDCBs; - dcb_printf(pdcb, " %-14s | %-26s | %-20s | %s\n", + dcb_printf(pdcb, "Descriptor Control Blocks\n"); + dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n"); + dcb_printf(pdcb, " %-10s | %-26s | %-20s | %s\n", "DCB", "State", "Service", "Remote"); - dcb_printf(pdcb, "-----------------------------------------------------------------------------\n"); + dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n"); while (dcb) { dcb_printf(pdcb, " %-14p | %-26s | %-20s | %s\n", dcb, gw_dcb_state2string(dcb->state), (dcb->session->service ? dcb->session->service->name : ""), + (dcb->session->service ? + dcb->session->service->name : ""), (dcb->remote ? dcb->remote : "")); - dcb = dcb->next; + dcb = dcb->next; } + dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n\n"); spinlock_release(&dcbspin); } +/** + * Diagnotic routine to print client DCB data in a tabular form. + * + * @param pdcb DCB to print results to + */ +void +dListClients(DCB *pdcb) +{ +DCB *dcb; + + spinlock_acquire(&dcbspin); + dcb = allDCBs; + dcb_printf(pdcb, "Client Connections\n"); + dcb_printf(pdcb, "-----------------+------------+----------------------+------------\n"); + dcb_printf(pdcb, " %-15s | %-10s | %-20s | %s\n", + "Client", "DCB", "Service", "Session"); + dcb_printf(pdcb, "-----------------+------------+----------------------+------------\n"); + while (dcb) + { + if (dcb_isclient(dcb) + && dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER) + { + dcb_printf(pdcb, " %-15s | %10p | %-20s | %10p\n", + (dcb->remote ? dcb->remote : ""), + dcb, (dcb->session->service ? + dcb->session->service->name : ""), + dcb->session); + } + dcb = dcb->next; + } + dcb_printf(pdcb, "-----------------+------------+----------------------+------------\n\n"); + spinlock_release(&dcbspin); +} + + /** * Diagnostic to print a DCB to another DCB * @@ -1206,8 +1368,14 @@ dprintDCB(DCB *pdcb, DCB *dcb) { dcb_printf(pdcb, "DCB: %p\n", (void *)dcb); dcb_printf(pdcb, "\tDCB state: %s\n", gw_dcb_state2string(dcb->state)); + if (dcb->session && dcb->session->service) + dcb_printf(pdcb, "\tService: %s\n", + dcb->session->service->name); if (dcb->remote) dcb_printf(pdcb, "\tConnected to: %s\n", dcb->remote); + if (dcb->user) + dcb_printf(pdcb, "\tUsername: %s\n", + dcb->user); dcb_printf(pdcb, "\tOwning Session: %p\n", dcb->session); if (dcb->writeq) dcb_printf(pdcb, "\tQueued write data: %d\n", gwbuf_length(dcb->writeq)); @@ -1222,12 +1390,30 @@ dprintDCB(DCB *pdcb, DCB *dcb) dcb->stats.n_buffered); dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts); - dcb_printf(pdcb, "\t\tNo. of busy polls: %d\n", dcb->stats.n_busypolls); - dcb_printf(pdcb, "\t\tNo. of read rechecks: %d\n", dcb->stats.n_busypolls); + dcb_printf(pdcb, "\t\tNo. of busy polls: %d\n", dcb->stats.n_busypolls); + dcb_printf(pdcb, "\t\tNo. of read rechecks: %d\n", dcb->stats.n_readrechecks); + dcb_printf(pdcb, "\t\tNo. of busy write polls: %d\n", dcb->stats.n_busywrpolls); + dcb_printf(pdcb, "\t\tNo. of write rechecks: %d\n", dcb->stats.n_writerechecks); dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water); dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water); + if (dcb->flags & DCBF_CLONE) + dcb_printf(pdcb, "\t\tDCB is a clone.\n"); +#if SPINLOCK_PROFILE + dcb_printf(pdcb, "\tInitlock Statistics:\n"); + spinlock_stats(&dcb->dcb_initlock, spin_reporter, pdcb); + dcb_printf(pdcb, "\tWrite Queue Lock Statistics:\n"); + spinlock_stats(&dcb->writeqlock, spin_reporter, pdcb); + dcb_printf(pdcb, "\tDelay Queue Lock Statistics:\n"); + spinlock_stats(&dcb->delayqlock, spin_reporter, pdcb); + dcb_printf(pdcb, "\tPollin Lock Statistics:\n"); + spinlock_stats(&dcb->pollinlock, spin_reporter, pdcb); + dcb_printf(pdcb, "\tPollout Lock Statistics:\n"); + spinlock_stats(&dcb->polloutlock, spin_reporter, pdcb); + dcb_printf(pdcb, "\tCallback Lock Statistics:\n"); + spinlock_stats(&dcb->cb_lock, spin_reporter, pdcb); +#endif } /** @@ -1258,7 +1444,7 @@ gw_dcb_state2string (int state) { } /** - * A DCB based wrapper for printf. Allows formattign printing to + * A DCB based wrapper for printf. Allows formatting printing to * a descritor control block. * * @param dcb Descriptor to write to @@ -1463,7 +1649,7 @@ static bool dcb_set_state_nomutex( } /*< switch (dcb->state) */ if (succp) { - LOGIF(LD, (skygw_log_write( + LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [dcb_set_state_nomutex] dcb %p fd %d %s -> %s", pthread_self(), @@ -1611,7 +1797,10 @@ int rval = 1; return 0; } if (cb->next == NULL) + { cb->next = ptr; + break; + } cb = cb->next; } spinlock_release(&dcb->cb_lock); @@ -1632,7 +1821,7 @@ int rval = 1; * @return Non-zero (true) if the callback was removed */ int -dcb_remove_callback(DCB *dcb, DCB_REASON reason, int (*callback)(struct dcb *, DCB_REASON), void *userdata) +dcb_remove_callback(DCB *dcb, DCB_REASON reason, int (*callback)(struct dcb *, DCB_REASON, void *), void *userdata) { DCB_CALLBACK *cb, *pcb = NULL; int rval = 0; @@ -1650,7 +1839,7 @@ int rval = 0; if (cb->reason == reason && cb->cb == callback && cb->userdata == userdata) { - if (pcb == NULL) + if (pcb != NULL) pcb->next = cb->next; else dcb->callbacks = cb->next; @@ -1727,9 +1916,16 @@ int rval = 0; /** * Called by the EPOLLIN event. Take care of calling the protocol - * read entry point and managing multiple threads copeting for the DCB + * read entry point and managing multiple threads competing for the DCB * without blocking those threads. * + * This mechanism does away with the need for a mutex on the EPOLLIN event + * and instead implements a queuing mechanism in which nested events are + * queued on the DCB such that when the thread processing the first event + * returns it will read the queued event and process it. This allows the + * thread that woudl otherwise have to wait to process the nested event + * to return immediately and and process other events. + * * @param dcb The DCB that has data available */ void @@ -1757,3 +1953,167 @@ dcb_pollin(DCB *dcb) } spinlock_release(&dcb->pollinlock); } + + +/** + * Called by the EPOLLOUT event. Take care of calling the protocol + * write_ready entry point and managing multiple threads competing for the DCB + * without blocking those threads. + * + * This mechanism does away with the need for a mutex on the EPOLLOUT event + * and instead implements a queuing mechanism in which nested events are + * queued on the DCB such that when the thread processing the first event + * returns it will read the queued event and process it. This allows the + * thread that would otherwise have to wait to process the nested event + * to return immediately and and process other events. + * + * @param dcb The DCB thats available for writes + */ +void +dcb_pollout(DCB *dcb) +{ + + spinlock_acquire(&dcb->polloutlock); + if (dcb->polloutbusy == 0) + { + dcb->polloutbusy = 1; + do { + if (dcb->writecheck) + dcb->stats.n_writerechecks++; + dcb->writecheck = 0; + spinlock_release(&dcb->polloutlock); + dcb->func.write_ready(dcb); + spinlock_acquire(&dcb->polloutlock); + } while (dcb->writecheck); + dcb->polloutbusy = 0; + } + else + { + dcb->stats.n_busywrpolls++; + dcb->writecheck = 1; + } + spinlock_release(&dcb->polloutlock); +} + + +/** + * Get the next DCB in the list of all DCB's + * + * @param dcb The current DCB + * @return The pointer to the next DCB or NULL if this is the last + */ +static DCB * +dcb_get_next (DCB* dcb) +{ + DCB* p; + + spinlock_acquire(&dcbspin); + + p = allDCBs; + + if (dcb == NULL || p == NULL) + { + dcb = p; + + } + else + { + while (p != NULL && dcb != p) + { + p = p->next; + } + + if (p != NULL) + { + dcb = p->next; + } + else + { + dcb = NULL; + } + } + spinlock_release(&dcbspin); + + return dcb; +} + +/** + * Call all the callbacks on all DCB's that match the reason given + * + * @param reason The DCB_REASON that triggers the callback + */ +void +dcb_call_foreach(DCB_REASON reason) +{ + switch (reason) { + case DCB_REASON_CLOSE: + case DCB_REASON_DRAINED: + case DCB_REASON_HIGH_WATER: + case DCB_REASON_LOW_WATER: + case DCB_REASON_ERROR: + case DCB_REASON_HUP: + case DCB_REASON_NOT_RESPONDING: + { + DCB* dcb; + dcb = dcb_get_next(NULL); + + while (dcb != NULL) + { + if (dcb->state == DCB_STATE_POLLING) + { + dcb_call_callback(dcb, DCB_REASON_NOT_RESPONDING); + } + dcb = dcb_get_next(dcb); + } + break; + } + + default: + break; + } + return; +} + + +/** + * Null protocol write routine used for cloned dcb's. It merely consumes + * buffers written on the cloned DCB. + * + * @params dcb The descriptor control block + * @params buf The buffer beign written + * @return Always returns a good write operation result + */ +static int +dcb_null_write(DCB *dcb, GWBUF *buf) +{ + while (buf) + { + buf = gwbuf_consume(buf, GWBUF_LENGTH(buf)); + } + return 1; +} + +/** + * Null protocol close operation for use by cloned DCB's. + * + * @param dcb The DCB being closed. + */ +static int +dcb_null_close(DCB *dcb) +{ + return 0; +} + +/** + * Null protocol auth operation for use by cloned DCB's. + * + * @param dcb The DCB being closed. + * @param server The server to auth against + * @param session The user session + * @param buf The buffer with the new auth request + */ +static int +dcb_null_auth(DCB *dcb, SERVER *server, SESSION *session, GWBUF *buf) +{ + return 0; +} diff --git a/server/core/filter.c b/server/core/filter.c index a3db72e3b..baeea21d7 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -135,6 +135,19 @@ FILTER_DEF *filter; return filter; } +/** + * Check a parameter to see if it is a standard filter parameter + * + * @param name Parameter name to check + */ +int +filter_standard_parameter(char *name) +{ + if (strcmp(name, "type") == 0 || strcmp(name, "module") == 0) + return 1; + return 0; +} + /** * Print all filters to a DCB * @@ -207,19 +220,23 @@ int i; ptr = allFilters; if (ptr) { - dcb_printf(dcb, "%-18s | %-15s | Options\n", + dcb_printf(dcb, "Filters\n"); + dcb_printf(dcb, "--------------------+-----------------+----------------------------------------\n"); + dcb_printf(dcb, "%-19s | %-15s | Options\n", "Filter", "Module"); - dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + dcb_printf(dcb, "--------------------+-----------------+----------------------------------------\n"); } while (ptr) { - dcb_printf(dcb, "%-18s | %-15s | ", + dcb_printf(dcb, "%-19s | %-15s | ", ptr->name, ptr->module); for (i = 0; ptr->options && ptr->options[i]; i++) dcb_printf(dcb, "%s ", ptr->options[i]); dcb_printf(dcb, "\n"); ptr = ptr->next; } + if (allFilters) + dcb_printf(dcb, "--------------------+-----------------+----------------------------------------\n\n"); spinlock_release(&filter_spin); } @@ -285,6 +302,17 @@ int i; spinlock_release(&filter->spin); } +/** + * Connect the downstream filter chain for a filter. + * + * This will create the filter instance, loading the filter module, and + * conenct the fitler into the downstream chain. + * + * @param filter The filter to add into the chain + * @param session The client session + * @param downstream The filter downstream of this filter + * @return The downstream component for the next filter + */ DOWNSTREAM * filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream) { @@ -307,10 +335,49 @@ DOWNSTREAM *me; return NULL; } me->instance = filter->filter; - me->routeQuery = filter->obj->routeQuery; + me->routeQuery = (void *)(filter->obj->routeQuery); me->session = filter->obj->newSession(me->instance, session); filter->obj->setDownstream(me->instance, me->session, downstream); return me; } + +/** + * Connect a filter in the up stream filter chain for a session + * + * Note, the filter will have been created when the downstream chian was + * previously setup. + * Note all filters require to be in the upstream chain, so this routine + * may skip a filter if it does not provide an upstream interface. + * + * @param filter The fitler to add to the chain + * @param fsession The filter session + * @param upstream The filter that should be upstream of this filter + * @return The upstream component for the next filter + */ +UPSTREAM * +filterUpstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream) +{ +UPSTREAM *me; + + /* + * The the filter has no setUpstream entry point then is does + * not require to see results and can be left out of the chain. + */ + if (filter->obj->setUpstream == NULL) + return upstream; + + if (filter->obj->clientReply != NULL) + { + if ((me = (UPSTREAM *)calloc(1, sizeof(UPSTREAM))) == NULL) + { + return NULL; + } + me->instance = filter->filter; + me->session = fsession; + me->clientReply = (void *)(filter->obj->clientReply); + filter->obj->setUpstream(me->instance, me->session, upstream); + } + return me; +} diff --git a/server/core/gateway.c b/server/core/gateway.c index 2bd592fe7..fda19fff8 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -31,10 +31,11 @@ * 19/06/13 Mark Riddoch Extract the epoll functionality * 21/06/13 Mark Riddoch Added initial config support * 27/06/13 - * 28/06/13 Vilho Raatikka Added necessary headers, example functions and - * calls to log manager and to query classifier. - * Put example code behind SS_DEBUG macros. + * 28/06/13 Vilho Raatikka Added necessary headers, example functions and + * calls to log manager and to query classifier. + * Put example code behind SS_DEBUG macros. * 05/02/14 Mark Riddoch Addition of version string + * 29/06/14 Massimiliano Pinto Addition of pidfile * * @endverbatim */ @@ -52,6 +53,7 @@ #include #include +#include #include #include #include @@ -108,6 +110,9 @@ static char* server_groups[] = { /* The data directory we created for this gateway instance */ static char datadir[PATH_MAX+1] = ""; +/* The data directory we created for this gateway instance */ +static char pidfile[PATH_MAX+1] = ""; + /** * exit flag for log flusher. */ @@ -125,6 +130,8 @@ static bool daemon_mode = true; static void log_flush_shutdown(void); static void log_flush_cb(void* arg); +static int write_pid_file(char *); /* write MaxScale pidfile */ +static void unlink_pidfile(void); /* remove pidfile */ static void libmysqld_done(void); static bool file_write_header(FILE* outfile); static bool file_write_footer(FILE* outfile); @@ -1017,6 +1024,7 @@ int main(int argc, char **argv) goto return_main; } } + if (!daemon_mode) { fprintf(stderr, @@ -1136,6 +1144,8 @@ int main(int argc, char **argv) rc = MAXSCALE_INTERNALERROR; goto return_main; } + + /* register exit function for embedded MySQL library */ l = atexit(libmysqld_done); if (l != 0) { @@ -1147,6 +1157,7 @@ int main(int argc, char **argv) rc = MAXSCALE_INTERNALERROR; goto return_main; } + /*< * If MaxScale home directory wasn't set by command-line argument. * Next, resolve it from environment variable and further on, @@ -1198,7 +1209,7 @@ int main(int argc, char **argv) rc = MAXSCALE_BADCONFIG; goto return_main; } - + /*< * Set a data directory for the mysqld library, we use * a unique directory name to avoid clauses if multiple @@ -1321,7 +1332,11 @@ int main(int argc, char **argv) LOGFILE_MESSAGE, "MaxScale is running in process %i", getpid()))); - + + /* Write process pid into MaxScale pidfile */ + write_pid_file(home_dir); + + /* Init MaxScale poll system */ poll_init(); /*< @@ -1362,6 +1377,7 @@ int main(int argc, char **argv) * Serve clients. */ poll_waitevents((void *)0); + /*< * Wait server threads' completion. */ @@ -1388,6 +1404,9 @@ int main(int argc, char **argv) LOGFILE_MESSAGE, "MaxScale shutdown completed."))); + /* Remove Pidfile */ + unlink_pidfile(); + return_main: return rc; } /*< End of main */ @@ -1434,3 +1453,59 @@ static void log_flush_cb( LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "Finished MaxScale log flusher."))); } + +/** + * Unlink pid file, called at program exit + */ +static void unlink_pidfile(void) +{ + if (strlen(pidfile)) { + if (unlink(pidfile)) { + fprintf(stderr, "MaxScale failed to remove pidfile %s: error %d, %s\n", pidfile, errno, strerror(errno)); + } + } +} + +/** + * Write process pid into pidfile anc close it + * Parameters: + * @param home_dir The MaxScale home dir + * @return 0 on success, 1 on failure + * + */ + +static int write_pid_file(char *home_dir) { + + int fd = -1; + + snprintf(pidfile, PATH_MAX, "%s/log/maxscale.pid", home_dir); + + fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (fd == -1) { + fprintf(stderr, "MaxScale failed to open pidFile %s: error %d, %s\n", pidfile, errno, strerror(errno)); + return 1; + } else { + char pidstr[50]=""; + /* truncate pidfile content */ + if (ftruncate(fd, 0) == -1) { + fprintf(stderr, "MaxScale failed to truncate pidfile %s: error %d, %s\n", pidfile, errno, strerror(errno)); + } + + snprintf(pidstr, sizeof(pidstr)-1, "%d", getpid()); + + if (pwrite(fd, pidstr, strlen(pidstr), 0) != (ssize_t)strlen(pidstr)) { + fprintf(stderr, "MaxScale failed to write into pidfile %s: error %d, %s\n", pidfile, errno, strerror(errno)); + /* close file and return */ + close(fd); + return 1; + } + + /* close file */ + close(fd); + + fprintf(stderr, "MaxScale PID %s in pidfile %s\n", pidstr, pidfile); + } + + /* success */ + return 0; +} diff --git a/server/core/gw_utils.c b/server/core/gw_utils.c index 0507e3d1c..2662ab41d 100644 --- a/server/core/gw_utils.c +++ b/server/core/gw_utils.c @@ -130,6 +130,7 @@ setipaddress(struct in_addr *a, char *p) { return 1; } #endif + return 0; } /** @@ -157,62 +158,6 @@ void gw_daemonize(void) { } } -///////////////////////////////////////////////// -// Read data from dcb and store it in the gwbuf -///////////////////////////////////////////////// -int gw_read_gwbuff(DCB *dcb, GWBUF **head, int b) { - GWBUF *buffer = NULL; - int n = -1; - - if (b <= 0) { - ss_dassert(false); -#if 0 - dcb->func.close(dcb); -#endif - return 1; - } - - while (b > 0) { - int bufsize = b < MAX_BUFFER_SIZE ? b : MAX_BUFFER_SIZE; - if ((buffer = gwbuf_alloc(bufsize)) == NULL) { - /* Bad news, we have run out of memory */ - /* Error handling */ - (dcb->func).close(dcb); - return 1; - } - - GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); dcb->stats.n_reads++); - - if (n < 0) { - if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { - gwbuf_free(buffer); - return 1; - } else { - gwbuf_free(buffer); - (dcb->func).close(dcb); - return 1; - } - } - - if (n == 0) { - // socket closed - gwbuf_free(buffer); -#if 1 - (dcb->func).close(dcb); -#endif - return 1; - } - - // append read data to the gwbuf - *head = gwbuf_append(*head, buffer); - - // how many bytes left - b -= n; - } - - return 0; -} - /** * Parse the bind config data. This is passed in a string as address:port. * diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 3ace0f91f..93cdf95f6 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -116,7 +116,8 @@ MODULE_INFO *mod_info = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load library for module: " - "%s\n\t\t\t %s.", + "%s\n\n\t\t %s." + "\n\n", module, dlerror()))); return NULL; @@ -359,8 +360,10 @@ dprintAllModules(DCB *dcb) { MODULES *ptr = registered; + dcb_printf(dcb, "Modules.\n"); + dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n"); dcb_printf(dcb, "%-15s | %-11s | Version | API | Status\n", "Module Name", "Module Type"); - dcb_printf(dcb, "--------------------------------------------------------------------------\n"); + dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n"); while (ptr) { dcb_printf(dcb, "%-15s | %-11s | %-7s ", ptr->module, ptr->type, ptr->version); @@ -376,8 +379,11 @@ MODULES *ptr = registered; : (ptr->info->status == MODULE_BETA_RELEASE ? "Beta" : (ptr->info->status == MODULE_GA - ? "GA" : "Unknown")))); + ? "GA" + : (ptr->info->status == MODULE_EXPERIMENTAL + ? "Experimental" : "Unknown"))))); dcb_printf(dcb, "\n"); ptr = ptr->next; } + dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n\n"); } diff --git a/server/core/modutil.c b/server/core/modutil.c index c6bc9bd00..78f389ebf 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -57,15 +57,18 @@ unsigned char *ptr; * This routine is very simplistic and does not deal with SQL text * that spans multiple buffers. * + * The length returned is the complete length of the SQL, which may + * be larger than the amount of data in this packet. + * * @param buf The packet buffer * @param sql Pointer that is set to point at the SQL data - * @param length Length of the SQL data + * @param length Length of the SQL query data * @return True if the packet is a COM_QUERY packet */ int modutil_extract_SQL(GWBUF *buf, char **sql, int *length) { -char *ptr; +unsigned char *ptr; if (!modutil_is_SQL(buf)) return 0; @@ -75,15 +78,62 @@ char *ptr; *length += (*ptr++ << 8); ptr += 2; // Skip sequence id and COM_QUERY byte *length = *length - 1; - *sql = ptr; + *sql = (char *) ptr; + return 1; +} + +/** + * Extract the SQL portion of a COM_QUERY packet + * + * NB This sets *sql to point into the packet and does not + * allocate any new storage. The string pointed to by *sql is + * not NULL terminated. + * + * The number of bytes pointed to *sql is returned in *length + * + * The remaining number of bytes required for the complete query string + * are returned in *residual + * + * @param buf The packet buffer + * @param sql Pointer that is set to point at the SQL data + * @param length Length of the SQL query data pointed to by sql + * @param residual Any remain part of the query in future packets + * @return True if the packet is a COM_QUERY packet + */ +int +modutil_MySQL_Query(GWBUF *buf, char **sql, int *length, int *residual) +{ +unsigned char *ptr; + + if (!modutil_is_SQL(buf)) + return 0; + ptr = GWBUF_DATA(buf); + *residual = *ptr++; + *residual += (*ptr++ << 8); + *residual += (*ptr++ << 8); + ptr += 2; // Skip sequence id and COM_QUERY byte + *residual = *residual - 1; + *length = GWBUF_LENGTH(buf) - 5; + *residual -= *length; + *sql = (char *)ptr; return 1; } + +/** + * Replace the contents of a GWBUF with the new SQL statement passed as a text string. + * The routine takes care of the modification needed to the MySQL packet, + * returning a GWBUF chian that cna be used to send the data to a MySQL server + * + * @param orig The original request in a GWBUF + * @param sql The SQL text to replace in the packet + * @return A newly formed GWBUF containing the MySQL packet. + */ GWBUF * modutil_replace_SQL(GWBUF *orig, char *sql) { -char *ptr; +unsigned char *ptr; int length, newlength; GWBUF *addition; diff --git a/server/core/monitor.c b/server/core/monitor.c index 2fca9bcac..d7b53f5b7 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -73,6 +73,7 @@ MONITOR *mon; return NULL; } mon->handle = (*mon->module->startMonitor)(NULL); + mon->state |= MONITOR_STATE_RUNNING; spinlock_acquire(&monLock); mon->next = allMonitors; allMonitors = mon; @@ -93,6 +94,7 @@ monitor_free(MONITOR *mon) MONITOR *ptr; mon->module->stopMonitor(mon->handle); + mon->state &= ~MONITOR_STATE_RUNNING; spinlock_acquire(&monLock); if (allMonitors == mon) allMonitors = mon->next; @@ -119,6 +121,7 @@ void monitorStart(MONITOR *monitor) { monitor->handle = (*monitor->module->startMonitor)(monitor->handle); + monitor->state |= MONITOR_STATE_RUNNING; } /** @@ -130,6 +133,7 @@ void monitorStop(MONITOR *monitor) { monitor->module->stopMonitor(monitor->handle); + monitor->state &= ~MONITOR_STATE_RUNNING; } /** @@ -200,6 +204,47 @@ MONITOR *ptr; spinlock_release(&monLock); } +/** + * Show a single monitor + * + * @param dcb DCB for printing output + */ +void +monitorShow(DCB *dcb, MONITOR *monitor) +{ + + dcb_printf(dcb, "Monitor: %p\n", monitor); + dcb_printf(dcb, "\tName: %s\n", monitor->name); + if (monitor->module->diagnostics) + monitor->module->diagnostics(dcb, monitor->handle); +} + +/** + * List all the monitors + * + * @param dcb DCB for printing output + */ +void +monitorList(DCB *dcb) +{ +MONITOR *ptr; + + spinlock_acquire(&monLock); + ptr = allMonitors; + dcb_printf(dcb, "+----------------------+---------------------\n"); + dcb_printf(dcb, "| %-20s | Status\n", "Monitor"); + dcb_printf(dcb, "+----------------------+---------------------\n"); + while (ptr) + { + dcb_printf(dcb, "| %-20s | %s\n", ptr->name, + ptr->state & MONITOR_STATE_RUNNING + ? "Running" : "Stopped"); + ptr = ptr->next; + } + dcb_printf(dcb, "+----------------------+---------------------\n"); + spinlock_release(&monLock); +} + /** * Find a monitor by name * @@ -249,6 +294,7 @@ void monitorSetInterval (MONITOR *mon, unsigned long interval) { if (mon->module->setInterval != NULL) { + mon->interval = interval; mon->module->setInterval(mon->handle, interval); } } diff --git a/server/core/poll.c b/server/core/poll.c index f6100df4d..17882d1ed 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -46,9 +46,12 @@ extern int lm_enabled_logfiles_bitmask; */ static int epoll_fd = -1; /*< The epoll file descriptor */ -static int do_shutdown = 0; /*< Flag the shutdown of the poll subsystem */ +static int do_shutdown = 0; /*< Flag the shutdown of the poll subsystem */ static GWBITMASK poll_mask; static simple_mutex_t epoll_wait_mutex; /*< serializes calls to epoll_wait */ +static int n_waiting = 0; /*< No. of threads in epoll_wait */ + +#define MAXNFDS 10 /** * The polling statistics @@ -60,6 +63,9 @@ static struct { int n_hup; /*< Number of hangup events */ int n_accept; /*< Number of accept events */ int n_polls; /*< Number of poll cycles */ + int n_nothreads; /*< Number of times no threads are polling */ + int n_fds[MAXNFDS]; /*< Number of wakeups with particular + n_fds value */ } pollStats; @@ -245,20 +251,22 @@ return_rc: void poll_waitevents(void *arg) { - struct epoll_event events[MAX_EVENTS]; - int i, nfds; - int thread_id = (int)arg; - bool no_op = false; - static bool process_zombies_only = false; /*< flag for all threads */ - DCB *zombies = NULL; +struct epoll_event events[MAX_EVENTS]; +int i, nfds; +int thread_id = (int)arg; +bool no_op = false; +static bool process_zombies_only = false; /*< flag for all threads */ +DCB *zombies = NULL; /* Add this thread to the bitmask of running polling threads */ bitmask_set(&poll_mask, thread_id); while (1) { + atomic_add(&n_waiting, 1); #if BLOCKINGPOLL nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); + atomic_add(&n_waiting, -1); #else /* BLOCKINGPOLL */ if (!no_op) { LOGIF(LD, (skygw_log_write( @@ -275,6 +283,7 @@ poll_waitevents(void *arg) if ((nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 0)) == -1) { + atomic_add(&n_waiting, -1); int eno = errno; errno = 0; LOGIF(LD, (skygw_log_write( @@ -288,6 +297,7 @@ poll_waitevents(void *arg) } else if (nfds == 0) { + atomic_add(&n_waiting, -1); if (process_zombies_only) { #if 0 simple_mutex_unlock(&epoll_wait_mutex); @@ -310,6 +320,13 @@ poll_waitevents(void *arg) } } } + else + { + atomic_add(&n_waiting, -1); + } + + if (n_waiting == 0) + atomic_add(&pollStats.n_nothreads, 1); #if 0 simple_mutex_unlock(&epoll_wait_mutex); #endif @@ -323,6 +340,8 @@ poll_waitevents(void *arg) nfds))); atomic_add(&pollStats.n_polls, 1); + pollStats.n_fds[(nfds < MAXNFDS ? (nfds - 1) : MAXNFDS)]++; + for (i = 0; i < nfds; i++) { DCB *dcb = (DCB *)events[i].data.ptr; @@ -349,8 +368,8 @@ poll_waitevents(void *arg) ss_dassert(dcb->state != DCB_STATE_FREED); ss_debug(spinlock_release(&dcb->dcb_initlock);) - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, "%lu [poll_waitevents] event %d dcb %p " "role %s", pthread_self(), @@ -364,6 +383,7 @@ poll_waitevents(void *arg) eno = gw_getsockerrno(dcb->fd); if (eno == 0) { +#if MUTEX_BLOCK simple_mutex_lock( &dcb->dcb_write_lock, true); @@ -378,6 +398,11 @@ poll_waitevents(void *arg) dcb->dcb_write_active = FALSE; simple_mutex_unlock( &dcb->dcb_write_lock); +#else + atomic_add(&pollStats.n_write, + 1); + dcb_pollout(dcb); +#endif } else { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -552,10 +577,29 @@ poll_bitmask() void dprintPollStats(DCB *dcb) { - dcb_printf(dcb, "Number of epoll cycles: %d\n", pollStats.n_polls); - dcb_printf(dcb, "Number of read events: %d\n", pollStats.n_read); - dcb_printf(dcb, "Number of write events: %d\n", pollStats.n_write); - dcb_printf(dcb, "Number of error events: %d\n", pollStats.n_error); - dcb_printf(dcb, "Number of hangup events: %d\n", pollStats.n_hup); - dcb_printf(dcb, "Number of accept events: %d\n", pollStats.n_accept); +int i; + + dcb_printf(dcb, "Number of epoll cycles: %d\n", + pollStats.n_polls); + dcb_printf(dcb, "Number of read events: %d\n", + pollStats.n_read); + dcb_printf(dcb, "Number of write events: %d\n", + pollStats.n_write); + dcb_printf(dcb, "Number of error events: %d\n", + pollStats.n_error); + dcb_printf(dcb, "Number of hangup events: %d\n", + pollStats.n_hup); + dcb_printf(dcb, "Number of accept events: %d\n", + pollStats.n_accept); + dcb_printf(dcb, "Number of times no threads polling: %d\n", + pollStats.n_nothreads); + + dcb_printf(dcb, "No of poll completions with dscriptors\n"); + dcb_printf(dcb, "\tNo. of descriptors\tNo. of poll completions.\n"); + for (i = 0; i < MAXNFDS - 1; i++) + { + dcb_printf(dcb, "\t%2d\t\t\t%d\n", i + 1, pollStats.n_fds[i]); + } + dcb_printf(dcb, "\t> %d\t\t\t%d\n", MAXNFDS, + pollStats.n_fds[MAXNFDS-1]); } diff --git a/server/core/server.c b/server/core/server.c index 42a60caea..bfe5e4d04 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -28,6 +28,8 @@ * 20/05/14 Massimiliano Pinto Addition of server_string * 21/05/14 Massimiliano Pinto Addition of node_id * 28/05/14 Massimiliano Pinto Addition of rlagd and node_ts fields + * 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields + * 26/06/14 Mark Riddoch Addition of server parameters * * @endverbatim */ @@ -74,8 +76,12 @@ SERVER *server; server->unique_name = NULL; server->server_string = NULL; server->node_id = -1; - server->rlag = -1; + server->rlag = -2; server->node_ts = 0; + server->parameters = NULL; + server->master_id = -1; + server->depth = -1; + server->slaves = NULL; spinlock_acquire(&server_spin); server->next = allServers; @@ -241,16 +247,38 @@ char *stat; while (ptr) { dcb_printf(dcb, "Server %p (%s)\n", ptr, ptr->unique_name); - dcb_printf(dcb, "\tServer: %s\n", ptr->name); + dcb_printf(dcb, "\tServer: %s\n", + ptr->name); stat = server_status(ptr); - dcb_printf(dcb, "\tStatus: %s\n", stat); + dcb_printf(dcb, "\tStatus: %s\n", + stat); free(stat); - dcb_printf(dcb, "\tProtocol: %s\n", ptr->protocol); - dcb_printf(dcb, "\tPort: %d\n", ptr->port); + dcb_printf(dcb, "\tProtocol: %s\n", + ptr->protocol); + dcb_printf(dcb, "\tPort: %d\n", + ptr->port); if (ptr->server_string) - dcb_printf(dcb, "\tServer Version:\t\t%s\n", ptr->server_string); - dcb_printf(dcb, "\tNode Id: %d\n", ptr->node_id); - if (SERVER_IS_SLAVE(ptr)) { + dcb_printf(dcb, "\tServer Version:\t\t\t%s\n", + ptr->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", + ptr->node_id); + dcb_printf(dcb, "\tMaster Id: %d\n", + ptr->master_id); + if (ptr->slaves) { + int i; + dcb_printf(dcb, "\tSlave Ids: "); + for (i = 0; ptr->slaves[i]; i++) + { + if (i == 0) + dcb_printf(dcb, "%li", ptr->slaves[i]); + else + dcb_printf(dcb, ", %li ", ptr->slaves[i]); + } + dcb_printf(dcb, "\n"); + } + dcb_printf(dcb, "\tRepl Depth: %d\n", + ptr->depth); + if (SERVER_IS_SLAVE(ptr) || SERVER_IS_RELAY_SERVER(ptr)) { if (ptr->rlag >= 0) { dcb_printf(dcb, "\tSlave delay:\t\t%d\n", ptr->rlag); } @@ -258,9 +286,13 @@ char *stat; if (ptr->node_ts > 0) { dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", ptr->node_ts); } - dcb_printf(dcb, "\tNumber of connections: %d\n", ptr->stats.n_connections); - dcb_printf(dcb, "\tCurrent no. of conns: %d\n", ptr->stats.n_current); - ptr = ptr->next; + dcb_printf(dcb, "\tNumber of connections: %d\n", + ptr->stats.n_connections); + dcb_printf(dcb, "\tCurrent no. of conns: %d\n", + ptr->stats.n_current); + dcb_printf(dcb, "\tCurrent no. of operations: %d\n", + ptr->stats.n_current_ops); + ptr = ptr->next; } spinlock_release(&server_spin); } @@ -274,28 +306,57 @@ char *stat; void dprintServer(DCB *dcb, SERVER *server) { -char *stat; +char *stat; +SERVER_PARAM *param; dcb_printf(dcb, "Server %p (%s)\n", server, server->unique_name); - dcb_printf(dcb, "\tServer: %s\n", server->name); + dcb_printf(dcb, "\tServer: %s\n", server->name); stat = server_status(server); - dcb_printf(dcb, "\tStatus: %s\n", stat); + dcb_printf(dcb, "\tStatus: %s\n", stat); free(stat); - dcb_printf(dcb, "\tProtocol: %s\n", server->protocol); - dcb_printf(dcb, "\tPort: %d\n", server->port); + dcb_printf(dcb, "\tProtocol: %s\n", server->protocol); + dcb_printf(dcb, "\tPort: %d\n", server->port); if (server->server_string) - dcb_printf(dcb, "\tServer Version:\t\t%s\n", server->server_string); - dcb_printf(dcb, "\tNode Id: %d\n", server->node_id); - if (SERVER_IS_SLAVE(server)) { + dcb_printf(dcb, "\tServer Version:\t\t\t%s\n", server->server_string); + dcb_printf(dcb, "\tNode Id: %d\n", server->node_id); + dcb_printf(dcb, "\tMaster Id: %d\n", server->master_id); + if (server->slaves) { + int i; + dcb_printf(dcb, "\tSlave Ids: "); + for (i = 0; server->slaves[i]; i++) + { + if (i == 0) + dcb_printf(dcb, "%li", server->slaves[i]); + else + dcb_printf(dcb, ", %li ", server->slaves[i]); + } + dcb_printf(dcb, "\n"); + } + dcb_printf(dcb, "\tRepl Depth: %d\n", server->depth); + if (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) { if (server->rlag >= 0) { dcb_printf(dcb, "\tSlave delay:\t\t%d\n", server->rlag); } } if (server->node_ts > 0) { - dcb_printf(dcb, "\tLast Repl Heartbeat:\t%lu\n", server->node_ts); + dcb_printf(dcb, "\tLast Repl Heartbeat:\t%s", + asctime(localtime(&server->node_ts))); } - dcb_printf(dcb, "\tNumber of connections: %d\n", server->stats.n_connections); - dcb_printf(dcb, "\tCurrent no. of conns: %d\n", server->stats.n_current); + if ((param = server->parameters) != NULL) + { + dcb_printf(dcb, "\tServer Parameters:\n"); + while (param) + { + dcb_printf(dcb, "\t\t%-20s\t%s\n", param->name, + param->value); + param = param->next; + } + } + dcb_printf(dcb, "\tNumber of connections: %d\n", + server->stats.n_connections); + dcb_printf(dcb, "\tCurrent no. of conns: %d\n", + server->stats.n_current); + dcb_printf(dcb, "\tCurrent no. of operations: %d\n", server->stats.n_current_ops); } /** @@ -312,20 +373,24 @@ char *stat; ptr = allServers; if (ptr) { - dcb_printf(dcb, "%-18s | %-15s | Port | %-18s | Connections\n", + dcb_printf(dcb, "Servers.\n"); + dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n"); + dcb_printf(dcb, "%-18s | %-15s | Port | %-20s | Connections\n", "Server", "Address", "Status"); - dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n"); } while (ptr) { stat = server_status(ptr); - dcb_printf(dcb, "%-18s | %-15s | %5d | %-18s | %4d\n", + dcb_printf(dcb, "%-18s | %-15s | %5d | %-20s | %4d\n", ptr->unique_name, ptr->name, ptr->port, stat, ptr->stats.n_current); free(stat); ptr = ptr->next; } + if (allServers) + dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n\n"); spinlock_release(&server_spin); } @@ -440,3 +505,59 @@ server_update(SERVER *server, char *protocol, char *user, char *passwd) } } + +/** + * Add a server parameter to a server. + * + * Server parameters may be used by routing to weight the load + * balancing they apply to the server. + * + * @param server The server we are adding the parameter to + * @param name The parameter name + * @param value The parameter value + */ +void +serverAddParameter(SERVER *server, char *name, char *value) +{ +SERVER_PARAM *param; + + if ((param = (SERVER_PARAM *)malloc(sizeof(SERVER_PARAM))) == NULL) + { + return; + } + if ((param->name = strdup(name)) == NULL) + { + free(param); + return; + } + if ((param->value = strdup(value)) == NULL) + { + free(param->value); + free(param); + return; + } + + param->next = server->parameters; + server->parameters = param; +} + +/** + * Retreive a parameter value from a server + * + * @param server The server we are looking for a parameter of + * @param name The name of the parameter we require + * @return The parameter value or NULL if not found + */ +char * +serverGetParameter(SERVER *server, char *name) +{ +SERVER_PARAM *param = server->parameters; + + while (param) + { + if (strcmp(param->name, name) == 0) + return param->value; + param = param->next; + } + return NULL; +} diff --git a/server/core/service.c b/server/core/service.c index d963987d5..1102dabb4 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -114,6 +114,7 @@ SERVICE *service; service->svc_config_version = 0; service->filters = NULL; service->n_filters = 0; + service->weightby = 0; spinlock_init(&service->spin); spinlock_init(&service->users_table_spin); memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE)); @@ -650,12 +651,23 @@ FILTER_DEF **flist; char *ptr, *brkt; int n = 0; - flist = (FILTER_DEF *)malloc(sizeof(FILTER_DEF *)); + if ((flist = (FILTER_DEF **)malloc(sizeof(FILTER_DEF *))) == NULL) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "Out of memory adding filters to service.\n"))); + return; + } ptr = strtok_r(filters, "|", &brkt); while (ptr) { n++; - flist = (FILTER_DEF *)realloc(flist, (n + 1) * sizeof(FILTER_DEF *)); + if ((flist = (FILTER_DEF **)realloc(flist, + (n + 1) * sizeof(FILTER_DEF *))) == NULL) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "Out of memory adding filters to service.\n"))); + return; + } if ((flist[n-1] = filter_find(trim(ptr))) == NULL) { LOGIF(LE, (skygw_log_write_flush( @@ -705,8 +717,8 @@ SERVER *ptr = service->databases; int i; printf("Service %p\n", service); - printf("\tService: %s\n", service->name); - printf("\tRouter: %s (%p)\n", service->routerModule, service->router); + printf("\tService: %s\n", service->name); + printf("\tRouter: %s (%p)\n", service->routerModule, service->router); printf("\tStarted: %s", asctime(localtime(&service->stats.started))); printf("\tBackend databases\n"); while (ptr) @@ -783,13 +795,16 @@ SERVER *server = service->databases; int i; dcb_printf(dcb, "Service %p\n", service); - dcb_printf(dcb, "\tService: %s\n", service->name); - dcb_printf(dcb, "\tRouter: %s (%p)\n", service->routerModule, - service->router); + dcb_printf(dcb, "\tService: %s\n", + service->name); + dcb_printf(dcb, "\tRouter: %s (%p)\n", + service->routerModule, service->router); if (service->router) service->router->diagnostics(service->router_instance, dcb); - dcb_printf(dcb, "\tStarted: %s", + dcb_printf(dcb, "\tStarted: %s", asctime(localtime(&service->stats.started))); + dcb_printf(dcb, "\tRoot user access: %s\n", + service->enable_root ? "Enabled" : "Disabled"); if (service->n_filters) { dcb_printf(dcb, "\tFilter chain: "); @@ -807,9 +822,15 @@ int i; server->protocol); server = server->nextdb; } - dcb_printf(dcb, "\tUsers data: %p\n", service->users); - dcb_printf(dcb, "\tTotal connections: %d\n", service->stats.n_sessions); - dcb_printf(dcb, "\tCurrently connected: %d\n", service->stats.n_current); + if (service->weightby) + dcb_printf(dcb, "\tRouting weight parameter: %s\n", + service->weightby); + dcb_printf(dcb, "\tUsers data: %p\n", + service->users); + dcb_printf(dcb, "\tTotal connections: %d\n", + service->stats.n_sessions); + dcb_printf(dcb, "\tCurrently connected: %d\n", + service->stats.n_current); } /** @@ -826,9 +847,11 @@ SERVICE *ptr; ptr = allServices; if (ptr) { + dcb_printf(dcb, "Services.\n"); + dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); dcb_printf(dcb, "%-25s | %-20s | #Users | Total Sessions\n", "Service Name", "Router Module"); - dcb_printf(dcb, "--------------------------------------------------------------------------\n"); + dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); } while (ptr) { @@ -837,6 +860,8 @@ SERVICE *ptr; ptr->stats.n_current, ptr->stats.n_sessions); ptr = ptr->next; } + if (allServices) + dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n\n"); spinlock_release(&service_spin); } @@ -855,9 +880,11 @@ SERV_PROTOCOL *lptr; ptr = allServices; if (ptr) { + dcb_printf(dcb, "Listeners.\n"); + dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n"); dcb_printf(dcb, "%-20s | %-18s | %-15s | Port | State\n", "Service Name", "Protocol Module", "Address"); - dcb_printf(dcb, "---------------------------------------------------------------------------\n"); + dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n"); } while (ptr) { @@ -866,7 +893,7 @@ SERV_PROTOCOL *lptr; { dcb_printf(dcb, "%-20s | %-18s | %-15s | %5d | %s\n", ptr->name, lptr->protocol, - (lptr != NULL) ? lptr->address : "*", + (lptr && lptr->address) ? lptr->address : "*", lptr->port, (lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ? "Stopped" : "Running" ); @@ -875,6 +902,8 @@ SERV_PROTOCOL *lptr; } ptr = ptr->next; } + if (allServices) + dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n\n"); spinlock_release(&service_spin); } @@ -970,15 +999,15 @@ int service_refresh_users(SERVICE *service) { return 1; } -bool service_set_slave_conn_limit ( - SERVICE* service, - CONFIG_PARAMETER* param, - char* valstr, - count_spec_t count_spec) +bool service_set_param_value ( + SERVICE* service, + CONFIG_PARAMETER* param, + char* valstr, + count_spec_t count_spec, + config_param_type_t type) { char* p; int valint; - bool percent = false; bool succp; /** @@ -1005,11 +1034,15 @@ bool service_set_slave_conn_limit ( { succp = false; } - else + else if (PARAM_IS_TYPE(type,PERCENT_TYPE)) { succp = true; config_set_qualified_param(param, (void *)&valint, PERCENT_TYPE); } + else + { + /** Log error */ + } } else { @@ -1024,11 +1057,15 @@ bool service_set_slave_conn_limit ( { succp = false; } - else + else if (PARAM_IS_TYPE(type,COUNT_TYPE)) { succp = true; config_set_qualified_param(param, (void *)&valint, COUNT_TYPE); } + else + { + /** Log error */ + } } if (succp) @@ -1044,36 +1081,93 @@ bool service_set_slave_conn_limit ( static void service_add_qualified_param( SERVICE* svc, CONFIG_PARAMETER* param) -{ - CONFIG_PARAMETER** p; - +{ spinlock_acquire(&svc->spin); - - p = &svc->svc_config_param; - - if ((*p) != NULL) + + if (svc->svc_config_param == NULL) { - do + svc->svc_config_param = config_clone_param(param); + svc->svc_config_param->next = NULL; + } + else + { + CONFIG_PARAMETER* p = svc->svc_config_param; + CONFIG_PARAMETER* prev = NULL; + + while (true) { - /** If duplicate is found, latter remains */ + CONFIG_PARAMETER* old; + + /** Replace existing parameter in the list, free old */ if (strncasecmp(param->name, - (*p)->name, + p->name, strlen(param->name)) == 0) - { - *p = config_clone_param(param); + { + old = p; + p = config_clone_param(param); + p->next = old->next; + + if (prev != NULL) + { + prev->next = p; + } + else + { + svc->svc_config_param = p; + } + free(old); break; } - } - while ((*p)->next != NULL); - - (*p)->next = config_clone_param(param); - } - else - { - (*p) = config_clone_param(param); + prev = p; + p = p->next; + + /** Hit end of the list, add new parameter */ + if (p == NULL) + { + p = config_clone_param(param); + prev->next = p; + p->next = NULL; + break; + } + } } /** Increment service's configuration version */ atomic_add(&svc->svc_config_version, 1); - (*p)->next = NULL; spinlock_release(&svc->spin); } + +/** + * Return the name of the service + * + * @param svc The service + */ +char * +service_get_name(SERVICE *svc) +{ + return svc->name; +} + +/** + * Set the weighting parameter for the service + * + * @param service The service pointer + * @param weightby The parameter name to weight the routing by + */ +void +serviceWeightBy(SERVICE *service, char *weightby) +{ + if (service->weightby) + free(service->weightby); + service->weightby = strdup(weightby); +} + +/** + * Return the parameter the wervice shoudl use to weight connections + * by + * @param service The Service pointer + */ +char * +serviceGetWeightingParameter(SERVICE *service) +{ + return service->weightby; +} diff --git a/server/core/session.c b/server/core/session.c index cb392258c..62e1015e4 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -127,7 +127,8 @@ session_alloc(SERVICE *service, DCB *client_dcb) * session, therefore it is important that the session lock is * relinquished beforethe router call. */ - if (client_dcb->state != DCB_STATE_LISTENING && client_dcb->dcb_role != DCB_ROLE_INTERNAL) + if (client_dcb->state != DCB_STATE_LISTENING && + client_dcb->dcb_role != DCB_ROLE_INTERNAL) { session->router_session = service->router->newSession(service->router_instance, @@ -165,7 +166,11 @@ session_alloc(SERVICE *service, DCB *client_dcb) session->head.instance = service->router_instance; session->head.session = session->router_session; - session->head.routeQuery = service->router->routeQuery; + session->head.routeQuery = (void *)(service->router->routeQuery); + + session->tail.instance = session; + session->tail.session = session; + session->tail.clientReply = session_reply; if (service->n_filters > 0) { @@ -192,14 +197,28 @@ session_alloc(SERVICE *service, DCB *client_dcb) } spinlock_acquire(&session_spin); - session->state = SESSION_STATE_ROUTER_READY; - session->next = allSessions; - allSessions = session; - spinlock_release(&session_spin); - atomic_add(&service->stats.n_sessions, 1); - atomic_add(&service->stats.n_current, 1); - CHK_SESSION(session); + if (session->state != SESSION_STATE_READY) + { + session_free(session); + client_dcb->session = NULL; + session = NULL; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to create %s session.", + service->name))); + spinlock_release(&session_spin); + } + else + { + session->state = SESSION_STATE_ROUTER_READY; + session->next = allSessions; + allSessions = session; + spinlock_release(&session_spin); + atomic_add(&service->stats.n_sessions, 1); + atomic_add(&service->stats.n_current, 1); + CHK_SESSION(session); + } return_session: return session; } @@ -306,15 +325,18 @@ bool session_free( /* Free router_session and session */ if (session->router_session) { - session->service->router->closeSession( - session->service->router_instance, - session->router_session); session->service->router->freeSession( session->service->router_instance, session->router_session); } if (session->n_filters) { + for (i = 0; i < session->n_filters; i++) + { + session->filters[i].filter->obj->closeSession( + session->filters[i].instance, + session->filters[i].session); + } for (i = 0; i < session->n_filters; i++) { session->filters[i].filter->obj->freeSession( @@ -537,17 +559,23 @@ SESSION *ptr; ptr = allSessions; if (ptr) { - dcb_printf(dcb, "Session | Client | State\n"); - dcb_printf(dcb, "------------------------------------------\n"); + dcb_printf(dcb, "Sessions.\n"); + dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n"); + dcb_printf(dcb, "Session | Client | Service | State\n"); + dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n"); } while (ptr) { - dcb_printf(dcb, "%-16p | %-15s | %s\n", ptr, + dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", ptr, ((ptr->client && ptr->client->remote) ? ptr->client->remote : ""), + (ptr->service && ptr->service->name ? ptr->service->name + : ""), session_state(ptr->state)); ptr = ptr->next; } + if (allSessions) + dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n\n"); spinlock_release(&session_spin); } @@ -610,6 +638,7 @@ session_setup_filters(SESSION *session) { SERVICE *service = session->service; DOWNSTREAM *head; +UPSTREAM *tail; int i; if ((session->filters = calloc(service->n_filters, @@ -638,7 +667,93 @@ int i; session->filters[i].session = head->session; session->filters[i].instance = head->instance; session->head = *head; + free(head); + } + + for (i = 0; i < service->n_filters; i++) + { + if ((tail = filterUpstream(service->filters[i], + session->filters[i].session, + &session->tail)) == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Failed to create filter '%s' for service '%s'.\n", + service->filters[i]->name, + service->name))); + return 0; + } + session->tail = *tail; } return 1; } + +/** + * Entry point for the final element int he upstream filter, i.e. the writing + * of the data to the client. + * + * @param instance The "instance" data + * @param session The session + * @param data The buffer chain to write + */ +int +session_reply(void *instance, void *session, GWBUF *data) +{ +SESSION *the_session = (SESSION *)session; + + return the_session->client->func.write(the_session->client, data); +} + +/** + * Return the client connection address or name + * + * @param session The session whose client address to return + */ +char * +session_get_remote(SESSION *session) +{ + if (session && session->client) + return session->client->remote; + return NULL; +} + +bool session_route_query ( + SESSION* ses, + GWBUF* buf) +{ + bool succp; + + if (ses->head.routeQuery == NULL || + ses->head.instance == NULL || + ses->head.session == NULL) + { + succp = false; + goto return_succp; + } + + if (ses->head.routeQuery(ses->head.instance, ses->head.session, buf) == 1) + { + succp = true; + } + else + { + succp = false; + } +return_succp: + return succp; +} + + +/** + * Return the username of the user connected to the client side of the + * session. + * + * @param session The session pointer. + * @return The user name or NULL if it can not be determined. + */ +char * +session_getUser(SESSION *session) +{ + return (session && session->client) ? session->client->user : NULL; +} diff --git a/server/core/spinlock.c b/server/core/spinlock.c index c859f726e..7b35163f3 100644 --- a/server/core/spinlock.c +++ b/server/core/spinlock.c @@ -40,9 +40,12 @@ void spinlock_init(SPINLOCK *lock) { lock->lock = 0; -#ifdef DEBUG +#ifdef SPINLOCK_PROFILE lock->spins = 0; lock->acquired = 0; + lock->waiting = 0; + lock->max_waiting = 0; + lock->contended = 0; #endif } @@ -54,16 +57,29 @@ spinlock_init(SPINLOCK *lock) void spinlock_acquire(SPINLOCK *lock) { +#ifdef SPINLOCK_PROFILE +int spins = 0; + + atomic_add(&(lock->waiting), 1); +#endif while (atomic_add(&(lock->lock), 1) != 0) { atomic_add(&(lock->lock), -1); -#ifdef DEBUG +#ifdef SPINLOCK_PROFILE atomic_add(&(lock->spins), 1); + spins++; #endif } -#ifdef DEBUG +#ifdef SPINLOCK_PROFILE + if (spins) + { + lock->contended++; + if (lock->maxspins < spins) + lock->maxspins = spins; + } lock->acquired++; lock->owner = THREAD_SHELF(); + atomic_add(&(lock->waiting), -1); #endif } @@ -71,7 +87,7 @@ spinlock_acquire(SPINLOCK *lock) * Acquire a spinlock if it is not already locked. * * @param lock The spinlock to acquire - * @return True ifthe spinlock was acquired, otherwise false + * @return True if the spinlock was acquired, otherwise false */ int spinlock_acquire_nowait(SPINLOCK *lock) @@ -81,7 +97,7 @@ spinlock_acquire_nowait(SPINLOCK *lock) atomic_add(&(lock->lock), -1); return FALSE; } -#ifdef DEBUG +#ifdef SPINLOCK_PROFILE lock->acquired++; lock->owner = THREAD_SHELF(); #endif @@ -96,5 +112,45 @@ spinlock_acquire_nowait(SPINLOCK *lock) void spinlock_release(SPINLOCK *lock) { +#ifdef SPINLOCK_PROFILE + if (lock->waiting > lock->max_waiting) + lock->max_waiting = lock->waiting; +#endif atomic_add(&(lock->lock), -1); } + +/** + * Report statistics on a spinlock. This only has an effect if the + * spinlock code has been compiled with the SPINLOCK_PROFILE option set. + * + * NB A callback function is used to return the data rather than + * merely printing to a DCB in order to avoid a dependency on the DCB + * form the spinlock code and also to facilitate other uses of the + * statistics reporting. + * + * @param lock The spinlock to report on + * @param reporter The callback function to pass the statistics to + * @param hdl A handle that is passed to the reporter function + */ +void +spinlock_stats(SPINLOCK *lock, void (*reporter)(void *, char *, int), void *hdl) +{ +#ifdef SPINLOCK_PROFILE + reporter(hdl, "Spinlock acquired", lock->acquired); + if (lock->acquired) + { + reporter(hdl, "Total no. of spins", lock->spins); + reporter(hdl, "Average no. of spins (overall)", + lock->spins / lock->acquired); + if (lock->contended) + reporter(hdl, "Average no. of spins (when contended)", + lock->spins / lock->contended); + reporter(hdl, "Maximum no. of spins", lock->maxspins); + reporter(hdl, "Maximim no. of blocked threads", + lock->max_waiting); + reporter(hdl, "Contended locks", lock->contended); + reporter(hdl, "Contention percentage", + (lock->contended * 100) / lock->acquired); + } +#endif +} diff --git a/server/include/buffer.h b/server/include/buffer.h index 9651031b2..ec2e91d01 100644 --- a/server/include/buffer.h +++ b/server/include/buffer.h @@ -46,11 +46,23 @@ typedef enum { - GWBUF_TYPE_UNDEFINED = 0x0, - GWBUF_TYPE_PLAINSQL = 0x1, - GWBUF_TYPE_MYSQL = 0x2 + GWBUF_TYPE_UNDEFINED = 0x00, + GWBUF_TYPE_PLAINSQL = 0x01, + GWBUF_TYPE_MYSQL = 0x02, + GWBUF_TYPE_SINGLE_STMT = 0x04, + GWBUF_TYPE_SESCMD_RESPONSE = 0x08, + GWBUF_TYPE_RESPONSE_END = 0x10, + GWBUF_TYPE_SESCMD = 0x20 } gwbuf_type_t; +#define GWBUF_IS_TYPE_UNDEFINED(b) (b->gwbuf_type == 0) +#define GWBUF_IS_TYPE_PLAINSQL(b) (b->gwbuf_type & GWBUF_TYPE_PLAINSQL) +#define GWBUF_IS_TYPE_MYSQL(b) (b->gwbuf_type & GWBUF_TYPE_MYSQL) +#define GWBUF_IS_TYPE_SINGLE_STMT(b) (b->gwbuf_type & GWBUF_TYPE_SINGLE_STMT) +#define GWBUF_IS_TYPE_SESCMD_RESPONSE(b) (b->gwbuf_type & GWBUF_TYPE_SESCMD_RESPONSE) +#define GWBUF_IS_TYPE_RESPONSE_END(b) (b->gwbuf_type & GWBUF_TYPE_RESPONSE_END) +#define GWBUF_IS_TYPE_SESCMD(b) (b->gwbuf_type & GWBUF_TYPE_SESCMD) + /** * A structure to encapsulate the data in a form that the data itself can be * shared between multiple GWBUF's without the need to make multiple copies @@ -71,6 +83,7 @@ typedef struct { */ typedef struct gwbuf { struct gwbuf *next; /*< Next buffer in a linked chain of buffers */ + struct gwbuf *tail; /*< Last buffer in a linked chain of buffers */ void *start; /*< Start of the valid data */ void *end; /*< First byte after the valid data */ SHARED_BUF *sbuf; /*< The shared buffer with the real data */ @@ -91,9 +104,9 @@ typedef struct gwbuf { #define GWBUF_EMPTY(b) ((b)->start == (b)->end) /*< Consume a number of bytes in the buffer */ -#define GWBUF_CONSUME(b, bytes) (b)->start += bytes +#define GWBUF_CONSUME(b, bytes) (b)->start += (bytes) -#define GWBUF_RTRIM(b, bytes) (b)->end -= bytes +#define GWBUF_RTRIM(b, bytes) (b)->end -= (bytes) #define GWBUF_TYPE(b) (b)->gwbuf_type /*< @@ -104,8 +117,9 @@ extern void gwbuf_free(GWBUF *buf); extern GWBUF *gwbuf_clone(GWBUF *buf); extern GWBUF *gwbuf_append(GWBUF *head, GWBUF *tail); extern GWBUF *gwbuf_consume(GWBUF *head, unsigned int length); +extern GWBUF *gwbuf_trim(GWBUF *head, unsigned int length); 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 bool gwbuf_set_type(GWBUF *head, gwbuf_type_t type); +extern void gwbuf_set_type(GWBUF *head, gwbuf_type_t type); #endif diff --git a/server/include/config.h b/server/include/config.h index dc94e3ad9..59cb096e8 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -39,13 +39,15 @@ enum {MAX_PARAM_LEN=256}; typedef enum { - UNDEFINED_TYPE=0, - STRING_TYPE, - COUNT_TYPE, - PERCENT_TYPE, - BOOL_TYPE + UNDEFINED_TYPE = 0x00, + STRING_TYPE = 0x01, + COUNT_TYPE = 0x02, + PERCENT_TYPE = 0x04, + BOOL_TYPE = 0x08 } config_param_type_t; +#define PARAM_IS_TYPE(p,t) ((p) & (t)) + /** * The config parameter */ diff --git a/server/include/dcb.h b/server/include/dcb.h index 68b1f349c..559113e72 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -24,6 +24,8 @@ #include #include +#define ERRHANDLE + struct session; struct server; struct service; @@ -51,6 +53,7 @@ struct service; * 07/02/2014 Massimiliano Pinto Added ipv4 data struct into for dcb * 07/05/2014 Mark Riddoch Addition of callback mechanism * 08/05/2014 Mark Riddoch Addition of writeq high and low watermarks + * 27/08/2014 Mark Ridddoch Addition of write event queuing * * @endverbatim */ @@ -113,6 +116,8 @@ typedef struct dcbstats { int n_low_water; /*< Number of crosses of low water mark */ int n_busypolls; /*< Number of read polls whiel reading */ int n_readrechecks; /*< Number of rechecks for reads */ + int n_busywrpolls; /*< Number of write polls while writing */ + int n_writerechecks;/*< Number of rechecks for writes */ } DCBSTATS; /** @@ -165,7 +170,8 @@ typedef enum { DCB_REASON_HIGH_WATER, /*< Cross high water mark */ DCB_REASON_LOW_WATER, /*< Cross low water mark */ DCB_REASON_ERROR, /*< An error was flagged on the connection */ - DCB_REASON_HUP /*< A hangup was detected */ + DCB_REASON_HUP, /*< A hangup was detected */ + DCB_REASON_NOT_RESPONDING /*< Server connection was lost */ } DCB_REASON; /** @@ -194,6 +200,7 @@ typedef struct dcb_callback { typedef struct dcb { #if defined(SS_DEBUG) skygw_chk_t dcb_chk_top; + bool dcb_errhandle_called; #endif dcb_role_t dcb_role; SPINLOCK dcb_initlock; @@ -205,13 +212,15 @@ typedef struct dcb { #endif int fd; /**< The descriptor */ dcb_state_t state; /**< Current descriptor state */ + int flags; /**< DCB flags */ char *remote; /**< Address of remote end */ + char *user; /**< User name for connection */ struct sockaddr_in ipv4; /**< remote end IPv4 address */ void *protocol; /**< The protocol specific state */ struct session *session; /**< The owning session */ GWPROTOCOL func; /**< The functions for this descriptor */ - unsigned int writeqlen; /**< Current number of byes in the write queue */ + int writeqlen; /**< Current number of byes in the write queue */ SPINLOCK writeqlock; /**< Write Queue spinlock */ GWBUF *writeq; /**< Write Data Queue */ SPINLOCK delayqlock; /**< Delay Backend Write Queue spinlock */ @@ -225,16 +234,20 @@ typedef struct dcb { struct service *service; /**< The related service */ void *data; /**< Specific client data */ DCBMM memdata; /**< The data related to DCB memory management */ - int command; /**< Specific client command type */ SPINLOCK cb_lock; /**< The lock for the callbacks linked list */ DCB_CALLBACK *callbacks; /**< The list of callbacks for the DCB */ SPINLOCK pollinlock; int pollinbusy; int readcheck; + SPINLOCK polloutlock; + int polloutbusy; + int writecheck; + unsigned int high_water; /**< High water mark */ unsigned int low_water; /**< Low water mark */ #if defined(SS_DEBUG) + int dcb_port; /**< port of target server */ skygw_chk_t dcb_chk_tail; #endif } DCB; @@ -271,6 +284,7 @@ int dcb_write(DCB *, GWBUF *); DCB *dcb_alloc(dcb_role_t); void dcb_free(DCB *); DCB *dcb_connect(struct server *, struct session *, const char *); +DCB *dcb_clone(DCB *); int dcb_read(DCB *, GWBUF **); int dcb_drain_writeq(DCB *); void dcb_close(DCB *); @@ -280,6 +294,7 @@ void printDCB(DCB *); /* Debug print routine */ void dprintAllDCBs(DCB *); /* Debug to print all DCB in the system */ void dprintDCB(DCB *, DCB *); /* Debug to print a DCB in the system */ void dListDCBs(DCB *); /* List all DCBs in the system */ +void dListClients(DCB *); /* List al the client DCBs */ const char *gw_dcb_state2string(int); /* DCB state to string */ void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */ int dcb_isclient(DCB *); /* the DCB is the client of the session */ @@ -287,7 +302,7 @@ void dcb_hashtable_stats(DCB *, void *); /**< Print statisitics */ void dcb_add_to_zombieslist(DCB* dcb); int dcb_add_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), void *); -int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON), +int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), void *); int dcb_isvalid(DCB *); /* Check the DCB is in the linked list */ @@ -295,4 +310,12 @@ bool dcb_set_state( DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state); +void dcb_call_foreach (DCB_REASON reason); + + +void dcb_call_foreach ( + DCB_REASON reason); + +/* DCB flags values */ +#define DCBF_CLONE 0x0001 /* DCB is a clone */ #endif /* _DCB_H */ diff --git a/server/include/filter.h b/server/include/filter.h index 8ddcfb00d..568df291a 100644 --- a/server/include/filter.h +++ b/server/include/filter.h @@ -61,6 +61,7 @@ typedef struct { * filter pipline * routeQuery Called on each query that requires * routing + * clientReply * diagnostics Called to force the filter to print * diagnostic output * @@ -74,7 +75,9 @@ typedef struct filter_object { void (*closeSession)(FILTER *instance, void *fsession); void (*freeSession)(FILTER *instance, void *fsession); void (*setDownstream)(FILTER *instance, void *fsession, DOWNSTREAM *downstream); + void (*setUpstream)(FILTER *instance, void *fsession, UPSTREAM *downstream); int (*routeQuery)(FILTER *instance, void *fsession, GWBUF *queue); + int (*clientReply)(FILTER *instance, void *fsession, GWBUF *queue); void (*diagnostics)(FILTER *instance, void *fsession, DCB *dcb); } FILTER_OBJECT; @@ -83,7 +86,7 @@ typedef struct filter_object { * is changed these values must be updated in line with the rules in the * file modinfo.h. */ -#define FILTER_VERSION {1, 0, 0} +#define FILTER_VERSION {1, 1, 0} /** * The definition of a filter form the configuration file. * This is basically the link between a plugin to load and the @@ -108,6 +111,8 @@ FILTER_DEF *filter_find(char *); void filterAddOption(FILTER_DEF *, char *); void filterAddParameter(FILTER_DEF *, char *, char *); DOWNSTREAM *filterApply(FILTER_DEF *, SESSION *, DOWNSTREAM *); +UPSTREAM *filterUpstream(FILTER_DEF *, void *, UPSTREAM *); +int filter_standard_parameter(char *); void dprintAllFilters(DCB *); void dprintFilter(DCB *, FILTER_DEF *); void dListFilters(DCB *); diff --git a/server/include/modinfo.h b/server/include/modinfo.h index c3c1e64da..bc4107b39 100644 --- a/server/include/modinfo.h +++ b/server/include/modinfo.h @@ -38,7 +38,8 @@ typedef enum { MODULE_IN_DEVELOPMENT = 0, MODULE_ALPHA_RELEASE, MODULE_BETA_RELEASE, - MODULE_GA + MODULE_GA, + MODULE_EXPERIMENTAL } MODULE_STATUS; /** diff --git a/server/include/modutil.h b/server/include/modutil.h index 2092ddea5..00336f937 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -26,6 +26,7 @@ * * Date Who Description * 04/06/14 Mark Riddoch Initial implementation + * 24/06/14 Mark Riddoch Add modutil_MySQL_Query to enable multipacket queries * * @endverbatim */ @@ -33,5 +34,6 @@ extern int modutil_is_SQL(GWBUF *); extern int modutil_extract_SQL(GWBUF *, char **, int *); +extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *); extern GWBUF *modutil_replace_SQL(GWBUF *, char *); #endif diff --git a/server/include/monitor.h b/server/include/monitor.h index 861c1c070..d65fd075f 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -78,13 +78,21 @@ typedef struct { */ #define MONITOR_VERSION {1, 0, 0} +/** + * Monitor state bit mask values + */ +#define MONITOR_STATE_RUNNING 0x0001 + + /** * Representation of the running monitor. */ typedef struct monitor { char *name; /**< The name of the monitor module */ + unsigned int state; /**< The monitor status */ MONITOR_OBJECT *module; /**< The "monitor object" */ void *handle; /**< Handle returned from startMonitor */ + int interval; /**< The monitor interval */ struct monitor *next; /**< Next monitor in the linked list */ } MONITOR; @@ -97,6 +105,8 @@ extern void monitorStop(MONITOR *); extern void monitorStart(MONITOR *); extern void monitorStopAll(); extern void monitorShowAll(DCB *); +extern void monitorShow(DCB *, MONITOR *); +extern void monitorList(DCB *); extern void monitorSetId(MONITOR *, unsigned long); extern void monitorSetInterval (MONITOR *, unsigned long); extern void monitorSetReplicationHeartbeat(MONITOR *, int); diff --git a/server/include/router.h b/server/include/router.h index ce8f547d8..6c29fe1bf 100644 --- a/server/include/router.h +++ b/server/include/router.h @@ -66,6 +66,12 @@ typedef void *ROUTER; * * @see load_module */ +typedef enum error_action { + ERRACT_NEW_CONNECTION = 0x001, + ERRACT_REPLY_CLIENT = 0x002 +} error_action_t; + + typedef struct router_object { ROUTER *(*createInstance)(SERVICE *service, char **options); void *(*newSession)(ROUTER *instance, SESSION *session); @@ -74,7 +80,13 @@ typedef struct router_object { int (*routeQuery)(ROUTER *instance, void *router_session, GWBUF *queue); void (*diagnostics)(ROUTER *instance, DCB *dcb); void (*clientReply)(ROUTER* instance, void* router_session, GWBUF* queue, DCB *backend_dcb); - void (*errorReply)(ROUTER* instance, void* router_session, char* message, DCB *backend_dcb, int action); + void (*handleError)( + ROUTER* instance, + void* router_session, + GWBUF* errmsgbuf, + DCB* backend_dcb, + error_action_t action, + bool* succp); uint8_t (*getCapabilities)(ROUTER *instance, void* router_session); } ROUTER_OBJECT; @@ -85,10 +97,15 @@ typedef struct router_object { */ #define ROUTER_VERSION { 1, 0, 0 } +/** + * Router capability type. Indicates what kind of input router accepts. + */ typedef enum router_capability_t { - RCAP_TYPE_UNDEFINED = 0, - RCAP_TYPE_STMT_INPUT = (1 << 0), - RCAP_TYPE_PACKET_INPUT = (1 << 1) + RCAP_TYPE_UNDEFINED = 0x00, + RCAP_TYPE_STMT_INPUT = 0x01, /*< statement per buffer */ + RCAP_TYPE_PACKET_INPUT = 0x02 /*< data as it was read from DCB */ } router_capability_t; + + #endif diff --git a/server/include/server.h b/server/include/server.h index b15453c18..e747c298d 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -36,10 +36,23 @@ * 20/05/14 Massimiliano Pinto Addition of node_id field * 23/05/14 Massimiliano Pinto Addition of rlag and node_ts fields * 03/06/14 Mark Riddoch Addition of maintainance mode + * 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields + * 26/06/14 Mark Riddoch Adidtion of server parameters * * @endverbatim */ +/** + * The server parameters used for weighting routing decissions + * + */ +typedef struct server_params { + char *name; /**< Parameter name */ + char *value; /**< Parameter value */ + struct server_params + *next; /**< Next Paramter in the linked list */ +} SERVER_PARAM; + /** * The server statistics structure * @@ -47,6 +60,7 @@ typedef struct { int n_connections; /**< Number of connections */ int n_current; /**< Current connections */ + int n_current_ops; /**< Current active operations */ } SERVER_STATS; /** @@ -70,6 +84,10 @@ typedef struct server { long node_id; /**< Node id, server_id for M/S or local_index for Galera */ int rlag; /**< Replication Lag for Master / Slave replication */ unsigned long node_ts; /**< Last timestamp set from M/S monitor module */ + SERVER_PARAM *parameters; /**< Parameters of a server that may be used to weight routing decisions */ + long master_id; /**< Master server id of this node */ + int depth; /**< Replication level in the tree */ + long *slaves; /**< Slaves of this node */ } SERVER; /** @@ -77,11 +95,12 @@ typedef struct server { * * These are a bitmap of attributes that may be applied to a server */ -#define SERVER_RUNNING 0x0001 /**<< The server is up and running */ -#define SERVER_MASTER 0x0002 /**<< The server is a master, i.e. can handle writes */ -#define SERVER_SLAVE 0x0004 /**<< The server is a slave, i.e. can handle reads */ -#define SERVER_JOINED 0x0008 /**<< The server is joined in a Galera cluster */ -#define SERVER_MAINT 0x1000 /**<< Server is in maintenance mode */ +#define SERVER_RUNNING 0x0001 /**<< The server is up and running */ +#define SERVER_MASTER 0x0002 /**<< The server is a master, i.e. can handle writes */ +#define SERVER_SLAVE 0x0004 /**<< The server is a slave, i.e. can handle reads */ +#define SERVER_JOINED 0x0008 /**<< The server is joined in a Galera cluster */ +#define SERVER_MAINT 0x1000 /**<< Server is in maintenance mode */ +#define SERVER_SLAVE_OF_EXTERNAL_MASTER 0x0080 /**<< Server is slave of a Master outside the provided replication topology */ /** * Is the server running - the macro returns true if the server is marked as running @@ -117,6 +136,14 @@ typedef struct server { */ #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_IS_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) != 0) + +#define SERVER_IS_RELAY_SERVER(server) \ + (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) + extern SERVER *server_alloc(char *, char *, unsigned short); extern int server_free(SERVER *); extern SERVER *server_find_by_unique_name(char *); @@ -130,6 +157,8 @@ extern char *server_status(SERVER *); extern void server_set_status(SERVER *, int); extern void server_clear_status(SERVER *, int); extern void serverAddMonUser(SERVER *, char *, char *); +extern void serverAddParameter(SERVER *, char *, char *); +extern char *serverGetParameter(SERVER *, char *); extern void server_update(SERVER *, char *, char *, char *); extern void server_set_unique_name(SERVER *, char *); #endif diff --git a/server/include/service.h b/server/include/service.h index 40023332b..cd13d411b 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -43,6 +43,7 @@ * 07/05/14 Massimiliano Pinto Added version_string field to service * struct * 29/05/14 Mark Riddoch Filter API mechanism + * 26/06/14 Mark Riddoch Added WeightBy support * * @endverbatim */ @@ -130,6 +131,7 @@ typedef struct service { rate_limit; /**< The refresh rate limit for users table */ FILTER_DEF **filters; /**< Ordered list of filters */ int n_filters; /**< Number of filters */ + char *weightby; struct service *next; /**< The next service in the linked list */ } SERVICE; @@ -155,18 +157,25 @@ extern int serviceStop(SERVICE *); extern int serviceRestart(SERVICE *); extern int serviceSetUser(SERVICE *, char *, char *); extern int serviceGetUser(SERVICE *, char **, char **); +extern void serviceSetFilters(SERVICE *, char *); extern int serviceEnableRootUser(SERVICE *, int ); +extern void serviceWeightBy(SERVICE *, char *); +extern char *serviceGetWeightingParameter(SERVICE *); extern void service_update(SERVICE *, char *, char *, char *); extern int service_refresh_users(SERVICE *); extern void printService(SERVICE *); extern void printAllServices(); extern void dprintAllServices(DCB *); -bool service_set_slave_conn_limit ( - SERVICE* service, - CONFIG_PARAMETER* param, - char* valstr, - count_spec_t count_spec); + +bool service_set_param_value ( + SERVICE* service, + CONFIG_PARAMETER* param, + char* valstr, + count_spec_t count_spec, + config_param_type_t type); + extern void dprintService(DCB *, SERVICE *); extern void dListServices(DCB *); extern void dListListeners(DCB *); +char* service_get_name(SERVICE* svc); #endif diff --git a/server/include/session.h b/server/include/session.h index f99982802..cbd43fe40 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -57,7 +57,7 @@ typedef enum { SESSION_STATE_ALLOC, /*< for all sessions */ SESSION_STATE_READY, /*< for router session */ SESSION_STATE_ROUTER_READY, /*< for router session */ - SESSION_STATE_STOPPING, /*< router is being closed */ + SESSION_STATE_STOPPING, /*< session and router are being closed */ SESSION_STATE_LISTENER, /*< for listener session */ SESSION_STATE_LISTENER_STOPPED, /*< for listener session */ SESSION_STATE_FREE /*< for all sessions */ @@ -70,8 +70,8 @@ typedef enum { typedef struct { void *instance; void *session; - int (*routeQuery)(void *instance, - void *router_session, GWBUF *queue); + int (*routeQuery)(void *instance, void *session, + GWBUF *request); } DOWNSTREAM; /** @@ -81,8 +81,9 @@ typedef struct { typedef struct { void *instance; void *session; - int (*write)(void *, void *, GWBUF *); - int (*error)(void *); + int (*clientReply)(void *instance, + void *session, GWBUF *response); + int (*error)(void *instance, void *session, void *); } UPSTREAM; /** @@ -117,6 +118,7 @@ typedef struct session { int n_filters; /**< Number of filter sessions */ SESSION_FILTER *filters; /**< The filters in use within this session */ DOWNSTREAM head; /**< Head of the filter chain */ + UPSTREAM tail; /**< The tail of the filter chain */ struct session *next; /**< Linked list of all sessions */ int refcount; /**< Reference count on the session */ #if defined(SS_DEBUG) @@ -131,13 +133,24 @@ typedef struct session { * the incoming data to the first element in the pipeline of filters and * routers. */ -#define SESSION_ROUTE_QUERY(session, buf) \ - ((session)->head.routeQuery)((session)->head.instance, \ - (session)->head.session, (buf)) +#define SESSION_ROUTE_QUERY(sess, buf) \ + ((sess)->head.routeQuery)((sess)->head.instance, \ + (sess)->head.session, (buf)) +/** + * A convenience macro that can be used by the router modules to route + * the replies to the first element in the pipeline of filters and + * the protocol. + */ +#define SESSION_ROUTE_REPLY(sess, buf) \ + ((sess)->tail.clientReply)((sess)->tail.instance, \ + (sess)->tail.session, (buf)) SESSION *session_alloc(struct service *, struct dcb *); bool session_free(SESSION *); int session_isvalid(SESSION *); +int session_reply(void *inst, void *session, GWBUF *data); +char *session_get_remote(SESSION *); +char *session_getUser(SESSION *); void printAllSessions(); void printSession(SESSION *); void dprintAllSessions(struct dcb *); diff --git a/server/include/spinlock.h b/server/include/spinlock.h index 42f7b5c2e..43192da3f 100644 --- a/server/include/spinlock.h +++ b/server/include/spinlock.h @@ -21,7 +21,7 @@ /** * @file spinlock.h * - * Spinlock implementation for ther gateway. + * Spinlock implementation for MaxScale. * * Spinlocks are cheap locks that can be used to protect short code blocks, they are * generally wasteful as any blocked threads will spin, consuming CPU cycles, waiting @@ -31,12 +31,28 @@ #include #include +#define SPINLOCK_PROFILE 1 + +/** + * The spinlock structure. + * + * In normal builds the structure merely contains a lock value which + * is 0 if the spinlock is not taken and greater than zero if it is held. + * + * In builds with the SPINLOCK_PROFILE option set this structure also holds + * a number of profile related fields that count the number of spins, number + * of waiting threads and the number of times the lock has been acquired. + */ typedef struct spinlock { - int lock; -#if DEBUG - int spins; - int acquired; - THREAD owner; + int lock; /*< Is the lock held? */ +#if SPINLOCK_PROFILE + int spins; /*< Number of spins on this lock */ + int maxspins; /*< Max no of spins to acquire lock */ + int acquired; /*< No. of times lock was acquired */ + int waiting; /*< No. of threads acquiring this lock */ + int max_waiting; /*< Max no of threads waiting for lock */ + int contended; /*< No. of times acquire was contended */ + THREAD owner; /*< Last owner of this lock */ #endif } SPINLOCK; @@ -47,8 +63,8 @@ typedef struct spinlock { #define FALSE false #endif -#if DEBUG -#define SPINLOCK_INIT { 0, 0, 0, NULL } +#if SPINLOCK_PROFILE +#define SPINLOCK_INIT { 0, 0, 0, 0, 0, 0, 0, 0 } #else #define SPINLOCK_INIT { 0 } #endif @@ -59,4 +75,6 @@ extern void spinlock_init(SPINLOCK *lock); extern void spinlock_acquire(SPINLOCK *lock); extern int spinlock_acquire_nowait(SPINLOCK *lock); extern void spinlock_release(SPINLOCK *lock); +extern void spinlock_stats(SPINLOCK *lock, + void (*reporter)(void *, char *, int), void *hdl); #endif diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile index 14b226b7d..931c35ab1 100644 --- a/server/modules/filter/Makefile +++ b/server/modules/filter/Makefile @@ -38,10 +38,14 @@ QLASRCS=qlafilter.c QLAOBJ=$(QLASRCS:.c=.o) REGEXSRCS=regexfilter.c REGEXOBJ=$(REGEXSRCS:.c=.o) -SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) +TOPNSRCS=topfilter.c +TOPNOBJ=$(TOPNSRCS:.c=.o) +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 -MODULES= libtestfilter.so libqlafilter.so libregexfilter.so +MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so all: $(MODULES) @@ -55,6 +59,12 @@ libqlafilter.so: $(QLAOBJ) libregexfilter.so: $(REGEXOBJ) $(CC) $(LDFLAGS) $(REGEXOBJ) $(LIBS) -o $@ +libtopfilter.so: $(TOPNOBJ) + $(CC) $(LDFLAGS) $(TOPNOBJ) $(LIBS) -o $@ + +libtee.so: $(TEEOBJ) + $(CC) $(LDFLAGS) $(TEEOBJ) $(LIBS) -o $@ + .c.o: $(CC) $(CFLAGS) $< -o $@ @@ -69,7 +79,7 @@ depend: cc -M $(CFLAGS) $(SRCS) > depend.mk install: $(MODULES) - install -D $(MODULES) $(DEST)/MaxScale/modules + install -D $(MODULES) $(DEST)/modules cleantests: $(MAKE) -C test cleantests diff --git a/server/modules/filter/qlafilter.c b/server/modules/filter/qlafilter.c index 520f1e1a9..da78713d2 100644 --- a/server/modules/filter/qlafilter.c +++ b/server/modules/filter/qlafilter.c @@ -27,24 +27,35 @@ * A single option may be passed to the filter, this is the name of the * file to which the queries are logged. A serial number is appended to this * name in order that each session logs to a different file. + * + * Date Who Description + * 03/06/2014 Mark Riddoch Initial implementation + * 11/06/2014 Mark Riddoch Addition of source and match parameters + * 19/06/2014 Mark Riddoch Addition of user parameter + * */ #include #include #include #include #include -#include +#include +#include #include #include +#include +#include + +extern int lm_enabled_logfiles_bitmask; MODULE_INFO info = { MODULE_API_FILTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, FILTER_VERSION, "A simple query logging filter" }; -static char *version_str = "V1.0.0"; +static char *version_str = "V1.1.1"; /* * The filter entry points @@ -64,7 +75,9 @@ static FILTER_OBJECT MyObject = { closeSession, freeSession, setDownstream, + NULL, // No Upstream requirement routeQuery, + NULL, // No client reply diagnostic, }; @@ -77,13 +90,19 @@ static FILTER_OBJECT MyObject = { * have a nique name. */ typedef struct { - int sessions; - char *filebase; + int sessions; /* The count of sessions */ + char *filebase; /* The filemane base */ + char *source; /* The source of the client connection */ + char *userName; /* The user name to filter on */ + char *match; /* Optional text to match against */ + regex_t re; /* Compiled regex text */ + char *nomatch; /* Optional text to match against for exclusion */ + regex_t nore; /* Compiled regex nomatch text */ } QLA_INSTANCE; /** * The session structure for this QLA filter. - * This stores the downstream filter information, such that the + * This stores the downstream filter information, such that the * filter is able to pass the query on to the next filter (or router) * in the chain. * @@ -92,7 +111,8 @@ typedef struct { typedef struct { DOWNSTREAM down; char *filename; - int fd; + FILE *fp; + int active; } QLA_SESSION; /** @@ -141,6 +161,7 @@ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { QLA_INSTANCE *my_instance; +int i; if ((my_instance = calloc(1, sizeof(QLA_INSTANCE))) != NULL) { @@ -148,7 +169,71 @@ QLA_INSTANCE *my_instance; my_instance->filebase = strdup(options[0]); else my_instance->filebase = strdup("qla"); + my_instance->source = NULL; + my_instance->userName = NULL; + my_instance->match = NULL; + my_instance->nomatch = NULL; + if (params) + { + for (i = 0; params[i]; i++) + { + if (!strcmp(params[i]->name, "match")) + { + my_instance->match = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "exclude")) + { + my_instance->nomatch = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "source")) + my_instance->source = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "user")) + my_instance->userName = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "filebase")) + { + if (my_instance->filebase) + free(my_instance->filebase); + my_instance->source = strdup(params[i]->value); + } + else if (!filter_standard_parameter(params[i]->name)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "qlafilter: Unexpected parameter '%s'.\n", + params[i]->name))); + } + } + } my_instance->sessions = 0; + if (my_instance->match && + regcomp(&my_instance->re, my_instance->match, REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "qlafilter: Invalid regular expression '%s'" + " for the match parameter.\n", + my_instance->match))); + free(my_instance->match); + free(my_instance->source); + free(my_instance->filebase); + free(my_instance); + return NULL; + } + if (my_instance->nomatch && + regcomp(&my_instance->nore, my_instance->nomatch, + REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "qlafilter: Invalid regular expression '%s'" + " for the nomatch paramter.\n", + my_instance->match))); + if (my_instance->match) + regfree(&my_instance->re); + free(my_instance->match); + free(my_instance->source); + free(my_instance->filebase); + free(my_instance); + return NULL; + } } return (FILTER *)my_instance; } @@ -167,6 +252,7 @@ newSession(FILTER *instance, SESSION *session) { QLA_INSTANCE *my_instance = (QLA_INSTANCE *)instance; QLA_SESSION *my_session; +char *remote, *userName; if ((my_session = calloc(1, sizeof(QLA_SESSION))) != NULL) { @@ -177,11 +263,22 @@ QLA_SESSION *my_session; free(my_session); return NULL; } + my_session->active = 1; + if (my_instance->source + && (remote = session_get_remote(session)) != NULL) + { + if (strcmp(remote, my_instance->source)) + my_session->active = 0; + } + userName = session_getUser(session); + if (my_instance->userName && userName && strcmp(userName, + my_instance->userName)) + my_session->active = 0; sprintf(my_session->filename, "%s.%d", my_instance->filebase, my_instance->sessions); my_instance->sessions++; - my_session->fd = open(my_session->filename, - O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (my_session->active) + my_session->fp = fopen(my_session->filename, "w"); } return my_session; @@ -200,7 +297,8 @@ closeSession(FILTER *instance, void *session) { QLA_SESSION *my_session = (QLA_SESSION *)session; - close(my_session->fd); + if (my_session->active && my_session->fp) + fclose(my_session->fp); } /** @@ -248,22 +346,29 @@ QLA_SESSION *my_session = (QLA_SESSION *)session; static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { +QLA_INSTANCE *my_instance = (QLA_INSTANCE *)instance; QLA_SESSION *my_session = (QLA_SESSION *)session; -char *ptr, t_buf[40]; +char *ptr; int length; struct tm t; struct timeval tv; - if (modutil_extract_SQL(queue, &ptr, &length)) + if (my_session->active && modutil_extract_SQL(queue, &ptr, &length)) { - gettimeofday(&tv, NULL); - localtime_r(&tv.tv_sec, &t); - sprintf(t_buf, "%02d:%02d:%02d.%-3d %d/%02d/%d, ", - t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000), - t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year); - write(my_session->fd, t_buf, strlen(t_buf)); - write(my_session->fd, ptr, length); - write(my_session->fd, "\n", 1); + if ((my_instance->match == NULL || + regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) && + (my_instance->nomatch == NULL || + regexec(&my_instance->nore,ptr,0,NULL, 0) != 0)) + { + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &t); + fprintf(my_session->fp, + "%02d:%02d:%02d.%-3d %d/%02d/%d, ", + t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000), + t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year); + fwrite(ptr, sizeof(char), length, my_session->fp); + fwrite("\n", sizeof(char), 1, my_session->fp); + } } /* Pass the query downstream */ @@ -285,11 +390,24 @@ struct timeval tv; static void diagnostic(FILTER *instance, void *fsession, DCB *dcb) { +QLA_INSTANCE *my_instance = (QLA_INSTANCE *)instance; QLA_SESSION *my_session = (QLA_SESSION *)fsession; if (my_session) { - dcb_printf(dcb, "\t\tLogging to file %s.\n", + dcb_printf(dcb, "\t\tLogging to file %s.\n", my_session->filename); } + if (my_instance->source) + dcb_printf(dcb, "\t\tLimit logging to connections from %s\n", + my_instance->source); + if (my_instance->userName) + dcb_printf(dcb, "\t\tLimit logging to user %s\n", + my_instance->userName); + if (my_instance->match) + dcb_printf(dcb, "\t\tInclude queries that match %s\n", + my_instance->match); + if (my_instance->nomatch) + dcb_printf(dcb, "\t\tExclude queries that match %s\n", + my_instance->nomatch); } diff --git a/server/modules/filter/regexfilter.c b/server/modules/filter/regexfilter.c index ad773c40c..5c4f1e7df 100644 --- a/server/modules/filter/regexfilter.c +++ b/server/modules/filter/regexfilter.c @@ -19,9 +19,13 @@ #include #include #include +#include +#include #include #include +extern int lm_enabled_logfiles_bitmask; + /** * regexfilter.c - a very simple regular expression rewrite filter. * @@ -29,16 +33,22 @@ * Two parameters should be defined in the filter configuration * match= * replace= + * Two optional parameters + * source= + * user= + * + * Date Who Description + * 19/06/2014 Mark Riddoch Addition of source and user parameters */ MODULE_INFO info = { MODULE_API_FILTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, FILTER_VERSION, "A query rewrite filter that uses regular expressions to rewite queries" }; -static char *version_str = "V1.0.0"; +static char *version_str = "V1.1.0"; static FILTER *createInstance(char **options, FILTER_PARAMETER **params); static void *newSession(FILTER *instance, SESSION *session); @@ -56,7 +66,9 @@ static FILTER_OBJECT MyObject = { closeSession, freeSession, setDownstream, + NULL, // No Upstream requirement routeQuery, + NULL, diagnostic, }; @@ -64,6 +76,8 @@ static FILTER_OBJECT MyObject = { * Instance structure */ typedef struct { + char *source; /* Source address to restrict matches */ + char *user; /* User name to restrict matches */ char *match; /* Regular expression to match */ char *replace; /* Replacement text */ regex_t re; /* Compiled regex text */ @@ -73,9 +87,10 @@ typedef struct { * The session structure for this regex filter */ typedef struct { - DOWNSTREAM down; - int no_change; - int replacements; + DOWNSTREAM down; /* The downstream filter */ + int no_change; /* No. of unchanged requests */ + int replacements; /* No. of changed requests */ + int active; /* Is filter active */ } REGEX_SESSION; /** @@ -124,20 +139,54 @@ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { REGEX_INSTANCE *my_instance; -int i; +int i, cflags = REG_ICASE; if ((my_instance = calloc(1, sizeof(REGEX_INSTANCE))) != NULL) { my_instance->match = NULL; my_instance->replace = NULL; - for (i = 0; params[i]; i++) + for (i = 0; params && params[i]; i++) { if (!strcmp(params[i]->name, "match")) my_instance->match = strdup(params[i]->value); - if (!strcmp(params[i]->name, "replace")) + else if (!strcmp(params[i]->name, "replace")) my_instance->replace = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "source")) + my_instance->source = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "user")) + my_instance->user = strdup(params[i]->value); + else if (!filter_standard_parameter(params[i]->name)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "regexfilter: Unexpected parameter '%s'.\n", + params[i]->name))); + } } + + if (options) + { + for (i = 0; options[i]; i++) + { + if (!strcasecmp(options[i], "ignorecase")) + { + cflags |= REG_ICASE; + } + else if (!strcasecmp(options[i], "case")) + { + cflags &= ~REG_ICASE; + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "regexfilter: unsupported option '%s'.\n", + options[i]))); + } + } + } + if (my_instance->match == NULL || my_instance->replace == NULL) { return NULL; @@ -145,6 +194,9 @@ int i; if (regcomp(&my_instance->re, my_instance->match, REG_ICASE)) { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "regexfilter: Invalid regular expression '%s'.\n", + my_instance->match))); free(my_instance->match); free(my_instance->replace); free(my_instance); @@ -164,12 +216,27 @@ int i; static void * newSession(FILTER *instance, SESSION *session) { +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; REGEX_SESSION *my_session; +char *remote, *user; if ((my_session = calloc(1, sizeof(REGEX_SESSION))) != NULL) { my_session->no_change = 0; my_session->replacements = 0; + my_session->active = 1; + if (my_instance->source + && (remote = session_get_remote(session)) != NULL) + { + if (strcmp(remote, my_instance->source)) + my_session->active = 0; + } + + if (my_instance->user && (user = session_getUser(session)) + && strcmp(user, my_instance->user)) + { + my_session->active = 0; + } } return my_session; @@ -278,6 +345,14 @@ REGEX_SESSION *my_session = (REGEX_SESSION *)fsession; dcb_printf(dcb, "\t\tNo. of queries altered by filter: %d\n", my_session->replacements); } + if (my_instance->source) + dcb_printf(dcb, + "\t\tReplacement limited to connections from %s\n", + my_instance->source); + if (my_instance->user) + dcb_printf(dcb, + "\t\tReplacement limit to user %s\n", + my_instance->user); } /** diff --git a/server/modules/filter/tee.c b/server/modules/filter/tee.c new file mode 100644 index 000000000..34efbb65b --- /dev/null +++ b/server/modules/filter/tee.c @@ -0,0 +1,467 @@ +/* + * 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 + */ + +/** + * @file tee.c A filter that splits the processing pipeline in two + * + * Conditionally duplicate requests and send the duplicates to another service + * within MaxScale. + * + * Parameters + * ========== + * + * service The service to send the duplicates to + * source The source address to match in order to duplicate (optional) + * match A regular expression to match in order to perform duplication + * of the request (optional) + * nomatch A regular expression to match in order to prevent duplication + * of the request (optional) + * user A user name to match against. If present only requests that + * originate from this user will be duplciated (optional) + * + * Revision History + * ================ + * + * Date Who Description + * 20/06/2014 Mark Riddoch Initial implementation + * 24/06/2014 Mark Riddoch Addition of support for multi-packet queries + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int lm_enabled_logfiles_bitmask; + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_BETA_RELEASE, + FILTER_VERSION, + "A tee piece in the filter plumbing" +}; + +static char *version_str = "V1.0.0"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + NULL, // No Upstream requirement + routeQuery, + NULL, // No client reply + diagnostic, +}; + +/** + * The instance structure for the TEE filter - this holds the configuration + * information for the filter. + */ +typedef struct { + SERVICE *service; /* The service to duplicate requests to */ + char *source; /* The source of the client connection */ + char *userName; /* The user name to filter on */ + char *match; /* Optional text to match against */ + regex_t re; /* Compiled regex text */ + char *nomatch; /* Optional text to match against for exclusion */ + regex_t nore; /* Compiled regex nomatch text */ +} TEE_INSTANCE; + +/** + * The session structure for this TEE filter. + * This stores the downstream filter information, such that the + * filter is able to pass the query on to the next filter (or router) + * in the chain. + * + * It also holds the file descriptor to which queries are written. + */ +typedef struct { + DOWNSTREAM down; /* The downstream filter */ + int active; /* filter is active? */ + DCB *branch_dcb; /* Client DCB for "branch" service */ + SESSION *branch_session;/* The branch service session */ + int n_duped; /* Number of duplicated queries */ + int n_rejected; /* Number of rejected queries */ + int residual; /* Any outstanding SQL text */ +} TEE_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +TEE_INSTANCE *my_instance; +int i; + + if ((my_instance = calloc(1, sizeof(TEE_INSTANCE))) != NULL) + { + if (options) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "tee: The tee filter has been passed an option, " + "this filter does not support any options.\n"))); + } + my_instance->service = NULL; + my_instance->source = NULL; + my_instance->userName = NULL; + my_instance->match = NULL; + my_instance->nomatch = NULL; + if (params) + { + for (i = 0; params[i]; i++) + { + if (!strcmp(params[i]->name, "service")) + { + if ((my_instance->service = service_find(params[i]->value)) == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "tee: service '%s' " + "not found.\n", + params[i]->value))); + } + } + else if (!strcmp(params[i]->name, "match")) + { + my_instance->match = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "exclude")) + { + my_instance->nomatch = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "source")) + my_instance->source = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "user")) + my_instance->userName = strdup(params[i]->value); + else if (!filter_standard_parameter(params[i]->name)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "tee: Unexpected parameter '%s'.\n", + params[i]->name))); + } + } + } + if (my_instance->service == NULL) + { + free(my_instance->match); + free(my_instance->source); + free(my_instance); + return NULL; + } + if (my_instance->match && + regcomp(&my_instance->re, my_instance->match, REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "tee: Invalid regular expression '%s'" + " for the match parameter.\n", + my_instance->match))); + free(my_instance->match); + free(my_instance->source); + free(my_instance); + return NULL; + } + if (my_instance->nomatch && + regcomp(&my_instance->nore, my_instance->nomatch, + REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "tee: Invalid regular expression '%s'" + " for the nomatch paramter.\n", + my_instance->match))); + if (my_instance->match) + regfree(&my_instance->re); + free(my_instance->match); + free(my_instance->source); + free(my_instance); + return NULL; + } + } + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * Create the file to log to and open it. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +TEE_INSTANCE *my_instance = (TEE_INSTANCE *)instance; +TEE_SESSION *my_session; +char *remote, *userName; + + if ((my_session = calloc(1, sizeof(TEE_SESSION))) != NULL) + { + my_session->active = 1; + my_session->residual = 0; + if (my_instance->source + && (remote = session_get_remote(session)) != NULL) + { + if (strcmp(remote, my_instance->source)) + my_session->active = 0; + } + userName = session_getUser(session); + if (my_instance->userName && userName && strcmp(userName, + my_instance->userName)) + my_session->active = 0; + if (my_session->active) + { + my_session->branch_dcb = dcb_clone(session->client); + my_session->branch_session = session_alloc(my_instance->service, my_session->branch_dcb); + } + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * In the case of the tee filter we need to close down the + * "branch" session. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +TEE_SESSION *my_session = (TEE_SESSION *)session; +ROUTER_OBJECT *router; +void *router_instance, *rsession; +SESSION *bsession; + + if (my_session->active) + { + bsession = my_session->branch_session; + router = bsession->service->router; + router_instance = bsession->service->router_instance; + rsession = bsession->router_session; + /** Close router session and all its connections */ + router->closeSession(router_instance, rsession); + dcb_free(my_session->branch_dcb); + /* No need to free the session, this is done as + * a side effect of closing the client DCB of the + * session. + */ + } +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ +TEE_SESSION *my_session = (TEE_SESSION *)session; + + free(session); + return; +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +TEE_SESSION *my_session = (TEE_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query should normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * If my_session->residual is set then duplicate that many bytes + * and send them to the branch. + * + * If my_session->residual is zero then this must be a new request + * Extract the SQL text if possible, match against that text and forward + * the request. If the requets is not contained witin the packet we have + * then set my_session->residual to the number of outstanding bytes + * + * @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) +{ +TEE_INSTANCE *my_instance = (TEE_INSTANCE *)instance; +TEE_SESSION *my_session = (TEE_SESSION *)session; +char *ptr; +int length, rval, residual; +GWBUF *clone = NULL; + + if (my_session->residual) + { + clone = gwbuf_clone(queue); + if (my_session->residual < GWBUF_LENGTH(clone)) + GWBUF_RTRIM(clone, GWBUF_LENGTH(clone) - residual); + my_session->residual -= GWBUF_LENGTH(clone); + if (my_session->residual < 0) + my_session->residual = 0; + } + else if (my_session->active && + modutil_MySQL_Query(queue, &ptr, &length, &residual)) + { + if ((my_instance->match == NULL || + regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) && + (my_instance->nomatch == NULL || + regexec(&my_instance->nore,ptr,0,NULL, 0) != 0)) + { + clone = gwbuf_clone(queue); + my_session->residual = residual; + } + } + + /* Pass the query downstream */ + rval = my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); + if (clone) + { + my_session->n_duped++; + SESSION_ROUTE_QUERY(my_session->branch_session, clone); + } + else + { + my_session->n_rejected++; + } + 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) +{ +TEE_INSTANCE *my_instance = (TEE_INSTANCE *)instance; +TEE_SESSION *my_session = (TEE_SESSION *)fsession; + + if (my_instance->source) + dcb_printf(dcb, "\t\tLimit to connections from %s\n", + my_instance->source); + dcb_printf(dcb, "\t\tDuplicate statements to service %s\n", + my_instance->service->name); + if (my_instance->userName) + dcb_printf(dcb, "\t\tLimit to user %s\n", + my_instance->userName); + if (my_instance->match) + dcb_printf(dcb, "\t\tInclude queries that match %s\n", + my_instance->match); + if (my_instance->nomatch) + dcb_printf(dcb, "\t\tExclude queries that match %s\n", + my_instance->nomatch); + if (my_session) + { + dcb_printf(dcb, "\t\tNo. of statements duplicated: %d.\n", + my_session->n_duped); + dcb_printf(dcb, "\t\tNo. of statements rejected: %d.\n", + my_session->n_rejected); + } +} diff --git a/server/modules/filter/testfilter.c b/server/modules/filter/testfilter.c index 270dbd1cb..f72471a36 100644 --- a/server/modules/filter/testfilter.c +++ b/server/modules/filter/testfilter.c @@ -32,7 +32,7 @@ MODULE_INFO info = { MODULE_API_FILTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, FILTER_VERSION, "A simple query counting filter" }; @@ -54,7 +54,9 @@ static FILTER_OBJECT MyObject = { closeSession, freeSession, setDownstream, + NULL, // No upstream requirement routeQuery, + NULL, diagnostic, }; diff --git a/server/modules/filter/topfilter.c b/server/modules/filter/topfilter.c new file mode 100644 index 000000000..d4d594e6d --- /dev/null +++ b/server/modules/filter/topfilter.c @@ -0,0 +1,578 @@ +/* + * 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 + */ + +/** + * TOPN Filter - Query Log All. A primitive query logging filter, simply + * used to verify the filter mechanism for downstream filters. All queries + * that are passed through the filter will be written to file. + * + * The filter makes no attempt to deal with query packets that do not fit + * in a single GWBUF. + * + * A single option may be passed to the filter, this is the name of the + * file to which the queries are logged. A serial number is appended to this + * name in order that each session logs to a different file. + * + * Date Who Description + * 18/06/2014 Mark Riddoch Addition of source and user filters + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int lm_enabled_logfiles_bitmask; + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_BETA_RELEASE, + FILTER_VERSION, + "A top N query logging filter" +}; + +static char *version_str = "V1.0.1"; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static int clientReply(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostic, +}; + +/** + * A instance structure, the assumption is that the option passed + * to the filter is simply a base for the filename to which the queries + * are logged. + * + * To this base a session number is attached such that each session will + * have a unique name. + */ +typedef struct { + int sessions; /* Session count */ + int topN; /* Number of queries to store */ + char *filebase; /* Base of fielname to log into */ + char *source; /* The source of the client connection */ + char *user; /* A user name to filter on */ + char *match; /* Optional text to match against */ + regex_t re; /* Compiled regex text */ + char *exclude; /* Optional text to match against for exclusion */ + regex_t exre; /* Compiled regex nomatch text */ +} TOPN_INSTANCE; + +/** + * Structure to hold the Top N queries + */ +typedef struct topnq { + struct timeval duration; + char *sql; +} TOPNQ; + +/** + * The session structure for this TOPN filter. + * This stores the downstream filter information, such that the + * filter is able to pass the query on to the next filter (or router) + * in the chain. + * + * It also holds the file descriptor to which queries are written. + */ +typedef struct { + DOWNSTREAM down; + UPSTREAM up; + int active; + char *clientHost; + char *userName; + char *filename; + int fd; + struct timeval start; + char *current; + TOPNQ **top; + int n_statements; + struct timeval total; + struct timeval connect; + struct timeval disconnect; +} TOPN_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +int i; +TOPN_INSTANCE *my_instance; + + if ((my_instance = calloc(1, sizeof(TOPN_INSTANCE))) != NULL) + { + my_instance->topN = 10; + my_instance->match = NULL; + my_instance->exclude = NULL; + my_instance->source = NULL; + my_instance->user = NULL; + my_instance->filebase = strdup("top"); + for (i = 0; params && params[i]; i++) + { + if (!strcmp(params[i]->name, "count")) + my_instance->topN = atoi(params[i]->value); + else if (!strcmp(params[i]->name, "filebase")) + { + free(my_instance->filebase); + my_instance->filebase = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "match")) + { + my_instance->match = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "exclude")) + { + my_instance->exclude = strdup(params[i]->value); + } + else if (!strcmp(params[i]->name, "source")) + my_instance->source = strdup(params[i]->value); + else if (!strcmp(params[i]->name, "user")) + my_instance->user = strdup(params[i]->value); + else if (!filter_standard_parameter(params[i]->name)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "topfilter: Unexpected parameter '%s'.\n", + params[i]->name))); + } + } + if (options) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "topfilter: Options are not supported by this " + " filter. They will be ignored\n"))); + } + my_instance->sessions = 0; + if (my_instance->match && + regcomp(&my_instance->re, my_instance->match, REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "topfilter: Invalid regular expression '%s'" + " for the match parameter.\n", + my_instance->match))); + free(my_instance->match); + free(my_instance->source); + free(my_instance->user); + free(my_instance->filebase); + free(my_instance); + return NULL; + } + if (my_instance->exclude && + regcomp(&my_instance->exre, my_instance->exclude, + REG_ICASE)) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "qlafilter: Invalid regular expression '%s'" + " for the nomatch paramter.\n", + my_instance->match))); + regfree(&my_instance->re); + free(my_instance->match); + free(my_instance->source); + free(my_instance->user); + free(my_instance->filebase); + free(my_instance); + return NULL; + } + } + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * Create the file to log to and open it. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +TOPN_INSTANCE *my_instance = (TOPN_INSTANCE *)instance; +TOPN_SESSION *my_session; +int i; +char *remote, *user; + + if ((my_session = calloc(1, sizeof(TOPN_SESSION))) != NULL) + { + if ((my_session->filename = + (char *)malloc(strlen(my_instance->filebase) + 20)) + == NULL) + { + free(my_session); + return NULL; + } + sprintf(my_session->filename, "%s.%d", my_instance->filebase, + my_instance->sessions); + my_instance->sessions++; + my_session->top = (TOPNQ **)calloc(my_instance->topN + 1, + sizeof(TOPNQ *)); + for (i = 0; i < my_instance->topN; i++) + { + my_session->top[i] = (TOPNQ *)calloc(1, sizeof(TOPNQ)); + my_session->top[i]->sql = NULL; + } + my_session->n_statements = 0; + my_session->total.tv_sec = 0; + my_session->total.tv_usec = 0; + my_session->current = NULL; + if ((remote = session_get_remote(session)) != NULL) + my_session->clientHost = strdup(remote); + else + my_session->clientHost = NULL; + if ((user = session_getUser(session)) != NULL) + my_session->userName = strdup(user); + else + my_session->userName = NULL; + my_session->active = 1; + if (my_instance->source && strcmp(my_session->clientHost, + my_instance->source)) + my_session->active = 0; + if (my_instance->user && strcmp(my_session->userName, + my_instance->user)) + my_session->active = 0; + + sprintf(my_session->filename, "%s.%d", my_instance->filebase, + my_instance->sessions); + gettimeofday(&my_session->connect, NULL); + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * In the case of the TOPN filter we simple close the file descriptor. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +TOPN_INSTANCE *my_instance = (TOPN_INSTANCE *)instance; +TOPN_SESSION *my_session = (TOPN_SESSION *)session; +struct timeval diff; +int i; +FILE *fp; + + gettimeofday(&my_session->disconnect, NULL); + timersub((&my_session->disconnect), &(my_session->connect), &diff); + if ((fp = fopen(my_session->filename, "w")) != NULL) + { + fprintf(fp, "Top %d longest running queries in session.\n", + my_instance->topN); + fprintf(fp, "==========================================\n\n"); + fprintf(fp, "Time (sec) | Query\n"); + fprintf(fp, "-----------+-----------------------------------------------------------------\n"); + for (i = 0; i < my_instance->topN; i++) + { + if (my_session->top[i]->sql) + { + fprintf(fp, "%10.3f | %s\n", + (double)((my_session->top[i]->duration.tv_sec * 1000) + + (my_session->top[i]->duration.tv_usec / 1000)) / 1000, + my_session->top[i]->sql); + } + } + fprintf(fp, "-----------+-----------------------------------------------------------------\n"); + fprintf(fp, "\n\nSession started %s", + asctime(localtime(&my_session->connect.tv_sec))); + if (my_session->clientHost) + fprintf(fp, "Connection from %s\n", + my_session->clientHost); + if (my_session->userName) + fprintf(fp, "Username %s\n", + my_session->userName); + fprintf(fp, "\nTotal of %d statements executed.\n", + my_session->n_statements); + fprintf(fp, "Total statement execution time %5d.%d seconds\n", + (int)my_session->total.tv_sec, + (int)my_session->total.tv_usec / 1000); + fprintf(fp, "Average statement execution time %9.3f seconds\n", + (double)((my_session->total.tv_sec * 1000) + + (my_session->total.tv_usec / 1000)) + / (1000 * my_session->n_statements)); + fprintf(fp, "Total connection time %5d.%d seconds\n", + (int)diff.tv_sec, (int)diff.tv_usec / 1000); + fclose(fp); + } +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ +TOPN_SESSION *my_session = (TOPN_SESSION *)session; + + free(my_session->filename); + free(session); + return; +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +TOPN_SESSION *my_session = (TOPN_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * Set the upstream filter or session to which results will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param upstream The upstream filter or session. + */ +static void +setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) +{ +TOPN_SESSION *my_session = (TOPN_SESSION *)session; + + my_session->up = *upstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query should normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param queue The query data + */ +static int +routeQuery(FILTER *instance, void *session, GWBUF *queue) +{ +TOPN_INSTANCE *my_instance = (TOPN_INSTANCE *)instance; +TOPN_SESSION *my_session = (TOPN_SESSION *)session; +char *ptr; +int length; + + if (my_session->active && modutil_extract_SQL(queue, &ptr, &length)) + { + if ((my_instance->match == NULL || + regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) && + (my_instance->exclude == NULL || + regexec(&my_instance->exre,ptr,0,NULL, 0) != 0)) + { + my_session->n_statements++; + if (my_session->current) + free(my_session->current); + gettimeofday(&my_session->start, NULL); + my_session->current = strndup(ptr, length); + } + } + + /* Pass the query downstream */ + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +static int +cmp_topn(TOPNQ **a, TOPNQ **b) +{ + if ((*b)->duration.tv_sec == (*a)->duration.tv_sec) + return (*b)->duration.tv_usec - (*a)->duration.tv_usec; + return (*b)->duration.tv_sec - (*a)->duration.tv_sec; +} + +static int +clientReply(FILTER *instance, void *session, GWBUF *reply) +{ +TOPN_INSTANCE *my_instance = (TOPN_INSTANCE *)instance; +TOPN_SESSION *my_session = (TOPN_SESSION *)session; +struct timeval tv, diff; +int i, inserted; + + if (my_session->current) + { + gettimeofday(&tv, NULL); + timersub(&tv, &(my_session->start), &diff); + + timeradd(&(my_session->total), &diff, &(my_session->total)); + + inserted = 0; + for (i = 0; i < my_instance->topN; i++) + { + if (my_session->top[i]->sql == NULL) + { + my_session->top[i]->sql = my_session->current; + my_session->top[i]->duration = diff; + inserted = 1; + break; + } + } + + if (inserted == 0 && ((diff.tv_sec > my_session->top[my_instance->topN-1]->duration.tv_sec) || (diff.tv_sec == my_session->top[my_instance->topN-1]->duration.tv_sec && diff.tv_usec > my_session->top[my_instance->topN-1]->duration.tv_usec ))) + { + free(my_session->top[my_instance->topN-1]->sql); + my_session->top[my_instance->topN-1]->sql = my_session->current; + my_session->top[my_instance->topN-1]->duration = diff; + inserted = 1; + } + + if (inserted) + qsort(my_session->top, my_instance->topN, + sizeof(TOPNQ *), cmp_topn); + else + free(my_session->current); + my_session->current = NULL; + } + + /* Pass the result upstream */ + return my_session->up.clientReply(my_session->up.instance, + my_session->up.session, reply); +} + +/** + * 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) +{ +TOPN_INSTANCE *my_instance = (TOPN_INSTANCE *)instance; +TOPN_SESSION *my_session = (TOPN_SESSION *)fsession; +int i; + + dcb_printf(dcb, "\t\tReport size %d\n", + my_instance->topN); + if (my_instance->source) + dcb_printf(dcb, "\t\tLimit logging to connections from %s\n", + my_instance->source); + if (my_instance->user) + dcb_printf(dcb, "\t\tLimit logging to user %s\n", + my_instance->user); + if (my_instance->match) + dcb_printf(dcb, "\t\tInclude queries that match %s\n", + my_instance->match); + if (my_instance->exclude) + dcb_printf(dcb, "\t\tExclude queries that match %s\n", + my_instance->exclude); + if (my_session) + { + dcb_printf(dcb, "\t\tLogging to file %s.\n", + my_session->filename); + dcb_printf(dcb, "\t\tCurrent Top %d:\n", my_instance->topN); + for (i = 0; i < my_instance->topN; i++) + { + if (my_session->top[i]->sql) + { + dcb_printf(dcb, "\t\t%d place:\n", i + 1); + dcb_printf(dcb, "\t\t\tExecution time: %.3f seconds\n", + (double)((my_session->top[i]->duration.tv_sec * 1000) + + (my_session->top[i]->duration.tv_usec / 1000)) / 1000); + dcb_printf(dcb, "\t\t\tSQL: %s\n", + my_session->top[i]->sql); + } + } + } +} diff --git a/server/modules/include/maxscaled.h b/server/modules/include/maxscaled.h new file mode 100644 index 000000000..3733772d8 --- /dev/null +++ b/server/modules/include/maxscaled.h @@ -0,0 +1,46 @@ +#ifndef _MAXSCALED_H +#define _MAXSCALED_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 maxscaled.h The maxscaled protocol module header file + * + * @verbatim + * Revision History + * + * Date Who Description + * 13/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include + +/** + * The telnetd specific protocol structure to put in the DCB. + */ +typedef struct maxscaled { + int state; /**< The connection state */ + char *username; /**< The login name of the user */ +} MAXSCALED; + +#define MAXSCALED_STATE_LOGIN 1 /**< Issued login prompt */ +#define MAXSCALED_STATE_PASSWD 2 /**< Issued password prompt */ +#define MAXSCALED_STATE_DATA 3 /**< User logged in */ + +#endif diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 41bcf0416..9e2147d05 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -66,6 +66,7 @@ #define GW_MYSQL_LOOP_TIMEOUT 300000000 #define GW_MYSQL_READ 0 #define GW_MYSQL_WRITE 1 +#define MYSQL_HEADER_LEN 4L #define GW_MYSQL_PROTOCOL_VERSION 10 // version is 10 #define GW_MYSQL_HANDSHAKE_FILLER 0x00 @@ -77,7 +78,7 @@ #define GW_SCRAMBLE_LENGTH_323 8 #ifndef MYSQL_SCRAMBLE_LEN -#define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE +# define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE #endif #define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR) @@ -88,47 +89,20 @@ #define SMALL_CHUNK 1024 #define MAX_CHUNK SMALL_CHUNK * 8 * 4 #define ToHex(Y) (Y>='0'&&Y<='9'?Y-'0':Y-'A'+10) - +#define COM_QUIT_PACKET_SIZE (4+1) struct dcb; typedef enum { - MYSQL_ALLOC, - MYSQL_PENDING_CONNECT, - MYSQL_CONNECTED, - MYSQL_AUTH_SENT, - MYSQL_AUTH_RECV, - MYSQL_AUTH_FAILED, - MYSQL_IDLE, - MYSQL_ROUTING, - MYSQL_WAITING_RESULT, - MYSQL_SESSION_CHANGE -} mysql_pstate_t; + MYSQL_ALLOC, + MYSQL_PENDING_CONNECT, + MYSQL_CONNECTED, + MYSQL_AUTH_SENT, + MYSQL_AUTH_RECV, + MYSQL_AUTH_FAILED, + MYSQL_IDLE +} mysql_auth_state_t; -/* - * MySQL Protocol specific state data - */ -typedef struct { -#if defined(SS_DEBUG) - skygw_chk_t protocol_chk_top; -#endif - int fd; /*< The socket descriptor */ - struct dcb *owner_dcb; /*< The DCB of the socket - * we are running on */ - mysql_pstate_t state; /*< Current protocol state */ - uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, - * created or received */ - uint32_t server_capabilities; /*< server capabilities, - * created or received */ - uint32_t client_capabilities; /*< client capabilities, - * created or received */ - unsigned long tid; /*< MySQL Thread ID, in - * handshake */ -#if defined(SS_DEBUG) - skygw_chk_t protocol_chk_tail; -#endif -} MySQLProtocol; - /* * MySQL session specific data * @@ -140,7 +114,6 @@ typedef struct mysql_session { } MYSQL_session; - /** Protocol packing macros. */ #define gw_mysql_set_byte2(__buffer, __int) do { \ (__buffer)[0]= (uint8_t)((__int) & 0xFF); \ @@ -231,20 +204,102 @@ typedef enum ), } gw_mysql_capabilities_t; -/** Basic mysql commands */ -#define MYSQL_COM_CHANGE_USER 0x11 -#define MYSQL_COM_QUIT 0x1 -#define MYSQL_COM_INIT_DB 0x2 -#define MYSQL_COM_QUERY 0x3 +/** Copy from enum in mariadb-5.5 mysql_com.h */ +typedef enum mysql_server_cmd { + MYSQL_COM_UNDEFINED = -1, + MYSQL_COM_SLEEP = 0, + MYSQL_COM_QUIT, + MYSQL_COM_INIT_DB, + MYSQL_COM_QUERY, + MYSQL_COM_FIELD_LIST, + MYSQL_COM_CREATE_DB, + MYSQL_COM_DROP_DB, + MYSQL_COM_REFRESH, + MYSQL_COM_SHUTDOWN, + MYSQL_COM_STATISTICS, + MYSQL_COM_PROCESS_INFO, + MYSQL_COM_CONNECT, + MYSQL_COM_PROCESS_KILL, + MYSQL_COM_DEBUG, + MYSQL_COM_PING, + MYSQL_COM_TIME, + MYSQL_COM_DELAYED_INSERT, + MYSQL_COM_CHANGE_USER, + MYSQL_COM_BINLOG_DUMP, + MYSQL_COM_TABLE_DUMP, + MYSQL_COM_CONNECT_OUT, + MYSQL_COM_REGISTER_SLAVE, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_CLOSE, + MYSQL_COM_STMT_RESET, + MYSQL_COM_SET_OPTION, + MYSQL_COM_STMT_FETCH, + MYSQL_COM_DAEMON, + MYSQL_COM_END /*< Must be the last */ +} mysql_server_cmd_t; -#define MYSQL_GET_COMMAND(payload) (payload[4]) -#define MYSQL_GET_PACKET_NO(payload) (payload[3]) -#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) +/** + * List of server commands, and number of response packets are stored here. + * server_command_t is used in MySQLProtocol structure, so for each DCB there is + * one MySQLProtocol and one server command list. + */ +typedef struct server_command_st { + mysql_server_cmd_t scom_cmd; + int scom_nresponse_packets; /*< packets in response */ + size_t scom_nbytes_to_read; /*< bytes left to read in current packet */ + struct server_command_st* scom_next; +} server_command_t; + +/** + * MySQL Protocol specific state data. + * + * Protocol carries information from client side to backend side, such as + * MySQL session command information and history of earlier session commands. + */ +typedef struct { +#if defined(SS_DEBUG) + skygw_chk_t protocol_chk_top; #endif + int fd; /*< The socket descriptor */ + struct dcb *owner_dcb; /*< The DCB of the socket + * we are running on */ + SPINLOCK protocol_lock; + server_command_t protocol_command; /*< session command list */ + server_command_t* protocol_cmd_history; /*< session command history */ + mysql_auth_state_t protocol_auth_state; /*< Authentication status */ + uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, + * created or received */ + uint32_t server_capabilities; /*< server capabilities, + * created or received */ + uint32_t client_capabilities; /*< client capabilities, + * created or received */ + unsigned long tid; /*< MySQL Thread ID, in + * handshake */ +#if defined(SS_DEBUG) + skygw_chk_t protocol_chk_tail; +#endif +} MySQLProtocol; + + + +#define MYSQL_GET_COMMAND(payload) (payload[4]) +#define MYSQL_GET_PACKET_NO(payload) (payload[3]) +#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload)) +#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5])) +#define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9])) +#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) +#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) +#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) +#define MYSQL_GET_NATTR(payload) ((int)payload[4]) + +#endif /** _MYSQL_PROTOCOL_H */ void gw_mysql_close(MySQLProtocol **ptr); MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd); +void mysql_protocol_done (DCB* dcb); MySQLProtocol *gw_mysql_init(MySQLProtocol *data); void gw_mysql_close(MySQLProtocol **ptr); int gw_receive_backend_auth(MySQLProtocol *protocol); @@ -256,12 +311,21 @@ int gw_send_authentication_to_backend( uint8_t *passwd, MySQLProtocol *protocol); const char *gw_mysql_protocol_state2string(int state); -int gw_do_connect_to_backend(char *host, int port, int* fd); +int gw_do_connect_to_backend(char *host, int port, int* fd); +int mysql_send_com_quit(DCB* dcb, int packet_number, GWBUF* buf); +GWBUF* mysql_create_com_quit(GWBUF* bufparam, int packet_number); + int mysql_send_custom_error ( DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); + +GWBUF* mysql_create_custom_error( + int packet_number, + int affected_rows, + const char* msg); + int gw_send_change_user_to_backend( char *dbname, char *user, @@ -297,12 +361,30 @@ void gw_str_xor( const uint8_t *input1, const uint8_t *input2, unsigned int len); -char *gw_bin2hex(char *out, const uint8_t *in, unsigned int len); -int gw_hex2bin(uint8_t *out, const char *in, unsigned int len); -int gw_generate_random_str(char *output, int len); -char *gw_strend(register const char *s); -int setnonblocking(int fd); -int setipaddress(struct in_addr *a, char *p); -int gw_read_gwbuff(DCB *dcb, GWBUF **head, int b); -GWBUF* gw_MySQL_get_next_packet(GWBUF** p_readbuf); + +char *gw_bin2hex(char *out, const uint8_t *in, unsigned int len); +int gw_hex2bin(uint8_t *out, const char *in, unsigned int len); +int gw_generate_random_str(char *output, int len); +char *gw_strend(register const char *s); +int setnonblocking(int fd); +int setipaddress(struct in_addr *a, char *p); +GWBUF* gw_MySQL_get_next_packet(GWBUF** p_readbuf); +GWBUF* gw_MySQL_get_packets(GWBUF** p_readbuf, int* npackets); +GWBUF* gw_MySQL_discard_packets(GWBUF* buf, int npackets); +void protocol_add_srv_command(MySQLProtocol* p, mysql_server_cmd_t cmd); +void protocol_remove_srv_command(MySQLProtocol* p); +bool protocol_waits_response(MySQLProtocol* p); +mysql_server_cmd_t protocol_get_srv_command(MySQLProtocol* p,bool removep); +int get_stmt_nresponse_packets(GWBUF* buf, mysql_server_cmd_t cmd); +bool protocol_get_response_status (MySQLProtocol* p, int* npackets, size_t* nbytes); +void protocol_set_response_status (MySQLProtocol* p, int npackets, size_t nbytes); +void protocol_archive_srv_command(MySQLProtocol* p); + + +void init_response_status ( + GWBUF* buf, + mysql_server_cmd_t cmd, + int* npackets, + size_t* nbytes); + diff --git a/server/modules/include/readconnection.h b/server/modules/include/readconnection.h index c6f19995c..589296302 100644 --- a/server/modules/include/readconnection.h +++ b/server/modules/include/readconnection.h @@ -26,6 +26,7 @@ * * Date Who Description * 14/06/13 Mark Riddoch Initial implementation + * 27/06/14 Mark Riddoch Addition of server weight percentage * * @endverbatim */ @@ -39,6 +40,7 @@ typedef struct backend { SERVER *server; /*< The server itself */ int current_connection_count; /*< Number of connections to the server */ + int weight; /*< Desired routing weight */ } BACKEND; /** diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 00857ae1b..6fc639005 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -30,6 +30,38 @@ */ #include +#include + +#undef PREP_STMT_CACHING + +#if defined(PREP_STMT_CACHING) + +typedef enum prep_stmt_type { + PREP_STMT_NAME, + PREP_STMT_ID +} prep_stmt_type_t; + +typedef enum prep_stmt_state { + PREP_STMT_ALLOC, + PREP_STMT_SENT, + PREP_STMT_RECV, + PREP_STMT_DROPPED +} prep_stmt_state_t; + +#endif /*< PREP_STMT_CACHING */ + +typedef enum bref_state { + BREF_IN_USE = 0x01, + BREF_WAITING_RESULT = 0x02, /*< for session commands only */ + BREF_QUERY_ACTIVE = 0x04, /*< for other queries */ + BREF_CLOSED = 0x08 +} bref_state_t; + +#define BREF_IS_NOT_USED(s) (s->bref_state & ~BREF_IN_USE) +#define BREF_IS_IN_USE(s) (s->bref_state & BREF_IN_USE) +#define BREF_IS_WAITING_RESULT(s) (s->bref_num_result_wait > 0) +#define BREF_IS_QUERY_ACTIVE(s) (s->bref_state & BREF_QUERY_ACTIVE) +#define BREF_IS_CLOSED(s) (s->bref_state & BREF_CLOSED) typedef enum backend_type_t { BE_UNDEFINED=-1, @@ -43,8 +75,8 @@ typedef struct rses_property_st rses_property_t; typedef struct router_client_session ROUTER_CLIENT_SES; typedef enum rses_property_type_t { - RSES_PROP_TYPE_UNDEFINED=0, - RSES_PROP_TYPE_SESCMD, + RSES_PROP_TYPE_UNDEFINED=-1, + RSES_PROP_TYPE_SESCMD=0, RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD, RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_SESCMD, RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1 @@ -60,15 +92,17 @@ typedef enum rses_property_type_t { typedef enum select_criteria { UNDEFINED_CRITERIA=0, LEAST_GLOBAL_CONNECTIONS, /*< all connections established by MaxScale */ - DEFAULT_CRITERIA=LEAST_GLOBAL_CONNECTIONS, LEAST_ROUTER_CONNECTIONS, /*< connections established by this router */ LEAST_BEHIND_MASTER, + LEAST_CURRENT_OPERATIONS, + DEFAULT_CRITERIA=LEAST_CURRENT_OPERATIONS, LAST_CRITERIA /*< not used except for an index */ } select_criteria_t; /** default values for rwsplit configuration parameters */ #define CONFIG_MAX_SLAVE_CONN 1 +#define CONFIG_MAX_SLAVE_RLAG -1 /*< not used */ #define GET_SELECT_CRITERIA(s) \ (strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \ @@ -76,7 +110,9 @@ typedef enum select_criteria { strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \ LEAST_BEHIND_MASTER : ( \ strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \ - LEAST_ROUTER_CONNECTIONS : UNDEFINED_CRITERIA))) + LEAST_ROUTER_CONNECTIONS : ( \ + strncmp(s,"LEAST_CURRENT_OPERATIONS", strlen("LEAST_CURRENT_OPERATIONS")) == 0 ? \ + LEAST_CURRENT_OPERATIONS : UNDEFINED_CRITERIA)))) /** * Session variable command @@ -139,9 +175,17 @@ typedef struct backend_st { #if defined(SS_DEBUG) skygw_chk_t be_chk_top; #endif - SERVER* backend_server; /*< The server itself */ - int backend_conn_count; /*< Number of connections to the server */ - bool be_valid; /*< valid when belongs to the router's configuration */ + SERVER* backend_server; /*< The server itself */ + int backend_conn_count; /*< Number of connections to + * the server + */ + bool be_valid; /*< Valid when belongs to the + * router's configuration + */ + int weight; /*< Desired weighting on the + * load. Expressed in .1% + * increments + */ #if defined(SS_DEBUG) skygw_chk_t be_chk_tail; #endif @@ -159,6 +203,8 @@ typedef struct backend_ref_st { #endif BACKEND* bref_backend; DCB* bref_dcb; + bref_state_t bref_state; + int bref_num_result_wait; sescmd_cursor_t bref_sescmd_cur; #if defined(SS_DEBUG) skygw_chk_t bref_chk_tail; @@ -170,9 +216,29 @@ typedef struct rwsplit_config_st { int rw_max_slave_conn_percent; int rw_max_slave_conn_count; select_criteria_t rw_slave_select_criteria; + int rw_max_slave_replication_lag; } rwsplit_config_t; +#if defined(PREP_STMT_CACHING) + +typedef struct prep_stmt_st { +#if defined(SS_DEBUG) + skygw_chk_t pstmt_chk_top; +#endif + union id { + int seq; + char* name; + } pstmt_id; + prep_stmt_state_t pstmt_state; + prep_stmt_type_t pstmt_type; +#if defined(SS_DEBUG) + skygw_chk_t pstmt_chk_tail; +#endif +} prep_stmt_t; + +#endif /*< PREP_STMT_CACHING */ + /** * The client session structure used within this router. */ @@ -192,7 +258,9 @@ struct router_client_session { int rses_capabilities; /*< input type, for example */ bool rses_autocommit_enabled; bool rses_transaction_active; - uint64_t rses_id; /*< ID for router client session */ +#if defined(PREP_STMT_CACHING) + HASHTABLE* rses_prep_stmt[2]; +#endif struct router_client_session* next; #if defined(SS_DEBUG) skygw_chk_t rses_chk_tail; diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile index 45a56a916..7fdbc5843 100644 --- a/server/modules/monitor/Makefile +++ b/server/modules/monitor/Makefile @@ -65,6 +65,6 @@ depend: cc -M $(CFLAGS) $(SRCS) > depend.mk install: $(MODULES) - install -D $(MODULES) $(DEST)/MaxScale/modules + install -D $(MODULES) $(DEST)/modules include depend.mk diff --git a/server/modules/monitor/galera_mon.c b/server/modules/monitor/galera_mon.c index a9f242756..3cba61325 100644 --- a/server/modules/monitor/galera_mon.c +++ b/server/modules/monitor/galera_mon.c @@ -29,6 +29,7 @@ * 23/05/14 Massimiliano Pinto Added 1 configuration option (setInterval). * Interval is printed in diagnostics. * 03/06/14 Mark Riddoch Add support for maintenance mode + * 24/06/14 Massimiliano Pinto Added depth level 0 for each node * * @endverbatim */ @@ -55,7 +56,7 @@ static char *version_str = "V1.2.0"; MODULE_INFO info = { MODULE_API_MONITOR, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, MONITOR_VERSION, "A Galera cluster monitor" }; @@ -434,6 +435,7 @@ long master_id; /* set master_id to the lowest value of ptr->server->node_id */ if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) { + ptr->server->depth = 0; if (ptr->server->node_id < master_id && master_id >= 0) { master_id = ptr->server->node_id; } else { @@ -445,6 +447,7 @@ long master_id; /* clear M/S status */ server_clear_status(ptr->server, SERVER_SLAVE); server_clear_status(ptr->server, SERVER_MASTER); + ptr->server->depth = -1; } if (ptr->server->status != prev_status || SERVER_IS_DOWN(ptr->server)) diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index d643a00b9..ee5b4eeb7 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -33,6 +33,13 @@ * 28/05/14 Massimiliano Pinto Added set Id and configuration options (setInverval) * Parameters are now printed in diagnostics * 03/06/14 Mark Ridoch Add support for maintenance mode + * 17/06/14 Massimiliano Pinto Addition of getServerByNodeId routine + * and first implementation for depth of replication for nodes. + * 23/06/14 Massimiliano Pinto Added replication consistency after replication tree computation + * 27/06/14 Massimiliano Pinto Added replication pending status in monitored server, storing there + * the status to update in server status field before + * starting the replication consistency check. + * This will also give routers a consistent "status" of all servers * * @endverbatim */ @@ -59,7 +66,7 @@ static char *version_str = "V1.2.0"; MODULE_INFO info = { MODULE_API_MONITOR, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, MONITOR_VERSION, "A MySQL Master/Slave replication monitor" }; @@ -73,6 +80,16 @@ static void diagnostics(DCB *, void *); static void setInterval(void *, unsigned long); static void defaultId(void *, unsigned long); static void replicationHeartbeat(void *, int); +static bool mon_status_changed(MONITOR_SERVERS* mon_srv); +static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv); +static MONITOR_SERVERS *getServerByNodeId(MONITOR_SERVERS *, long); +static MONITOR_SERVERS *getSlaveOfNodeId(MONITOR_SERVERS *, long); +static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *, int); +static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); +static void set_slave_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *); +static int add_slave_to_master(long *, int, long); +static void monitor_set_pending_status(MONITOR_SERVERS *, int); +static void monitor_clear_pending_status(MONITOR_SERVERS *, int); static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUser, diagnostics, setInterval, defaultId, replicationHeartbeat }; @@ -142,6 +159,8 @@ MYSQL_MONITOR *handle; handle->defaultPasswd = NULL; handle->id = MONITOR_DEFAULT_ID; handle->interval = MONITOR_INTERVAL; + handle->replicationHeartbeat = 0; + handle->master = NULL; spinlock_init(&handle->lock); } handle->tid = (THREAD)thread_start(monitorMain, handle); @@ -180,7 +199,13 @@ MONITOR_SERVERS *ptr, *db; db->server = server; db->con = NULL; db->next = NULL; + db->mon_err_count = 0; + db->mon_prev_status = 0; + /* pending status is updated by get_replication_tree */ + db->pending_status = 0; + spinlock_acquire(&handle->lock); + if (handle->databases == NULL) handle->databases = db; else @@ -307,21 +332,21 @@ char *sep; static void monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { -MYSQL_ROW row; -MYSQL_RES *result; -int num_fields; -int ismaster = 0, isslave = 0; -char *uname = handle->defaultUser, *passwd = handle->defaultPasswd; -unsigned long int server_version = 0; -char *server_string; -unsigned long id = handle->id; -int replication_heartbeat = handle->replicationHeartbeat; +MYSQL_ROW row; +MYSQL_RES *result; +int num_fields; +int isslave = 0; +char *uname = handle->defaultUser; +char *passwd = handle->defaultPasswd; +unsigned long int server_version = 0; +char *server_string; - if (database->server->monuser != NULL) + if (database->server->monuser != NULL) { uname = database->server->monuser; passwd = database->server->monpw; } + if (uname == NULL) return; @@ -329,12 +354,17 @@ int replication_heartbeat = handle->replicationHeartbeat; if (SERVER_IN_MAINT(database->server)) return; + /** Store prevous status */ + database->mon_prev_status = database->server->status; + if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); int rc; int read_timeout = 1; - database->con = mysql_init(NULL); + + database->con = mysql_init(NULL); + rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); if (mysql_real_connect(database->con, @@ -346,23 +376,40 @@ int replication_heartbeat = handle->replicationHeartbeat; NULL, 0) == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Monitor was unable to connect to " - "server %s:%d : \"%s\"", - database->server->name, - database->server->port, - mysql_error(database->con)))); + free(dpwd); - free(dpwd); + if (mon_print_fail_status(database)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Monitor was unable to connect to " + "server %s:%d : \"%s\"", + database->server->name, + database->server->port, + mysql_error(database->con)))); + } + + /* The current server is not running + * + * Store server NOT running in server and monitor server pending struct + * + */ server_clear_status(database->server, SERVER_RUNNING); + monitor_clear_pending_status(database, SERVER_RUNNING); + + /* Also clear M/S state in both server and monitor server pending struct */ + server_clear_status(database->server, SERVER_SLAVE); + server_clear_status(database->server, SERVER_MASTER); + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); + return; } free(dpwd); } - - /* If we get this far then we have a working connection */ + /* Store current status in both server and monitor server pending struct */ server_set_status(database->server, SERVER_RUNNING); + monitor_set_pending_status(database, SERVER_RUNNING); /* get server version from current server */ server_version = mysql_get_server_version(database->con); @@ -392,121 +439,6 @@ int replication_heartbeat = handle->replicationHeartbeat; mysql_free_result(result); } - /* Check SHOW SLAVE HOSTS - if we get rows then we are a master */ - if (mysql_query(database->con, "SHOW SLAVE HOSTS")) - { - if (mysql_errno(database->con) == ER_SPECIFIC_ACCESS_DENIED_ERROR) - { - /* Log lack of permission */ - } - - database->server->rlag = -1; - } else if ((result = mysql_store_result(database->con)) != NULL) { - num_fields = mysql_num_fields(result); - while ((row = mysql_fetch_row(result))) - { - ismaster = 1; - } - mysql_free_result(result); - - if (ismaster && replication_heartbeat == 1) { - time_t heartbeat; - time_t purge_time; - char heartbeat_insert_query[128]=""; - char heartbeat_purge_query[128]=""; - - handle->master_id = database->server->node_id; - - /* create the maxscale_schema database */ - if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: Error creating maxscale_schema database in Master server" - ": %s", mysql_error(database->con)))); - - database->server->rlag = -1; - } - - /* create repl_heartbeat table in maxscale_schema database */ - if (mysql_query(database->con, "CREATE TABLE IF NOT EXISTS " - "maxscale_schema.replication_heartbeat " - "(maxscale_id INT NOT NULL, " - "master_server_id INT NOT NULL, " - "master_timestamp INT UNSIGNED NOT NULL, " - "PRIMARY KEY ( master_server_id, maxscale_id ) ) " - "ENGINE=MYISAM DEFAULT CHARSET=latin1")) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: Error creating maxscale_schema.replication_heartbeat table in Master server" - ": %s", mysql_error(database->con)))); - - database->server->rlag = -1; - } - - /* auto purge old values after 48 hours*/ - purge_time = time(0) - (3600 * 48); - - sprintf(heartbeat_purge_query, "DELETE FROM maxscale_schema.replication_heartbeat WHERE master_timestamp < %lu", purge_time); - - if (mysql_query(database->con, heartbeat_purge_query)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: Error deleting from maxscale_schema.replication_heartbeat table: [%s], %s", - heartbeat_purge_query, - mysql_error(database->con)))); - } - - heartbeat = time(0); - - /* set node_ts for master as time(0) */ - database->server->node_ts = heartbeat; - - sprintf(heartbeat_insert_query, "UPDATE maxscale_schema.replication_heartbeat SET master_timestamp = %lu WHERE master_server_id = %i AND maxscale_id = %lu", heartbeat, handle->master_id, id); - - /* Try to insert MaxScale timestamp into master */ - if (mysql_query(database->con, heartbeat_insert_query)) { - - database->server->rlag = -1; - - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: Error updating maxscale_schema.replication_heartbeat table: [%s], %s", - heartbeat_insert_query, - mysql_error(database->con)))); - } else { - if (mysql_affected_rows(database->con) == 0) { - heartbeat = time(0); - sprintf(heartbeat_insert_query, "REPLACE INTO maxscale_schema.replication_heartbeat (master_server_id, maxscale_id, master_timestamp ) VALUES ( %i, %lu, %lu)", handle->master_id, id, heartbeat); - - if (mysql_query(database->con, heartbeat_insert_query)) { - - database->server->rlag = -1; - - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: Error inserting into maxscale_schema.replication_heartbeat table: [%s], %s", - heartbeat_insert_query, - mysql_error(database->con)))); - } else { - /* Set replication lag to 0 for the master */ - database->server->rlag = 0; - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "[mysql_mon]: heartbeat table inserted data for %s:%i", database->server->name, database->server->port))); - } - } else { - /* Set replication lag as 0 for the master */ - database->server->rlag = 0; - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "[mysql_mon]: heartbeat table updated for %s:%i", database->server->name, database->server->port))); - } - } - } - } - /* Check if the Slave_SQL_Running and Slave_IO_Running status is * set to Yes */ @@ -518,18 +450,29 @@ int replication_heartbeat = handle->replicationHeartbeat; && (result = mysql_store_result(database->con)) != NULL) { int i = 0; + long master_id = -1; num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { + /* get Slave_IO_Running and Slave_SQL_Running values*/ if (strncmp(row[12], "Yes", 3) == 0 && strncmp(row[13], "Yes", 3) == 0) { isslave += 1; + + /* get Master_Server_Id values */ + master_id = atol(row[41]); + if (master_id == 0) + master_id = -1; } i++; } + /* store master_id of current node */ + memcpy(&database->server->master_id, &master_id, sizeof(long)); + mysql_free_result(result); - if (isslave == i) + /* If all configured slaves are running set this node as slave */ + if (isslave > 0 && isslave == i) isslave = 1; else isslave = 0; @@ -538,105 +481,45 @@ int replication_heartbeat = handle->replicationHeartbeat; if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0 && (result = mysql_store_result(database->con)) != NULL) { + long master_id = -1; num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { + /* get Slave_IO_Running and Slave_SQL_Running values*/ if (strncmp(row[10], "Yes", 3) == 0 - && strncmp(row[11], "Yes", 3) == 0) + && strncmp(row[11], "Yes", 3) == 0) { isslave = 1; + + /* get Master_Server_Id values */ + master_id = atol(row[39]); + if (master_id == 0) + master_id = -1; + } } + /* store master_id of current node */ + memcpy(&database->server->master_id, &master_id, sizeof(long)); + mysql_free_result(result); } } - /* Get the master_timestamp value from maxscale_schema.replication_heartbeat table */ - if (isslave && replication_heartbeat == 1) { - time_t heartbeat; - char select_heartbeat_query[256] = ""; + /* Remove addition info */ + monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); - sprintf(select_heartbeat_query, "SELECT master_timestamp " - "FROM maxscale_schema.replication_heartbeat " - "WHERE maxscale_id = %lu AND master_server_id = %i", - id, handle->master_id); + /* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER + * will be assigned in the monitorMain() via get_replication_tree() routine + */ - /* if there is a master then send the query to the slave with master_id*/ - if (handle->master_id >= 0 && (mysql_query(database->con, select_heartbeat_query) == 0 - && (result = mysql_store_result(database->con)) != NULL)) { - num_fields = mysql_num_fields(result); - - while ((row = mysql_fetch_row(result))) { - int rlag = -1; - time_t slave_read; - - heartbeat = time(0); - slave_read = strtoul(row[0], NULL, 10); - - if ((errno == ERANGE && (slave_read == LONG_MAX || slave_read == LONG_MIN)) || (errno != 0 && slave_read == 0)) { - slave_read = 0; - } - - if (slave_read) { - /* set the replication lag */ - rlag = heartbeat - slave_read; - } - - /* set this node_ts as master_timestamp read from replication_heartbeat table */ - database->server->node_ts = slave_read; - - if (rlag >= 0) { - /* store rlag only if greater than monitor sampling interval */ - database->server->rlag = (rlag > (handle->interval / 1000)) ? rlag : 0; - } else { - database->server->rlag = -1; - } - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "[mysql_mon]: replication heartbeat: " - "server %s:%i is %i seconds behind master", - database->server->name, - database->server->port, - database->server->rlag))); - } - mysql_free_result(result); - } else { - database->server->rlag = -1; - database->server->node_ts = 0; - - if (handle->master_id < 0) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: error: replication heartbeat: " - "master_server_id NOT available for %s:%i", - database->server->name, - database->server->port))); - } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "[mysql_mon]: error: replication heartbeat: " - "failed selecting from hearthbeat table of %s:%i : [%s], %s", - database->server->name, - database->server->port, - select_heartbeat_query, - mysql_error(database->con)))); - } - } - } - - if (ismaster) + /* Set the Slave Role */ + if (isslave) { - server_set_status(database->server, SERVER_MASTER); - server_clear_status(database->server, SERVER_SLAVE); - } - else if (isslave) - { - server_set_status(database->server, SERVER_SLAVE); - server_clear_status(database->server, SERVER_MASTER); - } - if (ismaster == 0 && isslave == 0) - { - server_clear_status(database->server, SERVER_SLAVE); - server_clear_status(database->server, SERVER_MASTER); + monitor_set_pending_status(database, SERVER_SLAVE); + /* Avoid any possible stale Master state */ + monitor_clear_pending_status(database, SERVER_MASTER); + } else { + /* Avoid any possible Master/Slave stale state */ + monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_clear_pending_status(database, SERVER_MASTER); } } @@ -650,6 +533,9 @@ monitorMain(void *arg) { MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; MONITOR_SERVERS *ptr; +int replication_heartbeat = handle->replicationHeartbeat; +int num_servers=0; +MONITOR_SERVERS *root_master; if (mysql_thread_init()) { @@ -669,26 +555,88 @@ MONITOR_SERVERS *ptr; handle->status = MONITOR_STOPPED; return; } + /* reset num_servers */ + num_servers = 0; + + /* start from the first server in the list */ ptr = handle->databases; + while (ptr) { - unsigned int prev_status = ptr->server->status; - + /* copy server status into monitor pending_status */ + ptr->pending_status = ptr->server->status; + + /* monitor current node */ monitorDatabase(handle, ptr); + + /* reset the slave list of current node */ + if (ptr->server->slaves) { + free(ptr->server->slaves); + } + /* create a new slave list */ + ptr->server->slaves = (long *) calloc(MONITOR_MAX_NUM_SLAVES, sizeof(long)); + + num_servers++; + + if (mon_status_changed(ptr)) + { + dcb_call_foreach(DCB_REASON_NOT_RESPONDING); + } - if (ptr->server->status != prev_status || - SERVER_IS_DOWN(ptr->server)) + if (mon_status_changed(ptr) || + mon_print_fail_status(ptr)) { LOGIF(LM, (skygw_log_write_flush( LOGFILE_MESSAGE, "Backend server %s:%d state : %s", ptr->server->name, ptr->server->port, - STRSRVSTATUS(ptr->server)))); + STRSRVSTATUS(ptr->server)))); } - + if (SERVER_IS_DOWN(ptr->server)) + { + /** Increase this server'e error count */ + ptr->mon_err_count += 1; + } + else + { + /** Reset this server's error count */ + ptr->mon_err_count = 0; + } + ptr = ptr->next; } + + /* Compute the replication tree */ + root_master = get_replication_tree(handle, num_servers); + + /* Update server status from monitor pending status on that server*/ + + ptr = handle->databases; + while (ptr) + { + if (! SERVER_IN_MAINT(ptr->server)) { + ptr->server->status = ptr->pending_status; + } + ptr = ptr->next; + } + + /* Do now the heartbeat replication set/get for MySQL Replication Consistency */ + if (replication_heartbeat && root_master && (SERVER_IS_MASTER(root_master->server) || SERVER_IS_RELAY_SERVER(root_master->server))) { + set_master_heartbeat(handle, root_master); + ptr = handle->databases; + while (ptr) { + if( (! SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server)) + { + if (ptr->server->node_id != root_master->server->node_id && (SERVER_IS_SLAVE(ptr->server) || SERVER_IS_RELAY_SERVER(ptr->server))) { + set_slave_heartbeat(handle, ptr); + } + } + ptr = ptr->next; + } + } + + /* wait for the configured interval */ thread_millisleep(handle->interval); } } @@ -731,3 +679,432 @@ replicationHeartbeat(void *arg, int replicationHeartbeat) MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg; memcpy(&handle->replicationHeartbeat, &replicationHeartbeat, sizeof(int)); } + +static bool mon_status_changed( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + + if (mon_srv->mon_prev_status != mon_srv->server->status) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} + +static bool mon_print_fail_status( + MONITOR_SERVERS* mon_srv) +{ + bool succp; + int errcount = mon_srv->mon_err_count; + uint8_t modval; + + modval = 1<<(MIN(errcount/10, 7)); + + if (SERVER_IS_DOWN(mon_srv->server) && errcount%modval == 0) + { + succp = true; + } + else + { + succp = false; + } + return succp; +} + +/** + * Fetch a MySQL node by node_id + * + * @param ptr The list of servers to monitor + * @param node_id The MySQL server_id to fetch + * @return The server with the required server_id + */ +static MONITOR_SERVERS * +getServerByNodeId(MONITOR_SERVERS *ptr, long node_id) { + SERVER *current; + while (ptr) + { + current = ptr->server; + if (current->node_id == node_id) { + return ptr; + } + ptr = ptr->next; + } + return NULL; +} + +/** + * Fetch a MySQL slave node from a node_id + * + * @param ptr The list of servers to monitor + * @param node_id The MySQL server_id to fetch + * @return The slave server of this node_id + */ +static MONITOR_SERVERS * +getSlaveOfNodeId(MONITOR_SERVERS *ptr, long node_id) { + SERVER *current; + while (ptr) + { + current = ptr->server; + if (current->master_id == node_id) { + return ptr; + } + ptr = ptr->next; + } + return NULL; +} + +/******* + * This function sets the replication heartbeat + * into the maxscale_schema.replication_heartbeat table in the current master. + * The inserted values will be seen from all slaves replication from this master. + * + * @param handle The monitor handle + * @param database The number database server + */ +static void set_master_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { + unsigned long id = handle->id; + time_t heartbeat; + time_t purge_time; + char heartbeat_insert_query[128]=""; + char heartbeat_purge_query[128]=""; + + /* create the maxscale_schema database */ + if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error creating maxscale_schema database in Master server" + ": %s", mysql_error(database->con)))); + + database->server->rlag = -1; + } + + /* create repl_heartbeat table in maxscale_schema database */ + if (mysql_query(database->con, "CREATE TABLE IF NOT EXISTS " + "maxscale_schema.replication_heartbeat " + "(maxscale_id INT NOT NULL, " + "master_server_id INT NOT NULL, " + "master_timestamp INT UNSIGNED NOT NULL, " + "PRIMARY KEY ( master_server_id, maxscale_id ) ) " + "ENGINE=MYISAM DEFAULT CHARSET=latin1")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error creating maxscale_schema.replication_heartbeat table in Master server" + ": %s", mysql_error(database->con)))); + + database->server->rlag = -1; + } + + /* auto purge old values after 48 hours*/ + purge_time = time(0) - (3600 * 48); + + sprintf(heartbeat_purge_query, "DELETE FROM maxscale_schema.replication_heartbeat WHERE master_timestamp < %lu", purge_time); + + if (mysql_query(database->con, heartbeat_purge_query)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error deleting from maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_purge_query, + mysql_error(database->con)))); + } + + heartbeat = time(0); + + /* set node_ts for master as time(0) */ + database->server->node_ts = heartbeat; + + sprintf(heartbeat_insert_query, "UPDATE maxscale_schema.replication_heartbeat SET master_timestamp = %lu WHERE master_server_id = %li AND maxscale_id = %lu", heartbeat, handle->master->server->node_id, id); + + /* Try to insert MaxScale timestamp into master */ + if (mysql_query(database->con, heartbeat_insert_query)) { + + database->server->rlag = -1; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error updating maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_insert_query, + mysql_error(database->con)))); + } else { + if (mysql_affected_rows(database->con) == 0) { + heartbeat = time(0); + sprintf(heartbeat_insert_query, "REPLACE INTO maxscale_schema.replication_heartbeat (master_server_id, maxscale_id, master_timestamp ) VALUES ( %li, %lu, %lu)", handle->master->server->node_id, id, heartbeat); + + if (mysql_query(database->con, heartbeat_insert_query)) { + + database->server->rlag = -1; + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: Error inserting into maxscale_schema.replication_heartbeat table: [%s], %s", + heartbeat_insert_query, + mysql_error(database->con)))); + } else { + /* Set replication lag to 0 for the master */ + database->server->rlag = 0; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: heartbeat table inserted data for %s:%i", database->server->name, database->server->port))); + } + } else { + /* Set replication lag as 0 for the master */ + database->server->rlag = 0; + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: heartbeat table updated for Master %s:%i", database->server->name, database->server->port))); + } + } +} + +/******* + * This function gets the replication heartbeat + * from the maxscale_schema.replication_heartbeat table in the current slave + * and stores the timestamp and replication lag in the slave server struct + * + * @param handle The monitor handle + * @param database The number database server + */ +static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database) { + unsigned long id = handle->id; + time_t heartbeat; + char select_heartbeat_query[256] = ""; + MYSQL_ROW row; + MYSQL_RES *result; + int num_fields; + + /* Get the master_timestamp value from maxscale_schema.replication_heartbeat table */ + + sprintf(select_heartbeat_query, "SELECT master_timestamp " + "FROM maxscale_schema.replication_heartbeat " + "WHERE maxscale_id = %lu AND master_server_id = %li", + id, handle->master->server->node_id); + + /* if there is a master then send the query to the slave with master_id */ + if (handle->master !=NULL && (mysql_query(database->con, select_heartbeat_query) == 0 + && (result = mysql_store_result(database->con)) != NULL)) { + int rows_found = 0; + num_fields = mysql_num_fields(result); + + while ((row = mysql_fetch_row(result))) { + int rlag = -1; + time_t slave_read; + + rows_found = 1; + + heartbeat = time(0); + slave_read = strtoul(row[0], NULL, 10); + + if ((errno == ERANGE && (slave_read == LONG_MAX || slave_read == LONG_MIN)) || (errno != 0 && slave_read == 0)) { + slave_read = 0; + } + + if (slave_read) { + /* set the replication lag */ + rlag = heartbeat - slave_read; + } + + /* set this node_ts as master_timestamp read from replication_heartbeat table */ + database->server->node_ts = slave_read; + + if (rlag >= 0) { + /* store rlag only if greater than monitor sampling interval */ + database->server->rlag = (rlag > (handle->interval / 1000)) ? rlag : 0; + } else { + database->server->rlag = -1; + } + + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "[mysql_mon]: replication heartbeat: " + "Slave %s:%i has %i seconds lag", + database->server->name, + database->server->port, + database->server->rlag))); + } + if (!rows_found) { + database->server->rlag = -1; + database->server->node_ts = 0; + } + + mysql_free_result(result); + } else { + database->server->rlag = -1; + database->server->node_ts = 0; + + if (handle->master->server->node_id < 0) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: error: replication heartbeat: " + "master_server_id NOT available for %s:%i", + database->server->name, + database->server->port))); + } else { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "[mysql_mon]: error: replication heartbeat: " + "failed selecting from hearthbeat table of %s:%i : [%s], %s", + database->server->name, + database->server->port, + select_heartbeat_query, + mysql_error(database->con)))); + } + } +} + +/******* + * This function computes the replication tree + * from a set of MySQL Master/Slave monitored servers + * and returns the root server with SERVER_MASTER bit. + * The tree is computed even for servers in 'maintenance' mode. + * + * @param handle The monitor handle + * @param num_servers The number of servers monitored + * @return The server at root level with SERVER_MASTER bit + */ + +static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_servers) { + MONITOR_SERVERS *ptr; + MONITOR_SERVERS *backend; + SERVER *current; + int depth=0; + long node_id; + int root_level; + + ptr = handle->databases; + root_level = num_servers; + + while (ptr) + { + /* The server could be in SERVER_IN_MAINT + * that means SERVER_IS_RUNNING returns 0 + * Let's check only for SERVER_IS_DOWN: server is not running + */ + if (SERVER_IS_DOWN(ptr->server)) { + ptr = ptr->next; + continue; + } + depth = 0; + current = ptr->server; + + node_id = current->master_id; + if (node_id < 1) { + MONITOR_SERVERS *find_slave; + find_slave = getSlaveOfNodeId(handle->databases, current->node_id); + + if (find_slave == NULL) { + current->depth = -1; + ptr = ptr->next; + + continue; + } else { + current->depth = 0; + } + } else { + depth++; + } + + while(depth <= num_servers) { + /* set the root master at lowest depth level */ + if (current->depth > -1 && current->depth < root_level) { + root_level = current->depth; + handle->master = ptr; + } + backend = getServerByNodeId(handle->databases, node_id); + + if (backend) { + node_id = backend->server->master_id; + } else { + node_id = -1; + } + + if (node_id > 0) { + current->depth = depth + 1; + depth++; + + } else { + MONITOR_SERVERS *master; + current->depth = depth; + + master = getServerByNodeId(handle->databases, current->master_id); + if (master && master->server && master->server->node_id > 0) { + add_slave_to_master(master->server->slaves, MONITOR_MAX_NUM_SLAVES, current->node_id); + master->server->depth = current->depth -1; + monitor_set_pending_status(master, SERVER_MASTER); + } else { + if (current->master_id > 0) { + monitor_set_pending_status(ptr, SERVER_SLAVE_OF_EXTERNAL_MASTER); + } + } + break; + } + + } + + ptr = ptr->next; + } + + /* + * Return the root master + */ + + if (handle->master != NULL) { + /* If the root master is in MAINT, return NULL */ + if (SERVER_IN_MAINT(handle->master->server)) { + return NULL; + } else { + return handle->master; + } + } else { + return NULL; + } +} + +/******* + * This function add a slave id into the slaves server field + * of its master server + * + * @param slaves_list The slave list array of the master server + * @param list_size The size of the slave list + * @param node_id The node_id of the slave to be inserted + * @return 1 for inserted value and 0 otherwise + */ +static int add_slave_to_master(long *slaves_list, int list_size, long node_id) { + int i; + for (i = 0; i< list_size; i++) { + if (slaves_list[i] == 0) { + memcpy(&slaves_list[i], &node_id, sizeof(long)); + return 1; + } + } + return 0; +} + +/** + * Set a pending status bit in the monior server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +static void +monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status |= bit; +} + +/** + * Clear a pending status bit in the monior server + * + * @param server The server to update + * @param bit The bit to clear for the server + */ +static void +monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit) +{ + ptr->pending_status &= ~bit; +} diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 8f5bcd704..0e06db6e4 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -31,6 +31,7 @@ * 08/07/13 Mark Riddoch Initial implementation * 26/05/14 Massimiliano Pinto Default values for MONITOR_INTERVAL * 28/05/14 Massimiliano Pinto Addition of new fields in MYSQL_MONITOR struct + * 24/06/14 Massimiliano Pinto Addition of master field in MYSQL_MONITOR struct and MONITOR_MAX_NUM_SLAVES * * @endverbatim */ @@ -42,6 +43,9 @@ typedef struct monitor_servers { SERVER *server; /**< The server being monitored */ MYSQL *con; /**< The MySQL connection */ + int mon_err_count; + unsigned int mon_prev_status; + unsigned int pending_status; /**< Pending Status flag bitmap */ struct monitor_servers *next; /**< The next server in the list */ } MONITOR_SERVERS; @@ -50,17 +54,17 @@ typedef struct monitor_servers { * The handle for an instance of a MySQL Monitor module */ typedef struct { - SPINLOCK lock; /**< The monitor spinlock */ - pthread_t tid; /**< id of monitor thread */ - int shutdown; /**< Flag to shutdown the monitor thread */ - int status; /**< Monitor status */ - char *defaultUser; /**< Default username for monitoring */ - char *defaultPasswd; /**< Default password for monitoring */ - unsigned long interval; /**< Monitor sampling interval */ - unsigned long id; /**< Monitor ID */ + SPINLOCK lock; /**< The monitor spinlock */ + pthread_t tid; /**< id of monitor thread */ + int shutdown; /**< Flag to shutdown the monitor thread */ + int status; /**< Monitor status */ + char *defaultUser; /**< Default username for monitoring */ + char *defaultPasswd; /**< Default password for monitoring */ + unsigned long interval; /**< Monitor sampling interval */ + unsigned long id; /**< Monitor ID */ int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */ - int master_id; /**< Master server-id for MySQL Master/Slave replication */ - MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ + MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */ + MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */ } MYSQL_MONITOR; #define MONITOR_RUNNING 1 @@ -69,5 +73,6 @@ typedef struct { #define MONITOR_INTERVAL 10000 // in milliseconds #define MONITOR_DEFAULT_ID 1UL // unsigned long value +#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server #endif diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile index 1d98f76db..54a8f8c38 100644 --- a/server/modules/protocol/Makefile +++ b/server/modules/protocol/Makefile @@ -22,6 +22,7 @@ # headers so that liblog_manager.so can # be linked in. # 09/07/2013 Massimiliano Pinto Added the HTTPD protocol module +# 13/06/2014 Mark Riddoch Added thr MaxScale protocol module # include ../../../build_gateway.inc @@ -45,10 +46,14 @@ TELNETDSRCS=telnetd.c TELNETDOBJ=$(TELNETDSRCS:.c=.o) HTTPDSRCS=httpd.c HTTPDOBJ=$(HTTPDSRCS:.c=.o) -SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) +MAXSCALEDSRCS=maxscaled.c +MAXSCALEDOBJ=$(MAXSCALEDSRCS:.c=.o) +SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) \ + $(MAXSCALEDSRCS) OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so +MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so \ + libmaxscaled.so all: $(MODULES) @@ -64,6 +69,9 @@ libtelnetd.so: $(TELNETDOBJ) libHTTPD.so: $(HTTPDOBJ) $(CC) $(LDFLAGS) $(HTTPDOBJ) $(LIBS) -o $@ +libmaxscaled.so: $(MAXSCALEDOBJ) + $(CC) $(LDFLAGS) $(MAXSCALEDOBJ) $(LIBS) -lcrypt -o $@ + .c.o: $(CC) $(CFLAGS) $< -o $@ @@ -74,7 +82,7 @@ tags: ctags $(SRCS) $(HDRS) install: $(MODULES) - install -D $(MODULES) $(DEST)/MaxScale/modules + install -D $(MODULES) $(DEST)/modules depend: rm -f depend.mk diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index 7d06264b9..7db1366ad 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -245,7 +245,7 @@ HTTPD_session *client_data = NULL; } /* force the client connecton close */ - dcb->func.close(dcb); + dcb_close(dcb); return 0; } @@ -359,7 +359,6 @@ int n_connect = 0; static int httpd_close(DCB *dcb) { - dcb_close(dcb); return 0; } diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c new file mode 100644 index 000000000..738c78111 --- /dev/null +++ b/server/modules/protocol/maxscaled.c @@ -0,0 +1,381 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_BETA_RELEASE, + GWPROTOCOL_VERSION, + "A maxscale protocol for the administration interface" +}; + +extern int lm_enabled_logfiles_bitmask; + +/** + * @file maxscaled.c - MaxScale administration protocol + * + * + * @verbatim + * Revision History + * Date Who Description + * 13/06/2014 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +static char *version_str = "V1.0.0"; + +static int maxscaled_read_event(DCB* dcb); +static int maxscaled_write_event(DCB *dcb); +static int maxscaled_write(DCB *dcb, GWBUF *queue); +static int maxscaled_error(DCB *dcb); +static int maxscaled_hangup(DCB *dcb); +static int maxscaled_accept(DCB *dcb); +static int maxscaled_close(DCB *dcb); +static int maxscaled_listen(DCB *dcb, char *config); + +/** + * The "module object" for the maxscaled protocol module. + */ +static GWPROTOCOL MyObject = { + maxscaled_read_event, /**< Read - EPOLLIN handler */ + maxscaled_write, /**< Write - data from gateway */ + maxscaled_write_event, /**< WriteReady - EPOLLOUT handler */ + maxscaled_error, /**< Error - EPOLLERR handler */ + maxscaled_hangup, /**< HangUp - EPOLLHUP handler */ + maxscaled_accept, /**< Accept */ + NULL, /**< Connect */ + maxscaled_close, /**< Close */ + maxscaled_listen, /**< Create a listener */ + NULL, /**< Authentication */ + NULL /**< Session */ + }; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Initialise MaxScaled Protocol module.\n"))); +} + +/** + * 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 + */ +GWPROTOCOL * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Read event for EPOLLIN on the maxscaled protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +maxscaled_read_event(DCB* dcb) +{ +int n; +GWBUF *head = NULL; +SESSION *session = dcb->session; +MAXSCALED *maxscaled = (MAXSCALED *)dcb->protocol; +char *password; + + if ((n = dcb_read(dcb, &head)) != -1) + { + if (head) + { + unsigned char *ptr = GWBUF_DATA(head); + ptr = GWBUF_DATA(head); + if (GWBUF_LENGTH(head)) + { + switch (maxscaled->state) + { + case MAXSCALED_STATE_LOGIN: + maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + maxscaled->state = MAXSCALED_STATE_PASSWD; + dcb_printf(dcb, "PASSWORD"); + gwbuf_consume(head, GWBUF_LENGTH(head)); + break; + case MAXSCALED_STATE_PASSWD: + password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + if (admin_verify(maxscaled->username, password)) + { + dcb_printf(dcb, "OK----"); + maxscaled->state = MAXSCALED_STATE_DATA; + } + else + { + dcb_printf(dcb, "FAILED"); + maxscaled->state = MAXSCALED_STATE_LOGIN; + free(maxscaled->username); + } + gwbuf_consume(head, GWBUF_LENGTH(head)); + free(password); + break; + case MAXSCALED_STATE_DATA: + SESSION_ROUTE_QUERY(session, head); + dcb_printf(dcb, "OK"); + break; + } + } + else + { + // Force the free of the buffer header + gwbuf_consume(head, 0); + } + } + } + return n; +} + +/** + * EPOLLOUT handler for the maxscaled protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +maxscaled_write_event(DCB *dcb) +{ + return dcb_drain_writeq(dcb); +} + +/** + * Write routine for the maxscaled protocol module. + * + * Writes the content of the buffer queue to the socket + * observing the non-blocking principles of MaxScale. + * + * @param dcb Descriptor Control Block for the socket + * @param queue Linked list of buffes to write + */ +static int +maxscaled_write(DCB *dcb, GWBUF *queue) +{ + int rc; + rc = dcb_write(dcb, queue); + return rc; +} + +/** + * Handler for the EPOLLERR event. + * + * @param dcb The descriptor control block + */ +static int +maxscaled_error(DCB *dcb) +{ + return 0; +} + +/** + * Handler for the EPOLLHUP event. + * + * @param dcb The descriptor control block + */ +static int +maxscaled_hangup(DCB *dcb) +{ + return 0; +} + +/** + * Handler for the EPOLLIN event when the DCB refers to the listening + * socket for the protocol. + * + * @param dcb The descriptor control block + * @return The number of new connections created + */ +static int +maxscaled_accept(DCB *dcb) +{ +int n_connect = 0; + + while (1) + { + int so; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr); + DCB *client_dcb; + MAXSCALED *maxscaled_pr = NULL; + + so = accept(dcb->fd, (struct sockaddr *)&addr, &addrlen); + + if (so == -1) + return n_connect; + else + { + atomic_add(&dcb->stats.n_accepts, 1); + client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); + + if (client_dcb == NULL) + + { + return n_connect; + } + client_dcb->fd = so; + client_dcb->remote = strdup(inet_ntoa(addr.sin_addr)); + memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); + client_dcb->session = + session_alloc(dcb->session->service, client_dcb); + maxscaled_pr = (MAXSCALED *)malloc(sizeof(MAXSCALED)); + client_dcb->protocol = (void *)maxscaled_pr; + + if (maxscaled_pr == NULL) + { + dcb_add_to_zombieslist(client_dcb); + return n_connect; + } + + if (poll_add_dcb(client_dcb) == -1) + { + dcb_add_to_zombieslist(dcb); + return n_connect; + } + n_connect++; + maxscaled_pr->state = MAXSCALED_STATE_LOGIN; + maxscaled_pr->username = NULL; + dcb_printf(client_dcb, "USER"); + } + } + return n_connect; +} + +/** + * The close handler for the descriptor. Called by the gateway to + * explicitly close a connection. + * + * @param dcb The descriptor control block + */ + +static int +maxscaled_close(DCB *dcb) +{ +MAXSCALED *maxscaled = dcb->protocol; + + if (maxscaled && maxscaled->username) + free(maxscaled->username); + + dcb_close(dcb); + return 0; +} + +/** + * Maxscale daemon listener entry point + * + * @param listener The Listener DCB + * @param config Configuration (ip:port) + */ +static int +maxscaled_listen(DCB *listener, char *config) +{ +struct sockaddr_in addr; +int one = 1; +int rc; + + memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL)); + + if (!parse_bindconfig(config, 6033, &addr)) + return 0; + + + if ((listener->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + return 0; + } + + // socket options + setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); + // set NONBLOCKING mode + setnonblocking(listener->fd); + // bind address and port + if (bind(listener->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + return 0; + } + + rc = listen(listener->fd, SOMAXCONN); + + if (rc == 0) { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "Listening maxscale connections at %s\n", + config))); + } else { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Failed to start listening for maxscale admin connections " + "due error %d, %s\n\n", + eno, + strerror(eno)))); + return 0; + } + + + if (poll_add_dcb(listener) == -1) + { + return 0; + } + return 1; +} diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index f262232be..5d8088d4e 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -47,7 +47,7 @@ MODULE_INFO info = { MODULE_API_PROTOCOL, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, GWPROTOCOL_VERSION, "The MySQL to backend server protocol" }; @@ -65,7 +65,13 @@ static int gw_backend_hangup(DCB *dcb); static int backend_write_delayqueue(DCB *dcb); static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); -static int gw_session(DCB *backend_dcb, void *data); +static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); + + + +#if defined(NOT_USED) + static int gw_session(DCB *backend_dcb, void *data); +#endif static MYSQL_session* gw_get_shared_session_auth_info(DCB* dcb); static GWPROTOCOL MyObject = { @@ -79,7 +85,7 @@ static GWPROTOCOL MyObject = { gw_backend_close, /* Close */ NULL, /* Listen */ gw_change_user, /* Authentication */ - gw_session /* Session */ + NULL /* Session */ }; /* @@ -169,8 +175,8 @@ static int gw_read_backend_event(DCB *dcb) { pthread_self(), dcb, dcb->fd, - backend_protocol->state, - STRPROTOCOLSTATE(backend_protocol->state)))); + backend_protocol->protocol_auth_state, + STRPROTOCOLSTATE(backend_protocol->protocol_auth_state)))); /* backend is connected: @@ -184,18 +190,29 @@ static int gw_read_backend_event(DCB *dcb) { * If starting to auhenticate with backend server, lock dcb * to prevent overlapping processing of auth messages. */ - if (backend_protocol->state == MYSQL_CONNECTED) { - + if (backend_protocol->protocol_auth_state == MYSQL_CONNECTED) + { spinlock_acquire(&dcb->authlock); backend_protocol = (MySQLProtocol *) dcb->protocol; CHK_PROTOCOL(backend_protocol); - if (backend_protocol->state == MYSQL_CONNECTED) { - - if (gw_read_backend_handshake(backend_protocol) != 0) { - backend_protocol->state = MYSQL_AUTH_FAILED; - } else { + if (backend_protocol->protocol_auth_state == MYSQL_CONNECTED) + { + if (gw_read_backend_handshake(backend_protocol) != 0) + { + backend_protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_event] after " + "gw_read_backend_handshake, fd %d, " + "state = MYSQL_AUTH_FAILED.", + pthread_self(), + backend_protocol->owner_dcb->fd))); + + } + else + { /* handshake decoded, send the auth credentials */ if (gw_send_authentication_to_backend( current_session->db, @@ -203,32 +220,39 @@ static int gw_read_backend_event(DCB *dcb) { current_session->client_sha1, backend_protocol) != 0) { - backend_protocol->state = MYSQL_AUTH_FAILED; - } else { - backend_protocol->state = MYSQL_AUTH_RECV; + backend_protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_event] after " + "gw_send_authentication_to_backend " + "fd %d, state = MYSQL_AUTH_FAILED.", + pthread_self(), + backend_protocol->owner_dcb->fd))); + } + else + { + backend_protocol->protocol_auth_state = MYSQL_AUTH_RECV; } } } spinlock_release(&dcb->authlock); } - - /* * Now: * -- check the authentication reply from backend * OR * -- handle a previous handshake error */ - if (backend_protocol->state == MYSQL_AUTH_RECV || - backend_protocol->state == MYSQL_AUTH_FAILED) + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV || + backend_protocol->protocol_auth_state == MYSQL_AUTH_FAILED) { spinlock_acquire(&dcb->authlock); backend_protocol = (MySQLProtocol *) dcb->protocol; CHK_PROTOCOL(backend_protocol); - if (backend_protocol->state == MYSQL_AUTH_RECV || - backend_protocol->state == MYSQL_AUTH_FAILED) + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV || + backend_protocol->protocol_auth_state == MYSQL_AUTH_FAILED) { ROUTER_OBJECT *router = NULL; ROUTER *router_instance = NULL; @@ -240,8 +264,9 @@ static int gw_read_backend_event(DCB *dcb) { router = session->service->router; router_instance = session->service->router_instance; + rsession = session->router_session; - if (backend_protocol->state == MYSQL_AUTH_RECV) { + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV) { /*< * Read backed auth reply */ @@ -250,17 +275,25 @@ static int gw_read_backend_event(DCB *dcb) { switch (receive_rc) { case -1: - backend_protocol->state = MYSQL_AUTH_FAILED; + backend_protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_event] after " + "gw_receive_backend_authentication " + "fd %d, state = MYSQL_AUTH_FAILED.", + pthread_self(), + backend_protocol->owner_dcb->fd))); + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Backend server didn't " "accept authentication for user " "%s.", - current_session->user))); + current_session->user))); break; case 1: - backend_protocol->state = MYSQL_IDLE; + backend_protocol->protocol_auth_state = MYSQL_IDLE; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, @@ -290,7 +323,7 @@ static int gw_read_backend_event(DCB *dcb) { } /* switch */ } - if (backend_protocol->state == MYSQL_AUTH_FAILED) + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_FAILED) { /** * protocol state won't change anymore, @@ -298,78 +331,70 @@ static int gw_read_backend_event(DCB *dcb) { */ spinlock_release(&dcb->authlock); spinlock_acquire(&dcb->delayqlock); - /*< - * vraa : errorHandle - * check the delayq before the reply - */ - if (dcb->delayq != NULL) { - /* send an error to the client */ - mysql_send_custom_error( - dcb->session->client, - 1, - 0, - "Connection to backend lost."); - // consume all the delay queue - while ((dcb->delayq = gwbuf_consume( + + if (dcb->delayq != NULL) + { + while ((dcb->delayq = gwbuf_consume( dcb->delayq, GWBUF_LENGTH(dcb->delayq))) != NULL); } spinlock_release(&dcb->delayqlock); - - /* try reload users' table for next connection */ - service_refresh_users(dcb->session->client->service); - - while (session->state != SESSION_STATE_ROUTER_READY && - session->state != SESSION_STATE_STOPPING) - { - ss_dassert( - session->state == SESSION_STATE_READY || - session->state == - SESSION_STATE_ROUTER_READY || - session->state == SESSION_STATE_STOPPING); - /** - * Session shouldn't be NULL at this point - * anymore. Just checking.. - */ - if (session->client->session == NULL) - { - rc = 1; - goto return_rc; - } - usleep(1); - } - - if (session->state == SESSION_STATE_STOPPING) { - goto return_rc; + GWBUF* errbuf; + bool succp; + + /* try reload users' table for next connection */ + service_refresh_users(dcb->session->service); +#if defined(SS_DEBUG) + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_event] " + "calling handleError. Backend " + "DCB %p, session %p", + pthread_self(), + dcb, + dcb->session))); +#endif + + errbuf = mysql_create_custom_error( + 1, + 0, + "Authentication with backend failed. " + "Session will be closed."); + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_REPLY_CLIENT, + &succp); + + ss_dassert(!succp); + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_event] " + "after calling handleError. Backend " + "DCB %p, session %p", + pthread_self(), + dcb, + dcb->session))); + + if (session != NULL) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + dcb_close(dcb); } - spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - - /** - * rsession shouldn't be NULL since session - * state indicates that it was initialized - * successfully. - */ - rsession = session->router_session; - ss_dassert(rsession != NULL); - - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [gw_read_backend_event] " - "Call closeSession for backend's " - "router client session.", - pthread_self()))); - /* close router_session */ - router->closeSession(router_instance, rsession); rc = 1; goto return_rc; } else { - ss_dassert(backend_protocol->state == MYSQL_IDLE); + ss_dassert(backend_protocol->protocol_auth_state == MYSQL_IDLE); LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_read_backend_event] " @@ -394,64 +419,127 @@ static int gw_read_backend_event(DCB *dcb) { /* reading MySQL command output from backend and writing to the client */ { - GWBUF *writebuf = NULL; - ROUTER_OBJECT *router = NULL; - ROUTER *router_instance = NULL; - void *rsession = NULL; - SESSION *session = dcb->session; - - CHK_SESSION(session); - /* read available backend data */ - rc = dcb_read(dcb, &writebuf); + GWBUF *read_buffer = NULL; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + SESSION *session = dcb->session; + int nbytes_read = 0; - if (rc < 0) { - /*< vraa : errorHandle */ - /*< - * Backend generated EPOLLIN event and if backend has - * failed, connection must be closed to avoid backend - * dcb from getting hanged. - */ - (dcb->func).close(dcb); - rc = 0; - goto return_rc; - } - - if (writebuf == NULL) { - rc = 0; - goto return_rc; - } + CHK_SESSION(session); router = session->service->router; router_instance = session->service->router_instance; - rsession = session->router_session; - /* Note the gwbuf doesn't have here a valid queue->command - * descriptions as it is a fresh new one! - * We only have the copied value in dcb->command from - * previuos func.write() and this will be used by the - * router->clientReply - * and pass now the gwbuf to the router + /* read available backend data */ + rc = dcb_read(dcb, &read_buffer); + + if (rc < 0) + { + GWBUF* errbuf; + bool succp; +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend read error handling #2."))); +#endif + errbuf = mysql_create_custom_error( + 1, + 0, + "Read from backend failed"); + + router->handleError(router_instance, + session->router_session, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + + if (!succp) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + dcb_close(dcb); + rc = 0; + goto return_rc; + } + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read == 0) + { + goto return_rc; + } + else + { + ss_dassert(read_buffer != NULL); + } + + /** Packet prefix was read earlier */ + if (dcb->dcb_readqueue) + { + read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer); + nbytes_read = gwbuf_length(read_buffer); + + if (nbytes_read < 5) /*< read at least command type */ + { + rc = 0; + goto return_rc; + } + /** There is at least length and command type. */ + else + { + read_buffer = dcb->dcb_readqueue; + dcb->dcb_readqueue = NULL; + } + } + else + { + if (nbytes_read < 5) + { + gwbuf_append(dcb->dcb_readqueue, read_buffer); + rc = 0; + goto return_rc; + } + } + /** + * If protocol has session command set, concatenate whole + * response into one buffer. */ - + if (protocol_get_srv_command((MySQLProtocol *)dcb->protocol, false) != + MYSQL_COM_UNDEFINED) + { + read_buffer = process_response_data(dcb, read_buffer, nbytes_read); + } /*< * If dcb->session->client is freed already it may be NULL. */ - if (dcb->session->client != NULL) { + if (dcb->session->client != NULL) + { client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); - if (client_protocol != NULL) { + if (client_protocol != NULL) + { CHK_PROTOCOL(client_protocol); - - if (client_protocol->state == MYSQL_IDLE) + + if (client_protocol->protocol_auth_state == + MYSQL_IDLE) { - router->clientReply(router_instance, - rsession, - writebuf, - dcb); + gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); + + router->clientReply( + router_instance, + session->router_session, + read_buffer, + dcb); rc = 1; } goto return_rc; - } else if (dcb->session->client->dcb_role == DCB_ROLE_INTERNAL) { - router->clientReply(router_instance, rsession, writebuf, dcb); + } + else if (dcb->session->client->dcb_role == DCB_ROLE_INTERNAL) + { + gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); + router->clientReply(router_instance, session->router_session, read_buffer, dcb); rc = 1; } } @@ -479,32 +567,42 @@ static int gw_write_backend_event(DCB *dcb) { * Don't write to backend if backend_dcb is not in poll set anymore. */ if (dcb->state != DCB_STATE_POLLING) { - if (dcb->writeq != NULL) { - /*< vraa : errorHandle */ - mysql_send_custom_error( - dcb->session->client, - 1, - 0, - "Writing to backend failed due invalid Maxscale " - "state."); - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_write_backend_event] Write to backend " - "dcb %p fd %d " - "failed due invalid state %s.", - pthread_self(), - dcb, - dcb->fd, - STRDCBSTATE(dcb->state)))); + uint8_t* data; - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Attempt to write buffered data to backend " - "failed " - "due internal inconsistent state."))); + if (dcb->writeq != NULL) + { + data = (uint8_t *)GWBUF_DATA(dcb->writeq); - rc = 0; - } else { + if (!(MYSQL_IS_COM_QUIT(data))) + { + /*< vraa : errorHandle */ + mysql_send_custom_error( + dcb->session->client, + 1, + 0, + "Writing to backend failed due invalid Maxscale " + "state."); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_write_backend_event] Write to backend " + "dcb %p fd %d " + "failed due invalid state %s.", + pthread_self(), + dcb, + dcb->fd, + STRDCBSTATE(dcb->state)))); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Attempt to write buffered data to backend " + "failed " + "due internal inconsistent state."))); + + rc = 0; + } + } + else + { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_write_backend_event] Dcb %p in state %s " @@ -517,8 +615,8 @@ static int gw_write_backend_event(DCB *dcb) { goto return_rc; } - if (backend_protocol->state == MYSQL_PENDING_CONNECT) { - backend_protocol->state = MYSQL_CONNECTED; + if (backend_protocol->protocol_auth_state == MYSQL_PENDING_CONNECT) { + backend_protocol->protocol_auth_state = MYSQL_CONNECTED; rc = 1; goto return_rc; } @@ -538,7 +636,7 @@ return_rc: } /* - * Write function for backend DCB + * Write function for backend DCB. Store command to protocol. * * @param dcb The DCB of the backend * @param queue Queue of buffers to write @@ -550,36 +648,13 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) MySQLProtocol *backend_protocol = dcb->protocol; int rc = 0; - /*< - * Don't write to backend if backend_dcb is not in poll set anymore. - */ - spinlock_acquire(&dcb->dcb_initlock); - - if (dcb->state != DCB_STATE_POLLING) { - /*< vraa : errorHandle */ - /*< Free buffer memory */ - gwbuf_consume(queue, GWBUF_LENGTH(queue)); - - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_MySQLWrite_backend] Write to backend failed. " - "Backend dcb %p fd %d is %s.", - pthread_self(), - dcb, - dcb->fd, - STRDCBSTATE(dcb->state)))); - spinlock_release(&dcb->dcb_initlock); - rc = 0; - goto return_rc; - } - spinlock_release(&dcb->dcb_initlock); spinlock_acquire(&dcb->authlock); /** * Pick action according to state of protocol. * If auth failed, return value is 0, write and buffered write * return 1. */ - switch(backend_protocol->state) { + switch (backend_protocol->protocol_auth_state) { case MYSQL_AUTH_FAILED: { size_t len; @@ -600,13 +675,17 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) queue, GWBUF_LENGTH(queue))) != NULL); free(str); - } rc = 0; spinlock_release(&dcb->authlock); goto return_rc; break; - + } + case MYSQL_IDLE: + { + uint8_t* ptr = GWBUF_DATA(queue); + int cmd = MYSQL_GET_COMMAND(ptr); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_MySQLWrite_backend] write to dcb %p " @@ -614,17 +693,33 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) pthread_self(), dcb, dcb->fd, - STRPROTOCOLSTATE(backend_protocol->state)))); + STRPROTOCOLSTATE(backend_protocol->protocol_auth_state)))); + spinlock_release(&dcb->authlock); + /** + * Statement type is used in readwrite split router. + * Command is *not* set for readconn router. + * + * Server commands are stored to MySQLProtocol structure + * if buffer always includes a single statement. + */ + if (GWBUF_IS_TYPE_SINGLE_STMT(queue) && + GWBUF_IS_TYPE_SESCMD(queue)) + { + /** Record the command to backend's protocol */ + protocol_add_srv_command(backend_protocol, cmd); + } + /** Write to backend */ rc = dcb_write(dcb, queue); goto return_rc; break; - + } + default: - /*< - * Now put the incoming data to the delay queue unless backend is - * connected with auth ok - */ + { + uint8_t* ptr = GWBUF_DATA(queue); + int cmd = MYSQL_GET_COMMAND(ptr); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_MySQLWrite_backend] delayed write to " @@ -632,85 +727,91 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) pthread_self(), dcb, dcb->fd, - STRPROTOCOLSTATE(backend_protocol->state)))); + STRPROTOCOLSTATE(backend_protocol->protocol_auth_state)))); + /** + * In case of session commands, store command to DCB's + * protocol struct. + */ + if (GWBUF_IS_TYPE_SINGLE_STMT(queue) && + GWBUF_IS_TYPE_SESCMD(queue)) + { + /** Record the command to backend's protocol */ + protocol_add_srv_command(backend_protocol, cmd); + } + /*< + * Now put the incoming data to the delay queue unless backend is + * connected with auth ok + */ backend_set_delayqueue(dcb, queue); spinlock_release(&dcb->authlock); rc = 1; goto return_rc; break; + } } return_rc: return rc; } /** - * Backend Error Handling for EPOLLER - * + * Error event handler. + * Create error message, pass it to router's error handler and if error + * handler fails in providing enough backend servers, mark session being + * closed and call DCB close function which triggers closing router session + * and related backends (if any exists. */ -static int gw_error_backend_event(DCB *dcb) { - SESSION *session; - void *rsession; - ROUTER_OBJECT *router; - ROUTER *router_instance; - int rc = 0; - +static int gw_error_backend_event(DCB *dcb) +{ + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + GWBUF* errbuf; + bool succp; + CHK_DCB(dcb); session = dcb->session; CHK_SESSION(session); + rsession = session->router_session; + router = session->service->router; + router_instance = session->service->router_instance; - router = session->service->router; - router_instance = session->service->router_instance; - - if (dcb->state != DCB_STATE_POLLING) { - /*< vraa : errorHandle */ - /*< - * if client is not available it needs to be handled in send - * function. Session != NULL, that is known. - */ - mysql_send_custom_error( - dcb->session->client, - 1, - 0, - "Writing to backend failed."); - - rc = 0; - } else { - /*< vraa : errorHandle */ - mysql_send_custom_error( - dcb->session->client, - 1, - 0, - "Closed backend connection."); - rc = 1; - } - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [gw_error_backend_event] Some error occurred in backend. " - "rc = %d", - pthread_self(), - rc))); - - if (session->state == SESSION_STATE_ROUTER_READY) +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend error event handling."))); +#endif + /** + * Avoid running redundant error handling procedure. + * dcb_close is already called for the DCB. Thus, either connection is + * closed by router and COM_QUIT sent or there was an error which + * have already been handled. + */ + if (dcb->session != DCB_STATE_POLLING) { + return 1; + } + errbuf = mysql_create_custom_error( + 1, + 0, + "Lost connection to backend server."); + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + + /** There are not required backends available, close session. */ + if (!succp) { spinlock_acquire(&session->ses_lock); session->state = SESSION_STATE_STOPPING; spinlock_release(&session->ses_lock); - - rsession = session->router_session; - /*< - * rsession should never be NULL here. - */ - ss_dassert(rsession != NULL); - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [gw_error_backend_event] " - "Call closeSession for backend " - "session.", - pthread_self()))); - - router->closeSession(router_instance, rsession); } - return rc; + dcb_close(dcb); + + return 1; } /* @@ -762,7 +863,7 @@ static int gw_create_backend_connection( case 0: ss_dassert(fd > 0); protocol->fd = fd; - protocol->state = MYSQL_CONNECTED; + protocol->protocol_auth_state = MYSQL_CONNECTED; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_create_backend_connection] Established " @@ -777,7 +878,7 @@ static int gw_create_backend_connection( case 1: ss_dassert(fd > 0); - protocol->state = MYSQL_PENDING_CONNECT; + protocol->protocol_auth_state = MYSQL_PENDING_CONNECT; protocol->fd = fd; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -792,7 +893,7 @@ static int gw_create_backend_connection( default: ss_dassert(fd == -1); - ss_dassert(protocol->state == MYSQL_ALLOC); + ss_dassert(protocol->protocol_auth_state == MYSQL_ALLOC); LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_create_backend_connection] Connection " @@ -811,7 +912,11 @@ return_fd: /** - * Hangup routine the backend dcb: it does nothing + * Error event handler. + * Create error message, pass it to router's error handler and if error + * handler fails in providing enough backend servers, mark session being + * closed and call DCB close function which triggers closing router session + * and related backends (if any exists. * * @param dcb The current Backend DCB * @return 1 always @@ -819,46 +924,92 @@ return_fd: static int gw_backend_hangup(DCB *dcb) { - SESSION *session; - void *rsession; - ROUTER_OBJECT *router; - ROUTER *router_instance; - int rc = 0; + SESSION* session; + void* rsession; + ROUTER_OBJECT* router; + ROUTER* router_instance; + bool succp; + GWBUF* errbuf; + + CHK_DCB(dcb); + session = dcb->session; + CHK_SESSION(session); + + rsession = session->router_session; + router = session->service->router; + router_instance = session->service->router_instance; - session = dcb->session; +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend hangup error handling."))); +#endif + + + errbuf = mysql_create_custom_error( + 1, + 0, + "Lost connection to backend server."); - if (session->state == SESSION_STATE_ROUTER_READY) - { - router = session->service->router; - router_instance = session->service->router_instance; - rsession = session->router_session; - /*< - * rsession should never be NULL here. - */ - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [gw_backend_hangup] " - "Call closeSession for backend " - "session.", - pthread_self()))); + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + + /** There are not required backends available, close session. */ + if (!succp) { +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Backend hangup -> closing session."))); +#endif - router->closeSession(router_instance, rsession); + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); } - /*< vraa : errorHandle */ + dcb_close(dcb); + return 1; } /** - * Close the backend dcb - * + * Send COM_QUIT to backend so that it can be closed. * @param dcb The current Backend DCB * @return 1 always */ static int gw_backend_close(DCB *dcb) { - /*< vraa : errorHandle */ - dcb_close(dcb); + DCB* client_dcb; + SESSION* session; + GWBUF* quitbuf; + + CHK_DCB(dcb); + session = dcb->session; + CHK_SESSION(session); + + quitbuf = mysql_create_com_quit(NULL, 0); + gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL); + + /** Send COM_QUIT to the backend being closed */ + mysql_send_com_quit(dcb, 0, quitbuf); + + mysql_protocol_done(dcb); + + if (session != NULL && session->state == SESSION_STATE_STOPPING) + { + client_dcb = session->client; + + if (client_dcb != NULL && + client_dcb->state == DCB_STATE_POLLING) + { + /** Close client DCB */ + dcb_close(client_dcb); + } + } return 1; } @@ -908,27 +1059,55 @@ static int backend_write_delayqueue(DCB *dcb) } else { - localq = dcb->delayq; - dcb->delayq = NULL; - spinlock_release(&dcb->delayqlock); - rc = dcb_write(dcb, localq); + localq = dcb->delayq; + dcb->delayq = NULL; + spinlock_release(&dcb->delayqlock); + rc = dcb_write(dcb, localq); } - if (rc == 0) { + if (rc == 0) + { + GWBUF* errbuf; + bool succp; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + SESSION *session = dcb->session; + + CHK_SESSION(session); + + router = session->service->router; + router_instance = session->service->router_instance; + rsession = session->router_session; +#if defined(SS_DEBUG) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : failed to write buffered data to back-end " - "server. Buffer was empty of back-end was disconnected " - "during operation."))); - - mysql_send_custom_error( - dcb->session->client, - 1, - 0, + "Backend write delayqueue error handling."))); +#endif + errbuf = mysql_create_custom_error( + 1, + 0, "Failed to write buffered data to back-end server. " "Buffer was empty or back-end was disconnected during " - "operation."); - dcb_close(dcb); + "operation. Session will be closed."); + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_NEW_CONNECTION, + &succp); + + if (!succp) + { + if (session != NULL) + { + spinlock_acquire(&session->ses_lock); + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } + dcb_close(dcb); + } } return rc; @@ -936,7 +1115,12 @@ static int backend_write_delayqueue(DCB *dcb) -static int gw_change_user(DCB *backend, SERVER *server, SESSION *in_session, GWBUF *queue) { +static int gw_change_user( + DCB *backend, + SERVER *server, + SESSION *in_session, + GWBUF *queue) +{ MYSQL_session *current_session = NULL; MySQLProtocol *backend_protocol = NULL; MySQLProtocol *client_protocol = NULL; @@ -1022,6 +1206,7 @@ static int gw_change_user(DCB *backend, SERVER *server, SESSION *in_session, GWB * @param * @return always 1 */ +/* static int gw_session(DCB *backend_dcb, void *data) { GWBUF *queue = NULL; @@ -1031,3 +1216,139 @@ static int gw_session(DCB *backend_dcb, void *data) { return 1; } +*/ + + +/** + * Move packets or parts of packets from redbuf to outbuf as the packet headers + * and lengths have been noticed and counted. + * Session commands need to be marked so that they can be handled properly in + * the router's clientReply. + * Return the pointer to outbuf. + */ +static GWBUF* process_response_data ( + DCB* dcb, + GWBUF* readbuf, + int nbytes_to_process) /*< number of new bytes read */ +{ + int npackets_left = 0; /*< response's packet count */ + size_t nbytes_left = 0; /*< nbytes to be read for the packet */ + MySQLProtocol* p; + GWBUF* outbuf = NULL; + + /** Get command which was stored in gw_MySQLWrite_backend */ + p = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(p); + + /** All buffers processed here are sescmd responses */ + gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE); + + /** + * Now it is known how many packets there should be and how much + * is read earlier. + */ + while (nbytes_to_process != 0) + { + mysql_server_cmd_t srvcmd; + bool succp; + + srvcmd = protocol_get_srv_command(p, false); + + /** + * Read values from protocol structure, fails if values are + * uninitialized. + */ + if (npackets_left == 0) + { + succp = protocol_get_response_status(p, &npackets_left, &nbytes_left); + + if (!succp || npackets_left == 0) + { + /** + * Examine command type and the readbuf. Conclude response + * packet count from the command type or from the first + * packet content. Fails if read buffer doesn't include + * enough data to read the packet length. + */ + init_response_status(readbuf, srvcmd, &npackets_left, &nbytes_left); + } + } + /** Only session commands with responses should be processed */ + ss_dassert(npackets_left > 0); + + /** Read incomplete packet. */ + if (nbytes_left > nbytes_to_process) + { + /** Includes length info so it can be processed */ + if (nbytes_to_process >= 5) + { + /** discard source buffer */ + readbuf = gwbuf_consume(readbuf, GWBUF_LENGTH(readbuf)); + nbytes_left -= nbytes_to_process; + } + nbytes_to_process = 0; + } + /** Packet was read. All bytes belonged to the last packet. */ + else if (nbytes_left == nbytes_to_process) + { + nbytes_left = 0; + nbytes_to_process = 0; + ss_dassert(npackets_left > 0); + npackets_left -= 1; + outbuf = gwbuf_append(outbuf, readbuf); + readbuf = NULL; + } + /** + * Packet was read. There should be more since bytes were + * left over. + * Move the next packet to its own buffer and add that next + * to the prev packet's buffer. + */ + else /*< nbytes_left < nbytes_to_process */ + { + nbytes_to_process -= nbytes_left; + + /** Move the prefix of the buffer to outbuf from redbuf */ + outbuf = gwbuf_append(outbuf, gwbuf_clone_portion(readbuf, 0, nbytes_left)); + readbuf = gwbuf_consume(readbuf, nbytes_left); + ss_dassert(npackets_left > 0); + npackets_left -= 1; + nbytes_left = 0; + } + + /** Store new status to protocol structure */ + protocol_set_response_status(p, npackets_left, nbytes_left); + + /** A complete packet was read */ + if (nbytes_left == 0) + { + /** No more packets in this response */ + if (npackets_left == 0) + { + GWBUF* b = outbuf; + + while (b->next != NULL) + { + b = b->next; + } + /** Mark last as end of response */ + gwbuf_set_type(b, GWBUF_TYPE_RESPONSE_END); + + /** Archive the command */ + protocol_archive_srv_command(p); + } + /** Read next packet */ + else + { + uint8_t* data; + + /** Read next packet length */ + data = GWBUF_DATA(readbuf); + nbytes_left = MYSQL_GET_PACKET_LEN(data)+MYSQL_HEADER_LEN; + /** Store new status to protocol structure */ + protocol_set_response_status(p, npackets_left, nbytes_left); + } + } + } + return outbuf; +} diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 8134005a4..5b62d7615 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -44,7 +44,7 @@ MODULE_INFO info = { MODULE_API_PROTOCOL, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, GWPROTOCOL_VERSION, "The client to MaxScale MySQL protocol implementation" }; @@ -483,6 +483,11 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (auth_token) free(auth_token); + if (auth_ret == 0) + { + dcb->user = strdup(client_data->user); + } + return auth_ret; } @@ -495,7 +500,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue) { - return dcb_write(dcb, queue); + return dcb_write(dcb, queue); } /** @@ -504,75 +509,32 @@ gw_MySQLWrite_client(DCB *dcb, GWBUF *queue) * @param dcb Descriptor control block * @return 0 if succeed, 1 otherwise */ -int gw_read_client_event(DCB* dcb) { +int gw_read_client_event( + DCB* dcb) +{ SESSION *session = NULL; ROUTER_OBJECT *router = NULL; ROUTER *router_instance = NULL; void *rsession = NULL; MySQLProtocol *protocol = NULL; GWBUF *read_buffer = NULL; - int b = -1; int rc = 0; int nbytes_read = 0; CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); - /** - * Check how many bytes are readable in dcb->fd. - */ - if (ioctl(dcb->fd, FIONREAD, &b) != 0) { - int eno = errno; - errno = 0; - - LOGIF(LE, (skygw_log_write( - LOGFILE_ERROR, - "%lu [gw_read_client_event] ioctl FIONREAD for fd " - "%d failed. errno %d, %s. dcb->state = %d", - pthread_self(), - dcb->fd, - eno, - strerror(eno), - dcb->state))); - rc = 1; - goto return_rc; + rc = dcb_read(dcb, &read_buffer); + + if (rc < 0) + { + dcb_close(dcb); } - - /* - * Handle the closed client socket. - */ - if (b == 0) { - char c; - int l_errno = 0; - int r = -1; - - rc = 0; - - /* try to read 1 byte, without consuming the socket buffer */ - r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK); - l_errno = errno; - - if (r <= 0) { - if ( (l_errno == EAGAIN) || (l_errno == EWOULDBLOCK)) { - goto return_rc; - } - - // close client socket and the session too - dcb->func.close(dcb); - } else { - // do nothing if reading 1 byte - } - - goto return_rc; - } - rc = gw_read_gwbuff(dcb, &read_buffer, b); - - if (rc != 0) { - goto return_rc; - } - nbytes_read = gwbuf_length(read_buffer); - ss_dassert(nbytes_read > 0); - + + if (nbytes_read == 0) + { + goto return_rc; + } /** * if read queue existed appent read to it. * if length of read buffer is less than 3 or less than mysql packet @@ -602,7 +564,8 @@ int gw_read_client_event(DCB* dcb) { else { /** - * There is at least one complete mysql packet read + * There is at least one complete mysql packet in + * read_buffer. */ read_buffer = dcb->dcb_readqueue; dcb->dcb_readqueue = NULL; @@ -611,9 +574,8 @@ int gw_read_client_event(DCB* dcb) { else { uint8_t* data = (uint8_t *)GWBUF_DATA(read_buffer); - size_t packetlen = MYSQL_GET_PACKET_LEN(data)+4; - if (nbytes_read < 3 || nbytes_read < packetlen) + if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data)+4) { gwbuf_append(dcb->dcb_readqueue, read_buffer); rc = 0; @@ -624,95 +586,117 @@ int gw_read_client_event(DCB* dcb) { /** * Now there should be at least one complete mysql packet in read_buffer. */ - switch (protocol->state) { + switch (protocol->protocol_auth_state) { case MYSQL_AUTH_SENT: - /* - * Read all the data that is available into a chain of buffers - */ { int auth_val = -1; auth_val = gw_mysql_do_authentication(dcb, read_buffer); - // Data handled withot the dcb->func.write - // so consume it now - // be sure to consume it all read_buffer = gwbuf_consume(read_buffer, nbytes_read); + ss_dassert(read_buffer == NULL || GWBUF_EMPTY(read_buffer)); if (auth_val == 0) { SESSION *session = NULL; - protocol->state = MYSQL_AUTH_RECV; - //write to client mysql AUTH_OK packet, packet n. is 2 - // start a new session, and connect to backends + protocol->protocol_auth_state = MYSQL_AUTH_RECV; + /** + * Create session, and a router session for it. + * If successful, there will be backend connection(s) + * after this point. + */ session = session_alloc(dcb->service, dcb); - if (session != NULL) { + if (session != NULL) + { CHK_SESSION(session); ss_dassert(session->state != SESSION_STATE_ALLOC); - protocol->state = MYSQL_IDLE; + + protocol->protocol_auth_state = MYSQL_IDLE; + /** + * Send an AUTH_OK packet to the client, + * packet sequence is # 2 + */ mysql_send_ok(dcb, 2, 0, NULL); - } else { - protocol->state = MYSQL_AUTH_FAILED; + } + else + { + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] session " + "creation failed. fd %d, " + "state = MYSQL_AUTH_FAILED.", + protocol->owner_dcb->fd, + pthread_self()))); + + /** Send ERR 1045 to client */ mysql_send_auth_error( dcb, 2, 0, "failed to create new session"); - dcb->func.close(dcb); + + dcb_close(dcb); } } else { - protocol->state = MYSQL_AUTH_FAILED; + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] after " + "gw_mysql_do_authentication, fd %d, " + "state = MYSQL_AUTH_FAILED.", + protocol->owner_dcb->fd, + pthread_self()))); + + /** Send ERR 1045 to client */ mysql_send_auth_error( dcb, 2, 0, "Authorization failed"); - dcb->func.close(dcb); + + dcb_close(dcb); } } break; case MYSQL_IDLE: - /* - * Read all the data that is available into a chain of buffers - */ { uint8_t cap = 0; - uint8_t *ptr_buff = NULL; - int mysql_command = -1; + uint8_t* payload = NULL; bool stmt_input; /*< router input type */ + ss_dassert(nbytes_read >= 5); + session = dcb->session; + ss_dassert( session!= NULL); - // get the backend session, if available - if (session != NULL) { + if (session != NULL) + { CHK_SESSION(session); router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; + ss_dassert(rsession != NULL); } /* Now, we are assuming in the first buffer there is * the information form mysql command */ - ptr_buff = GWBUF_DATA(read_buffer); - - /* get mysql commang at fifth byte */ - if (ptr_buff) { - ss_dassert(nbytes_read >= 5); - mysql_command = ptr_buff[4]; - } + payload = GWBUF_DATA(read_buffer); /** * Without rsession there is no access to backend. * COM_QUIT : close client dcb * else : write custom error to client dcb. */ - if(rsession == NULL) { + if(rsession == NULL) + { /** COM_QUIT */ - if (mysql_command == '\x01') { + if (MYSQL_IS_COM_QUIT(payload)) + { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_read_client_event] Client read " @@ -720,8 +704,20 @@ int gw_read_client_event(DCB* dcb) { "client dcb %p.", pthread_self(), dcb))); - (dcb->func).close(dcb); - } else { + /** + * close router session and that closes + * backends + */ + dcb_close(dcb); + } + else + { +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Client read error handling."))); +#endif + /* Send a custom error as MySQL command reply */ mysql_send_custom_error( dcb, @@ -729,16 +725,16 @@ int gw_read_client_event(DCB* dcb) { 0, "Can't route query. Connection to " "backend lost"); - protocol->state = MYSQL_IDLE; } rc = 1; /** Free buffer */ read_buffer = gwbuf_consume(read_buffer, nbytes_read); goto return_rc; } + /** Ask what type of input the router expects */ cap = router->getCapabilities(router_instance, rsession); - + if (cap == 0 || (cap == RCAP_TYPE_PACKET_INPUT)) { stmt_input = false; @@ -756,7 +752,6 @@ int gw_read_client_event(DCB* dcb) { "%lu [gw_read_client_event] Reading router " "capabilities failed.", pthread_self()))); - mysql_send_custom_error(dcb, 1, 0, @@ -765,19 +760,20 @@ int gw_read_client_event(DCB* dcb) { rc = 1; goto return_rc; } - /** Route COM_QUIT to backend */ - if (mysql_command == '\x01') { + if (MYSQL_IS_COM_QUIT(payload)) + { + /** + * Sends COM_QUIT packets since buffer is already + * created. A BREF_CLOSED flag is set so dcb_close won't + * send redundant COM_QUIT. + */ SESSION_ROUTE_QUERY(session, read_buffer); - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [gw_read_client_event] Routed COM_QUIT to " - "backend. Close client dcb %p", - pthread_self(), - dcb))); - /** close client connection, closes router session too */ - rc = dcb->func.close(dcb); + /** + * Close router session which causes closing of backends. + */ + dcb_close(dcb); } else { @@ -788,6 +784,7 @@ int gw_read_client_event(DCB* dcb) { * to router. */ rc = route_by_statement(session, read_buffer); + if (read_buffer != NULL) { /** add incomplete mysql packet to read queue */ @@ -801,16 +798,35 @@ int gw_read_client_event(DCB* dcb) { } /** succeed */ - if (rc == 1) { + if (rc) { rc = 0; /**< here '0' means success */ } else { - mysql_send_custom_error(dcb, - 1, - 0, - "Query routing failed. " - "Connection to backend " - "lost."); - protocol->state = MYSQL_IDLE; + GWBUF* errbuf; + bool succp; + + errbuf = mysql_create_custom_error( + 1, + 0, + "Write to backend failed. Session closed."); +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Client routing error handling."))); +#endif + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Routing the query failed. " + "Session will be closed."))); + + router->handleError(router_instance, + rsession, + errbuf, + dcb, + ERRACT_REPLY_CLIENT, + &succp); + ss_dassert(!succp); + + dcb_close(dcb); } } goto return_rc; @@ -872,8 +888,8 @@ int gw_write_client_event(DCB *dcb) } protocol = (MySQLProtocol *)dcb->protocol; CHK_PROTOCOL(protocol); - - if (protocol->state == MYSQL_IDLE) + + if (protocol->protocol_auth_state == MYSQL_IDLE) { dcb_drain_writeq(dcb); goto return_1; @@ -1170,23 +1186,31 @@ int gw_MySQLAccept(DCB *listener) client_dcb->fd = c_sock; // get client address - if ( client_conn.sa_family == AF_UNIX) { + if ( client_conn.sa_family == AF_UNIX) + { // client address client_dcb->remote = strdup("localhost_from_socket"); // set localhost IP for user authentication (client_dcb->ipv4).sin_addr.s_addr = 0x0100007F; - } else { + } + else + { /* client IPv4 in raw data*/ - memcpy(&client_dcb->ipv4, (struct sockaddr_in *)&client_conn, sizeof(struct sockaddr_in)); + memcpy(&client_dcb->ipv4, + (struct sockaddr_in *)&client_conn, + sizeof(struct sockaddr_in)); /* client IPv4 in string representation */ client_dcb->remote = (char *)calloc(INET_ADDRSTRLEN+1, sizeof(char)); - if (client_dcb->remote != NULL) { - inet_ntop(AF_INET, &(client_dcb->ipv4).sin_addr, client_dcb->remote, INET_ADDRSTRLEN); + + if (client_dcb->remote != NULL) + { + inet_ntop(AF_INET, + &(client_dcb->ipv4).sin_addr, + client_dcb->remote, + INET_ADDRSTRLEN); } } - protocol = mysql_protocol_init(client_dcb, c_sock); - ss_dassert(protocol != NULL); if (protocol == NULL) { @@ -1207,7 +1231,7 @@ int gw_MySQLAccept(DCB *listener) MySQLSendHandshake(client_dcb); // client protocol state change - protocol->state = MYSQL_AUTH_SENT; + protocol->protocol_auth_state = MYSQL_AUTH_SENT; /** * Set new descriptor to event set. At the same time, @@ -1223,7 +1247,7 @@ int gw_MySQLAccept(DCB *listener) 0, "MaxScale internal error."); - /** delete client_dcb */ + /** close client_dcb */ dcb_close(client_dcb); /** Previous state is recovered in poll_add_dcb. */ @@ -1260,14 +1284,29 @@ return_rc: static int gw_error_client_event( DCB* dcb) - { - int rc; +{ + SESSION* session; CHK_DCB(dcb); - - rc = dcb->func.close(dcb); - return rc; + session = dcb->session; + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_error_client_event] Error event handling for DCB %p " + "in state %s, session %p.", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + (session != NULL ? session : NULL)))); + +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Client error event handling."))); +#endif + dcb_close(dcb); + return 1; } static int @@ -1286,26 +1325,38 @@ gw_client_close(DCB *dcb) CHK_PROTOCOL(protocol); } #endif + mysql_protocol_done(dcb); session = dcb->session; /** * session may be NULL if session_alloc failed. * In that case, router session wasn't created. */ - if (session != NULL) { + if (session != NULL) + { CHK_SESSION(session); spinlock_acquire(&session->ses_lock); - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); + if (session->state == SESSION_STATE_STOPPING) + { + /** + * Session is already getting closed so avoid + * redundant calls + */ + spinlock_release(&session->ses_lock); + return 1; + } + else + { + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; - + /** Close router session and all its connections */ router->closeSession(router_instance, rsession); } - dcb_close(dcb); - return 1; } @@ -1320,18 +1371,28 @@ gw_client_close(DCB *dcb) static int gw_client_hangup_event(DCB *dcb) { - int rc; + SESSION* session; CHK_DCB(dcb); - rc = dcb->func.close(dcb); + session = dcb->session; - return rc; + if (session != NULL && session->state == SESSION_STATE_ROUTER_READY) + { + CHK_SESSION(session); + } +#if defined(SS_DEBUG) + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Client hangup error handling."))); +#endif + dcb_close(dcb); + return 1; } /** * Detect if buffer includes partial mysql packet or multiple packets. - * Store partial packet to pendingqueue. Send complete packets one by one + * Store partial packet to dcb_readqueue. Send complete packets one by one * to router. * * It is assumed readbuf includes at least one complete packet. @@ -1342,14 +1403,42 @@ static int route_by_statement(SESSION *session, GWBUF *readbuf) { int rc = -1; GWBUF* packetbuf; +#if defined(SS_DEBUG) + gwbuf_type_t prevtype; + GWBUF* tmpbuf; + tmpbuf = readbuf; + while (tmpbuf != NULL) + { + ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf)); + tmpbuf=tmpbuf->next; + } +#endif do { + ss_dassert(GWBUF_IS_TYPE_MYSQL(readbuf)); + packetbuf = gw_MySQL_get_next_packet(&readbuf); + ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf)); + if (packetbuf != NULL) { CHK_GWBUF(packetbuf); + /** + * This means that buffer includes exactly one MySQL + * statement. + * backend func.write uses the information. MySQL backend + * protocol, for example, stores the command identifier + * to protocol structure. When some other thread reads + * the corresponding response the command tells how to + * handle response. + * + * Set it here instead of gw_read_client_event to make + * sure it is set to each (MySQL) packet. + */ + gwbuf_set_type(packetbuf, GWBUF_TYPE_SINGLE_STMT); + /** Route query */ rc = SESSION_ROUTE_QUERY(session, packetbuf); } else diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 1d0932d7b..70050a13c 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -41,6 +41,9 @@ extern int gw_write_backend_event(DCB *dcb); extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); extern int gw_error_backend_event(DCB *dcb); +static server_command_t* server_command_init(server_command_t* srvcmd, + mysql_server_cmd_t cmd); + /** * Creates MySQL protocol structure @@ -76,7 +79,10 @@ MySQLProtocol* mysql_protocol_init( strerror(eno)))); goto return_p; } - p->state = MYSQL_ALLOC; + p->protocol_auth_state = MYSQL_ALLOC; + p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; + p->protocol_command.scom_nresponse_packets = 0; + p->protocol_command.scom_nbytes_to_read = 0; #if defined(SS_DEBUG) p->protocol_chk_top = CHK_NUM_PROTOCOL; p->protocol_chk_tail = CHK_NUM_PROTOCOL; @@ -90,6 +96,31 @@ return_p: } +/** + * mysql_protocol_done + * + * free protocol allocations. + * + * @param dcb owner DCB + * + */ +void mysql_protocol_done ( + DCB* dcb) +{ + server_command_t* scmd = ((MySQLProtocol *)dcb->protocol)->protocol_cmd_history; + server_command_t* scmd2; + + while (scmd != NULL) + { + scmd2 = scmd->scom_next; + free(scmd); + scmd = scmd2; + } +} + + + + /** * gw_mysql_close * @@ -126,7 +157,9 @@ void gw_mysql_close(MySQLProtocol **ptr) { * @param conn MySQL protocol structure * @return 0 on success, 1 on failure */ -int gw_read_backend_handshake(MySQLProtocol *conn) { +int gw_read_backend_handshake( + MySQLProtocol *conn) +{ GWBUF *head = NULL; DCB *dcb = conn->owner_dcb; int n = -1; @@ -135,23 +168,60 @@ int gw_read_backend_handshake(MySQLProtocol *conn) { int success = 0; int packet_len = 0; - if ((n = dcb_read(dcb, &head)) != -1) { - if (head) { + if ((n = dcb_read(dcb, &head)) != -1) + { + if (head) + { payload = GWBUF_DATA(head); h_len = gwbuf_length(head); - - /* + + /** * The mysql packets content starts at byte fifth * just return with less bytes */ if (h_len <= 4) { /* log error this exit point */ - conn->state = MYSQL_AUTH_FAILED; + conn->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_handshake] after " + "dcb_read, fd %d, " + "state = MYSQL_AUTH_FAILED.", + dcb->fd, + pthread_self()))); + return 1; } - //get mysql packet size, 3 bytes + if (payload[4] == 0xff) + { + size_t len = MYSQL_GET_PACKET_LEN(payload); + uint16_t errcode = MYSQL_GET_ERRCODE(payload); + char* bufstr = strndup(&((char *)payload)[7], len-3); + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_receive_backend_auth] Invalid " + "authentication message from backend dcb %p " + "fd %d, ptr[4] = %p, error code %d, msg %s.", + pthread_self(), + dcb, + dcb->fd, + payload[4], + errcode, + bufstr))); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Invalid authentication message " + "from backend. Error code: %d, Msg : %s", + errcode, + bufstr))); + + free(bufstr); + } + //get mysql packet size, 3 bytes packet_len = gw_mysql_get_byte3(payload); if (h_len < (packet_len + 4)) { @@ -159,7 +229,16 @@ int gw_read_backend_handshake(MySQLProtocol *conn) { * data in buffer less than expected in the * packet. Log error this exit point */ - conn->state = MYSQL_AUTH_FAILED; + conn->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_handshake] after " + "gw_mysql_get_byte3, fd %d, " + "state = MYSQL_AUTH_FAILED.", + pthread_self(), + dcb->fd, + pthread_self()))); + return 1; } @@ -175,11 +254,20 @@ int gw_read_backend_handshake(MySQLProtocol *conn) { * we cannot continue * log error this exit point */ - conn->state = MYSQL_AUTH_FAILED; + conn->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_backend_handshake] after " + "gw_decode_mysql_server_handshake, fd %d, " + "state = MYSQL_AUTH_FAILED.", + pthread_self(), + conn->owner_dcb->fd, + pthread_self()))); + return 1; } - conn->state = MYSQL_AUTH_SENT; + conn->protocol_auth_state = MYSQL_AUTH_SENT; // consume all the data here head = gwbuf_consume(head, GWBUF_LENGTH(head)); @@ -202,7 +290,10 @@ int gw_read_backend_handshake(MySQLProtocol *conn) { * @return 0 on success, < 0 on failure * */ -int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { +int gw_decode_mysql_server_handshake( + MySQLProtocol *conn, + uint8_t *payload) +{ uint8_t *server_version_end = NULL; uint16_t mysql_server_capabilities_one = 0; uint16_t mysql_server_capabilities_two = 0; @@ -216,8 +307,8 @@ int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { protocol_version = payload[0]; - if (protocol_version != GW_MYSQL_PROTOCOL_VERSION) { - /* log error for this */ + if (protocol_version != GW_MYSQL_PROTOCOL_VERSION) + { return -1; } @@ -257,19 +348,23 @@ int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { payload+=2; // get scramble len - if (payload[0] > 0) { + if (payload[0] > 0) + { scramble_len = payload[0] -1; ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323); ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE); - if ( (scramble_len < GW_SCRAMBLE_LENGTH_323) || scramble_len > GW_MYSQL_SCRAMBLE_SIZE) { + if ((scramble_len < GW_SCRAMBLE_LENGTH_323) || + scramble_len > GW_MYSQL_SCRAMBLE_SIZE) + { /* log this */ - return -2; + return -2; } - } else { + } + else + { scramble_len = GW_MYSQL_SCRAMBLE_SIZE; } - // skip 10 zero bytes payload += 11; @@ -321,26 +416,27 @@ int gw_receive_backend_auth( } else if (ptr[4] == 0xff) { - size_t packetlen = MYSQL_GET_PACKET_LEN(ptr)+4; - char* bufstr = (char *)calloc(1, packetlen-3); - - snprintf(bufstr, packetlen-6, "%s", &ptr[7]); - + size_t len = MYSQL_GET_PACKET_LEN(ptr); + char* err = strndup(&((char *)ptr)[8], 5); + char* bufstr = strndup(&((char *)ptr)[13], len-4-5); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Invalid " "authentication message from backend dcb %p " - "fd %d, ptr[4] = %p, msg %s.", + "fd %d, ptr[4] = %p, error %s, msg %s.", pthread_self(), dcb, dcb->fd, ptr[4], + err, bufstr))); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Invalid authentication message " - "from backend. Msg : %s", + "from backend. Error : %s, Msg : %s", + err, bufstr))); free(bufstr); @@ -367,7 +463,7 @@ int gw_receive_backend_auth( /*< * Remove data from buffer. */ - head = gwbuf_consume(head, GWBUF_LENGTH(head)); + while ((head = gwbuf_consume(head, GWBUF_LENGTH(head))) != NULL); } else if (n == 0) { @@ -634,8 +730,8 @@ int gw_do_connect_to_backend( LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error: Establishing connection to backend server " - "%s:%d failed.\n\t\t Socket creation failed due " - "%d, %s.", + "%s:%d failed.\n\t\t Socket creation failed " + "due %d, %s.", host, port, eno, @@ -724,18 +820,151 @@ gw_mysql_protocol_state2string (int state) { case MYSQL_AUTH_FAILED: return "MySQL Authentication failed"; case MYSQL_IDLE: - return "MySQL Auth done. Protocol is idle, waiting for statements"; - case MYSQL_ROUTING: - return "MySQL received command has been routed to backend(s)"; - case MYSQL_WAITING_RESULT: - return "MySQL Waiting for result set"; - case MYSQL_SESSION_CHANGE: - return "MySQL change session"; + return "MySQL authentication is succesfully done."; default: return "MySQL (unknown protocol state)"; } } +GWBUF* mysql_create_com_quit( + GWBUF* bufparam, + int packet_number) +{ + uint8_t* data; + GWBUF* buf; + + if (bufparam == NULL) + { + buf = gwbuf_alloc(COM_QUIT_PACKET_SIZE); + } + else + { + buf = bufparam; + } + + if (buf == NULL) + { + return 0; + } + ss_dassert(GWBUF_LENGTH(buf) == COM_QUIT_PACKET_SIZE); + + data = GWBUF_DATA(buf); + + *data++ = 0x1; + *data++ = 0x0; + *data++ = 0x0; + *data++ = packet_number; + *data = 0x1; + + return buf; +} + +int mysql_send_com_quit( + DCB* dcb, + int packet_number, + GWBUF* bufparam) +{ + GWBUF *buf; + int nbytes = 0; + + CHK_DCB(dcb); + ss_dassert(packet_number <= 255); + + if (dcb == NULL || dcb->state == DCB_STATE_ZOMBIE) + { + return 0; + } + if (bufparam == NULL) + { + buf = mysql_create_com_quit(NULL, packet_number); + } + else + { + buf = bufparam; + } + + if (buf == NULL) + { + return 0; + } + nbytes = dcb->func.write(dcb, buf); + + return nbytes; +} + + +GWBUF* mysql_create_custom_error( + int packet_number, + int affected_rows, + const char* msg) +{ + uint8_t* outbuf = NULL; + uint8_t mysql_payload_size = 0; + uint8_t mysql_packet_header[4]; + uint8_t* mysql_payload = NULL; + uint8_t field_count = 0; + uint8_t mysql_err[2]; + uint8_t mysql_statemsg[6]; + unsigned int mysql_errno = 0; + const char* mysql_error_msg = NULL; + const char* mysql_state = NULL; + + GWBUF* errbuf = NULL; + + mysql_errno = 2003; + mysql_error_msg = "An errorr occurred ..."; + mysql_state = "HY000"; + + field_count = 0xff; + gw_mysql_set_byte2(mysql_err, mysql_errno); + mysql_statemsg[0]='#'; + memcpy(mysql_statemsg+1, mysql_state, 5); + + if (msg != NULL) { + mysql_error_msg = msg; + } + + mysql_payload_size = sizeof(field_count) + + sizeof(mysql_err) + + sizeof(mysql_statemsg) + + strlen(mysql_error_msg); + + /** allocate memory for packet header + payload */ + errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size); + ss_dassert(errbuf != NULL); + + if (errbuf == NULL) + { + return 0; + } + outbuf = GWBUF_DATA(errbuf); + + /** write packet header and packet number */ + gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size); + mysql_packet_header[3] = packet_number; + + /** write header */ + memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header)); + + mysql_payload = outbuf + sizeof(mysql_packet_header); + + /** write field */ + memcpy(mysql_payload, &field_count, sizeof(field_count)); + mysql_payload = mysql_payload + sizeof(field_count); + + /** write errno */ + memcpy(mysql_payload, mysql_err, sizeof(mysql_err)); + mysql_payload = mysql_payload + sizeof(mysql_err); + + /** write sqlstate */ + memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg)); + mysql_payload = mysql_payload + sizeof(mysql_statemsg); + + /** write error message */ + memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); + + return errbuf; +} /** * mysql_send_custom_error * @@ -749,79 +978,19 @@ gw_mysql_protocol_state2string (int state) { * @return packet length * */ -int -mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { - uint8_t *outbuf = NULL; - uint8_t mysql_payload_size = 0; - uint8_t mysql_packet_header[4]; - uint8_t *mysql_payload = NULL; - uint8_t field_count = 0; - uint8_t mysql_err[2]; - uint8_t mysql_statemsg[6]; - unsigned int mysql_errno = 0; - const char *mysql_error_msg = NULL; - const char *mysql_state = NULL; +int mysql_send_custom_error ( + DCB *dcb, + int packet_number, + int in_affected_rows, + const char *mysql_message) +{ + GWBUF* buf; - GWBUF *buf = NULL; - - if (dcb == NULL || - dcb->state != DCB_STATE_POLLING) - { - return 0; - } - mysql_errno = 2003; - mysql_error_msg = "An errorr occurred ..."; - mysql_state = "HY000"; - - field_count = 0xff; - gw_mysql_set_byte2(mysql_err, mysql_errno); - mysql_statemsg[0]='#'; - memcpy(mysql_statemsg+1, mysql_state, 5); - - if (mysql_message != NULL) { - mysql_error_msg = mysql_message; - } - - mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg); - - // allocate memory for packet header + payload - buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size); - ss_dassert(buf != NULL); + buf = mysql_create_custom_error(packet_number, in_affected_rows, mysql_message); - if (buf == NULL) - { - return 0; - } - outbuf = GWBUF_DATA(buf); - - // write packet header with packet number - gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size); - mysql_packet_header[3] = packet_number; - - // write header - memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header)); - - mysql_payload = outbuf + sizeof(mysql_packet_header); - - // write field - memcpy(mysql_payload, &field_count, sizeof(field_count)); - mysql_payload = mysql_payload + sizeof(field_count); - - // write errno - memcpy(mysql_payload, mysql_err, sizeof(mysql_err)); - mysql_payload = mysql_payload + sizeof(mysql_err); - - // write sqlstate - memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg)); - mysql_payload = mysql_payload + sizeof(mysql_statemsg); - - // write err messg - memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); - - // writing data in the Client buffer queue dcb->func.write(dcb, buf); - return sizeof(mysql_packet_header) + mysql_payload_size; + return GWBUF_LENGTH(buf); } /** @@ -1229,7 +1398,12 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, * */ int -mysql_send_auth_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { +mysql_send_auth_error ( + DCB *dcb, + int packet_number, + int in_affected_rows, + const char *mysql_message) +{ uint8_t *outbuf = NULL; uint8_t mysql_payload_size = 0; uint8_t mysql_packet_header[4]; @@ -1349,7 +1523,7 @@ GWBUF* gw_MySQL_get_next_packet( packetbuf = NULL; goto return_packetbuf; } - + /** there is one complete packet in the buffer */ if (packetlen == buflen) { packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); @@ -1390,3 +1564,328 @@ return_packetbuf: return packetbuf; } + +/** + * Move from buffer pointed to by <*p_readbuf>. + */ +GWBUF* gw_MySQL_get_packets( + GWBUF** p_srcbuf, + int* npackets) +{ + GWBUF* packetbuf; + GWBUF* targetbuf = NULL; + + while (*npackets > 0 && (packetbuf = gw_MySQL_get_next_packet(p_srcbuf)) != NULL) + { + targetbuf = gwbuf_append(targetbuf, packetbuf); + *npackets -= 1; + } + ss_dassert(*npackets < 128); + ss_dassert(*npackets >= 0); + + return targetbuf; +} + + +static server_command_t* server_command_init( + server_command_t* srvcmd, + mysql_server_cmd_t cmd) +{ + server_command_t* c; + + if (srvcmd != NULL) + { + c = srvcmd; + } + else + { + c = (server_command_t *)malloc(sizeof(server_command_t)); + } + c->scom_cmd = cmd; + c->scom_nresponse_packets = -1; + c->scom_nbytes_to_read = 0; + c->scom_next = NULL; + + return c; +} + +static server_command_t* server_command_copy( + server_command_t* srvcmd) +{ + server_command_t* c = + (server_command_t *)malloc(sizeof(server_command_t)); + *c = *srvcmd; + + return c; +} + +#define MAX_CMD_HISTORY 10 + +void protocol_archive_srv_command( + MySQLProtocol* p) +{ + server_command_t* s1; + server_command_t** s2; + int len = 0; + + spinlock_acquire(&p->protocol_lock); + + s1 = &p->protocol_command; + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Move command %s from fd %d to command history.", + STRPACKETTYPE(s1->scom_cmd), + p->owner_dcb->fd))); + + /** Copy to history list */ + s2 = &p->protocol_cmd_history; + + if (*s2 != NULL) + { + while ((*s2)->scom_next != NULL) + { + *s2 = (*s2)->scom_next; + len += 1; + } + } + *s2 = server_command_copy(s1); + + /** Keep history limits, remove oldest */ + if (len > MAX_CMD_HISTORY) + { + server_command_t* c = p->protocol_cmd_history; + p->protocol_cmd_history = p->protocol_cmd_history->scom_next; + free(c); + } + + /** Remove from command list */ + if (s1->scom_next == NULL) + { + p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; + } + else + { + p->protocol_command = *(s1->scom_next); + free(s1->scom_next); + } + spinlock_release(&p->protocol_lock); +} + + +/** + * If router expects to get separate, complete statements, add MySQL command + * to MySQLProtocol structure. It is removed when response has arrived. + */ +void protocol_add_srv_command( + MySQLProtocol* p, + mysql_server_cmd_t cmd) +{ + server_command_t* c; + + spinlock_acquire(&p->protocol_lock); + + /** this is the only server command in protocol */ + if (p->protocol_command.scom_cmd == MYSQL_COM_UNDEFINED) + { + /** write into structure */ + server_command_init(&p->protocol_command, cmd); + } + else + { + /** add to the end of list */ + p->protocol_command.scom_next = server_command_init(NULL, cmd); + } + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Added command %s to fd %d.", + STRPACKETTYPE(cmd), + p->owner_dcb->fd))); + +#if defined(SS_DEBUG) + c = &p->protocol_command; + + while (c != NULL && c->scom_cmd != MYSQL_COM_UNDEFINED) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "fd %d : %d %s", + p->owner_dcb->fd, + c->scom_cmd, + STRPACKETTYPE(c->scom_cmd)))); + c = c->scom_next; + } +#endif + spinlock_release(&p->protocol_lock); +} + + +/** + * If router processes separate statements, every stmt has corresponding MySQL + * command stored in MySQLProtocol structure. + * + * Remove current (=oldest) command. + */ +void protocol_remove_srv_command( + MySQLProtocol* p) +{ + server_command_t* s; + spinlock_acquire(&p->protocol_lock); + s = &p->protocol_command; + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Removed command %s from fd %d.", + STRPACKETTYPE(s->scom_cmd), + p->owner_dcb->fd))); + + if (s->scom_next == NULL) + { + p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; + } + else + { + p->protocol_command = *(s->scom_next); + free(s->scom_next); + } + + spinlock_release(&p->protocol_lock); +} + +mysql_server_cmd_t protocol_get_srv_command( + MySQLProtocol* p, + bool removep) +{ + mysql_server_cmd_t cmd; + + cmd = p->protocol_command.scom_cmd; + + if (removep) + { + protocol_remove_srv_command(p); + } + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [protocol_get_srv_command] Read command %s for fd %d.", + pthread_self(), + STRPACKETTYPE(cmd), + p->owner_dcb->fd))); + return cmd; +} + + +/** + * Examine command type and the readbuf. Conclude response + * packet count from the command type or from the first packet + * content. + * Fails if read buffer doesn't include enough data to read the + * packet length. + */ +void init_response_status ( + GWBUF* buf, + mysql_server_cmd_t cmd, + int* npackets, + size_t* nbytes_left) +{ + uint8_t* packet; + int nparam; + int nattr; + uint8_t* data; + + ss_dassert(gwbuf_length(buf) >= 3); + + data = (uint8_t *)buf->start; + + if (data[4] == 0xff) /*< error */ + { + *npackets = 1; + } + else + { + switch (cmd) { + case MYSQL_COM_STMT_PREPARE: + packet = (uint8_t *)GWBUF_DATA(buf); + /** ok + nparam + eof + nattr + eof */ + nparam = MYSQL_GET_STMTOK_NPARAM(packet); + nattr = MYSQL_GET_STMTOK_NATTR(packet); + + *npackets = 1 + nparam + MIN(1, nparam) + + nattr + MIN(nattr, 1); + break; + + case MYSQL_COM_QUIT: + case MYSQL_COM_STMT_SEND_LONG_DATA: + case MYSQL_COM_STMT_CLOSE: + *npackets = 0; /*< these don't reply anything */ + break; + + default: + /** + * assume that other session commands respond + * OK or ERR + */ + *npackets = 1; + break; + } + } + *nbytes_left = MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN; + /** + * There is at least one complete packet in the buffer so buffer is bigger + * than packet + */ + ss_dassert(*nbytes_left > 0); + ss_dassert(*npackets > 0); + ss_dassert(*npackets<128); +} + + + +/** + * Read how many packets are left from current response and how many bytes there + * is still to be read from the current packet. + */ +bool protocol_get_response_status ( + MySQLProtocol* p, + int* npackets, + size_t* nbytes) +{ + bool succp; + + CHK_PROTOCOL(p); + + spinlock_acquire(&p->protocol_lock); + *npackets = p->protocol_command.scom_nresponse_packets; + *nbytes = p->protocol_command.scom_nbytes_to_read; + spinlock_release(&p->protocol_lock); + + if (*npackets < 0 && *nbytes == 0) + { + succp = false; + } + else + { + succp = true; + } + + return succp; +} + +void protocol_set_response_status ( + MySQLProtocol* p, + int npackets_left, + size_t nbytes) +{ + + CHK_PROTOCOL(p); + + spinlock_acquire(&p->protocol_lock); + + p->protocol_command.scom_nbytes_to_read = nbytes; + ss_dassert(p->protocol_command.scom_nbytes_to_read >= 0); + + p->protocol_command.scom_nresponse_packets = npackets_left; + + spinlock_release(&p->protocol_lock); +} + diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index 86e98f397..aeb6607c4 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -40,7 +40,7 @@ MODULE_INFO info = { MODULE_API_PROTOCOL, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, GWPROTOCOL_VERSION, "A telnet deamon protocol for simple administration interface" }; @@ -83,17 +83,17 @@ static int telnetd_listen(DCB *dcb, char *config); * The "module object" for the telnetd protocol module. */ static GWPROTOCOL MyObject = { - telnetd_read_event, /**< Read - EPOLLIN handler */ - telnetd_write, /**< Write - data from gateway */ - telnetd_write_event, /**< WriteReady - EPOLLOUT handler */ - telnetd_error, /**< Error - EPOLLERR handler */ - telnetd_hangup, /**< HangUp - EPOLLHUP handler */ - telnetd_accept, /**< Accept */ - NULL, /**< Connect */ - telnetd_close, /**< Close */ - telnetd_listen, /**< Create a listener */ - NULL, /**< Authentication */ - NULL /**< Session */ + telnetd_read_event, /**< Read - EPOLLIN handler */ + telnetd_write, /**< Write - data from gateway */ + telnetd_write_event, /**< WriteReady - EPOLLOUT handler */ + telnetd_error, /**< Error - EPOLLERR handler */ + telnetd_hangup, /**< HangUp - EPOLLHUP handler */ + telnetd_accept, /**< Accept */ + NULL, /**< Connect */ + telnetd_close, /**< Close */ + telnetd_listen, /**< Create a listener */ + NULL, /**< Authentication */ + NULL /**< Session */ }; static void telnetd_command(DCB *, unsigned char *cmd); @@ -343,8 +343,7 @@ TELNETD *telnetd = dcb->protocol; if (telnetd && telnetd->username) free(telnetd->username); - dcb_close(dcb); - return 0; + return 0; } /** diff --git a/server/modules/routing/GaleraHACRoute.c b/server/modules/routing/GaleraHACRoute.c index cf3e833ed..33a9597cc 100644 --- a/server/modules/routing/GaleraHACRoute.c +++ b/server/modules/routing/GaleraHACRoute.c @@ -58,12 +58,14 @@ static void GHACloseSession(ROUTER *instance, void *router_session); static void GHAFreeSession(ROUTER *instance, void *router_session); static int GHARouteQuery(ROUTER *instance, void *router_session, GWBUF *queue); static void GHADiagnostics(ROUTER *instance, DCB *dcb); + static void GHAClientReply( ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb); -static void GHAErrorReply( + +static void GHAHandleError( ROUTER *instance, void *router_session, char *message, @@ -79,7 +81,7 @@ static ROUTER_OBJECT MyObject = { GHARouteQuery, GHADiagnostics, GHAClientReply, - GHAErrorReply + GHAHandleError }; static bool rses_begin_router_action( @@ -489,7 +491,7 @@ DCB* backend_dcb; */ if (backend_dcb != NULL) { CHK_DCB(backend_dcb); - backend_dcb->func.close(backend_dcb); + dcb_close(backend_dcb); } } } @@ -630,10 +632,9 @@ GHAClientReply( } /** - * Error Reply routine + * Error handling routine * - * The routine will reply to client errors and/or closing the session - * or try to open a new backend connection. + * The routine will handle error occurred in backend. * * @param instance The router instance * @param router_session The router session @@ -643,7 +644,7 @@ GHAClientReply( * */ static void -GHAErrorReply( +GHAHandleError( ROUTER *instance, void *router_session, char *message, diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 5e2c45845..6dbb9cd4f 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -42,10 +42,12 @@ READCONSRCS=readconnroute.c READCONOBJ=$(READCONSRCS:.c=.o) DEBUGCLISRCS=debugcli.c debugcmd.c DEBUGCLIOBJ=$(DEBUGCLISRCS:.c=.o) -SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) +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 -MODULES= libdebugcli.so libreadconnroute.so libtestroute.so +MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so all: $(MODULES) @@ -61,6 +63,9 @@ libreadconnroute.so: $(READCONOBJ) libdebugcli.so: $(DEBUGCLIOBJ) $(CC) $(LDFLAGS) $(DEBUGCLIOBJ) $(LIBS) -o $@ +libcli.so: $(CLIOBJ) + $(CC) $(LDFLAGS) $(CLIOBJ) $(LIBS) -o $@ + libreadwritesplit.so: # (cd readwritesplit; touch depend.mk ; make; cp $@ ..) @@ -82,7 +87,7 @@ depend: (cd binlog; touch depend.mk ; make depend) install: $(MODULES) - install -D $(MODULES) $(DEST)/MaxScale/modules + install -D $(MODULES) $(DEST)/modules (cd readwritesplit; make DEST=$(DEST) install) (cd binlog; make DEST=$(DEST) install) diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index e25f688ff..1633c374e 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -58,7 +58,7 @@ extern int lm_enabled_logfiles_bitmask; -static char *version_str = "V1.0.1"; +static char *version_str = "V1.0.6"; /* The router entry points */ static ROUTER *createInstance(SERVICE *service, char **options); @@ -343,6 +343,8 @@ ROUTER_SLAVE *slave; atomic_add(&inst->stats.n_slaves, 1); slave->state = BLRS_CREATED; /* Set initial state of the slave */ slave->cstate = 0; + slave->pthread = 0; + slave->overrun = 0; spinlock_init(&slave->catch_lock); slave->dcb = session->client; slave->router = instance; @@ -501,6 +503,20 @@ static char *event_names[] = { "Update Rows Event (v2)", "Delete Rows Event (v2)", "GTID Event", "Anonymous GTID Event", "Previous GTIDS Event" }; + +/** + * Display an entry from the spinlock statistics data + * + * @param dcb The DCB to print to + * @param desc Description of the statistic + * @param value The statistic value + */ +static void +spin_reporter(void *dcb, char *desc, int value) +{ + dcb_printf((DCB *)dcb, "\t\t%-35s %d\n", desc, value); +} + /** * Display router diagnostics * @@ -585,6 +601,13 @@ struct tm tm; dcb_printf(dcb, "\t\t%-38s: %u\n", event_names[i], router_inst->stats.events[i]); } +#if SPINLOCK_PROFILE + dcb_printf(dcb, "\tSpinlock statistics (instlock):\n"); + spinlock_stats(&instlock, spin_reporter, dcb); + dcb_printf(dcb, "\tSpinlock statistics (instance lock):\n"); + spinlock_stats(&router_inst->lock, spin_reporter, dcb); +#endif + if (router_inst->slaves) { dcb_printf(dcb, "\tSlaves:\n"); @@ -592,26 +615,55 @@ struct tm tm; session = router_inst->slaves; while (session) { - dcb_printf(dcb, "\t\tServer-id: %d\n", session->serverid); + dcb_printf(dcb, "\t\tServer-id: %d\n", session->serverid); if (session->hostname) - dcb_printf(dcb, "\t\tHostname: %s\n", session->hostname); - dcb_printf(dcb, "\t\tSlave DCB: %p\n", session->dcb); - dcb_printf(dcb, "\t\tNext Sequence No: %d\n", session->seqno); - dcb_printf(dcb, "\t\tState: %s\n", blrs_states[session->state]); - dcb_printf(dcb, "\t\tBinlog file: %s\n", session->binlogfile); - dcb_printf(dcb, "\t\tBinlog position: %u\n", session->binlog_pos); - dcb_printf(dcb, "\t\tNo. requests: %u\n", session->stats.n_requests); - dcb_printf(dcb, "\t\tNo. events sent: %u\n", session->stats.n_events); - dcb_printf(dcb, "\t\tNo. bursts sent: %u\n", session->stats.n_bursts); - dcb_printf(dcb, "\t\tNo. flow control: %u\n", session->stats.n_flows); + dcb_printf(dcb, "\t\tHostname: %s\n", session->hostname); + dcb_printf(dcb, "\t\tSlave DCB: %p\n", session->dcb); + dcb_printf(dcb, "\t\tNext Sequence No: %d\n", session->seqno); + dcb_printf(dcb, "\t\tState: %s\n", blrs_states[session->state]); + dcb_printf(dcb, "\t\tBinlog file: %s\n", session->binlogfile); + dcb_printf(dcb, "\t\tBinlog position: %u\n", session->binlog_pos); + if (session->nocrc) + dcb_printf(dcb, "\t\tMaster Binlog CRC: None\n"); + dcb_printf(dcb, "\t\tNo. requests: %u\n", session->stats.n_requests); + dcb_printf(dcb, "\t\tNo. events sent: %u\n", session->stats.n_events); + dcb_printf(dcb, "\t\tNo. bursts sent: %u\n", session->stats.n_bursts); + dcb_printf(dcb, "\t\tNo. flow control: %u\n", session->stats.n_flows); + dcb_printf(dcb, "\t\tNo. catchup NRs: %u\n", session->stats.n_catchupnr); + dcb_printf(dcb, "\t\tNo. already up to date: %u\n", session->stats.n_alreadyupd); + dcb_printf(dcb, "\t\tNo. up to date: %u\n", session->stats.n_upd); + dcb_printf(dcb, "\t\tNo. of low water cbs %u\n", session->stats.n_cb); + dcb_printf(dcb, "\t\tNo. of drained cbs %u\n", session->stats.n_dcb); + dcb_printf(dcb, "\t\tNo. of low water cbs N/A %u\n", session->stats.n_cbna); + dcb_printf(dcb, "\t\tNo. of events > high water %u\n", session->stats.n_above); + dcb_printf(dcb, "\t\tNo. of failed reads %u\n", session->stats.n_failed_read); + dcb_printf(dcb, "\t\tNo. of nested distribute events %u\n", session->stats.n_overrun); + dcb_printf(dcb, "\t\tNo. of distribute action 1 %u\n", session->stats.n_actions[0]); + dcb_printf(dcb, "\t\tNo. of distribute action 2 %u\n", session->stats.n_actions[1]); + dcb_printf(dcb, "\t\tNo. of distribute action 3 %u\n", session->stats.n_actions[2]); if ((session->cstate & CS_UPTODATE) == 0) { dcb_printf(dcb, "\t\tSlave is in catchup mode. %s\n", ((session->cstate & CS_EXPECTCB) == 0 ? "" : "Waiting for DCB queue to drain.")); + } else + { dcb_printf(dcb, "\t\tSlave is in normal mode.\n"); + if (session->binlog_pos != router_inst->binlog_position) + { + dcb_printf(dcb, "\t\tSlave reports up to date however " + "the slave binlog position does not match the master\n"); + } + } +#if SPINLOCK_PROFILE + dcb_printf(dcb, "\tSpinlock statistics (catch_lock):\n"); + spinlock_stats(&session->catch_lock, spin_reporter, dcb); + dcb_printf(dcb, "\tSpinlock statistics (rses_lock):\n"); + spinlock_stats(&session->rses_lock, spin_reporter, dcb); +#endif + session = session->next; } spinlock_release(&router_inst->lock); diff --git a/server/modules/routing/binlog/blr_file.c b/server/modules/routing/binlog/blr_file.c index 7632b9d2e..a17ea5e92 100644 --- a/server/modules/routing/binlog/blr_file.c +++ b/server/modules/routing/binlog/blr_file.c @@ -30,6 +30,7 @@ */ #include #include +#include #include #include #include @@ -257,12 +258,20 @@ unsigned char *data; if (lseek(fd, pos, SEEK_SET) != pos) { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "Failed to seek for binlog entry, " + "at %d.\n", pos))); return NULL; } /* Read the header information from the file */ if (read(fd, hdbuf, 19) != 19) + { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "Failed to read header for binlog entry, " + "at %d (%s).\n", pos, strerror(errno)))); return NULL; + } hdr->timestamp = extract_field(hdbuf, 32); hdr->event_type = hdbuf[4]; hdr->serverid = extract_field(&hdbuf[5], 32); @@ -272,7 +281,9 @@ unsigned char *data; if ((result = gwbuf_alloc(hdr->event_size)) == NULL) { LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, - "Failed to allocate memory for binlog entry.\n"))); + "Failed to allocate memory for binlog entry, " + "size %d at %d.\n", + hdr->event_size, pos))); return NULL; } data = GWBUF_DATA(result); diff --git a/server/modules/routing/binlog/blr_master.c b/server/modules/routing/binlog/blr_master.c index 800535e65..ef32f5ccf 100644 --- a/server/modules/routing/binlog/blr_master.c +++ b/server/modules/routing/binlog/blr_master.c @@ -359,6 +359,27 @@ char query[128]; case BLRM_LATIN1: // Response to the SET NAMES latin1, should be stored router->saved_master.setnames = buf; + buf = blr_make_query("SET NAMES utf8"); + router->master_state = BLRM_UTF8; + router->master->func.write(router->master, buf); + break; + case BLRM_UTF8: + // Response to the SET NAMES utf8, should be stored + router->saved_master.utf8 = buf; + buf = blr_make_query("SELECT 1"); + router->master_state = BLRM_SELECT1; + router->master->func.write(router->master, buf); + break; + case BLRM_SELECT1: + // Response to the SELECT 1, should be stored + router->saved_master.select1 = buf; + buf = blr_make_query("SELECT VERSION();"); + router->master_state = BLRM_SELECTVER; + router->master->func.write(router->master, buf); + break; + case BLRM_SELECTVER: + // Response to SELECT VERSION should be stored + router->saved_master.selectver = buf; buf = blr_make_registration(router); router->master_state = BLRM_REGISTER; router->master->func.write(router->master, buf); @@ -879,59 +900,123 @@ MYSQL_session *auth_info; * * @param router The router instance * @param hdr The replication event header - * @param ptr The raw replication eent data + * @param ptr The raw replication event data */ static void blr_distribute_binlog_record(ROUTER_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr) { -GWBUF *pkt; +GWBUF *pkt, *distq; uint8_t *buf; ROUTER_SLAVE *slave; +int action; spinlock_acquire(&router->lock); slave = router->slaves; while (slave) { - if ((slave->binlog_pos == hdr->next_pos - hdr->event_size) - && strcmp(slave->binlogfile, router->binlog_name) == 0) + spinlock_acquire(&slave->catch_lock); + if ((slave->cstate & (CS_UPTODATE|CS_DIST)) == CS_UPTODATE) { - pkt = gwbuf_alloc(hdr->event_size + 5); - buf = GWBUF_DATA(pkt); - encode_value(buf, hdr->event_size + 1, 24); - buf += 3; - *buf++ = slave->seqno++; - *buf++ = 0; // OK - memcpy(buf, ptr, hdr->event_size); - if (hdr->event_type == ROTATE_EVENT) - { - blr_slave_rotate(slave, ptr); - } - slave->dcb->func.write(slave->dcb, pkt); - if (hdr->event_type != ROTATE_EVENT) - { - slave->binlog_pos = hdr->next_pos; - } - } - else if ((hdr->event_type != ROTATE_EVENT) - && (slave->binlog_pos != hdr->next_pos || - strcmp(slave->binlogfile, router->binlog_name) != 0)) - { - /* Check slave is in catchup mode and if not - * force it to go into catchup mode. + /* Slave is up to date with the binlog and no distribute is + * running on this slave. */ - if (slave->cstate & CS_UPTODATE) + action = 1; + slave->cstate |= CS_DIST; + } + else if ((slave->cstate & (CS_UPTODATE|CS_DIST)) == (CS_UPTODATE|CS_DIST)) + { + /* Slave is up to date with the binlog and a distribute is + * running on this slave. + */ + slave->overrun = 1; + action = 2; + } + else if ((slave->cstate & CS_UPTODATE) == 0) + { + /* Slave is in catchup mode */ + action = 3; + } + slave->stats.n_actions[action-1]++; + spinlock_release(&slave->catch_lock); + if (action == 1) + { + if ((slave->binlog_pos == hdr->next_pos - hdr->event_size) + && (strcmp(slave->binlogfile, router->binlog_name) == 0 || + hdr->event_type == ROTATE_EVENT)) { - spinlock_release(&router->lock); + pkt = gwbuf_alloc(hdr->event_size + 5); + buf = GWBUF_DATA(pkt); + encode_value(buf, hdr->event_size + 1, 24); + buf += 3; + *buf++ = slave->seqno++; + *buf++ = 0; // OK + memcpy(buf, ptr, hdr->event_size); + if (hdr->event_type == ROTATE_EVENT) + { + blr_slave_rotate(slave, ptr); + } + slave->dcb->func.write(slave->dcb, pkt); + if (hdr->event_type != ROTATE_EVENT) + { + slave->binlog_pos = hdr->next_pos; + } spinlock_acquire(&slave->catch_lock); - slave->cstate &= ~CS_UPTODATE; - spinlock_release(&slave->catch_lock); - blr_slave_catchup(router, slave); - spinlock_acquire(&router->lock); - slave = router->slaves; - if (slave) - continue; + if (slave->overrun) + { + slave->stats.n_overrun++; + slave->overrun = 0; + spinlock_release(&router->lock); + slave->cstate &= ~(CS_UPTODATE|CS_DIST); + spinlock_release(&slave->catch_lock); + blr_slave_catchup(router, slave); + spinlock_acquire(&router->lock); + slave = router->slaves; + if (slave) + continue; + else + break; + } else - break; + { + slave->cstate &= ~CS_DIST; + } + spinlock_release(&slave->catch_lock); + } + else if ((slave->binlog_pos > hdr->next_pos - hdr->event_size) + && strcmp(slave->binlogfile, router->binlog_name) == 0) + { + LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, + "Slave %d is ahead of expected position %s@%d. " + "Expected position %d", + slave->serverid, slave->binlogfile, + slave->binlog_pos, + hdr->next_pos - hdr->event_size))); + } + else if ((hdr->event_type != ROTATE_EVENT) + && (slave->binlog_pos != hdr->next_pos - hdr->event_size || + strcmp(slave->binlogfile, router->binlog_name) != 0)) + { + /* Check slave is in catchup mode and if not + * force it to go into catchup mode. + */ + if (slave->cstate & CS_UPTODATE) + { + spinlock_release(&router->lock); + LOGIF(LD, (skygw_log_write_flush(LOGFILE_DEBUG, + "Force slave %d into catchup mode %s@%d\n", + slave->serverid, slave->binlogfile, + slave->binlog_pos))); + spinlock_acquire(&slave->catch_lock); + slave->cstate &= ~(CS_UPTODATE|CS_DIST); + spinlock_release(&slave->catch_lock); + blr_slave_catchup(router, slave); + spinlock_acquire(&router->lock); + slave = router->slaves; + if (slave) + continue; + else + break; + } } } diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlog/blr_slave.c index a9afc2a50..30aebfe65 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlog/blr_slave.c @@ -107,6 +107,11 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) case COM_BINLOG_DUMP: return blr_slave_binlog_dump(router, slave, queue); break; + case COM_QUIT: + LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, + "COM_QUIT received from slave with server_id %d\n", + slave->serverid))); + break; default: LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, @@ -124,20 +129,23 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) * when MaxScale registered as a slave. The exception to the rule is the * request to obtain the current timestamp value of the server. * - * Three select statements are currently supported: + * Five select statements are currently supported: * SELECT UNIX_TIMESTAMP(); * SELECT @master_binlog_checksum * SELECT @@GLOBAL.GTID_MODE + * SELECT VERSION() + * SELECT 1 * * Two show commands are supported: * SHOW VARIABLES LIKE 'SERVER_ID' * SHOW VARIABLES LIKE 'SERVER_UUID' * - * Four set commands are supported: + * Five set commands are supported: * SET @master_binlog_checksum = @@global.binlog_checksum * SET @master_heartbeat_period=... * SET @slave_slave_uuid=... * SET NAMES latin1 + * SET NAMES utf8 * * @param router The router instance this defines the master for this replication chain * @param slave The slave specific data @@ -186,6 +194,16 @@ int query_len; free(query_text); return blr_slave_replay(router, slave, router->saved_master.gtid_mode); } + else if (strcasecmp(word, "1") == 0) + { + free(query_text); + return blr_slave_replay(router, slave, router->saved_master.select1); + } + else if (strcasecmp(word, "VERSION()") == 0) + { + free(query_text); + return blr_slave_replay(router, slave, router->saved_master.selectver); + } } else if (strcasecmp(word, "SHOW") == 0) { @@ -219,6 +237,11 @@ int query_len; } else if (strcasecmp(word, "@master_binlog_checksum") == 0) { + word = strtok_r(NULL, sep, &brkb); + if (strcasecmp(word, "'none'") == 0) + slave->nocrc = 1; + else + slave->nocrc = 0; free(query_text); return blr_slave_replay(router, slave, router->saved_master.chksum1); } @@ -235,6 +258,11 @@ int query_len; free(query_text); return blr_slave_replay(router, slave, router->saved_master.setnames); } + else if (strcasecmp(word, "utf8") == 0) + { + free(query_text); + return blr_slave_replay(router, slave, router->saved_master.utf8); + } } } free(query_text); @@ -473,34 +501,42 @@ uint32_t chksum; slave->state = BLRS_DUMPING; slave->seqno = 1; + if (slave->nocrc) + len = 0x2b; + else + len = 0x2f; + // Build a fake rotate event - resp = gwbuf_alloc(0x34); - hdr.payload_len = 0x30; + resp = gwbuf_alloc(len + 5); + hdr.payload_len = len + 1; hdr.seqno = slave->seqno++; hdr.ok = 0; hdr.timestamp = 0L; hdr.event_type = ROTATE_EVENT; hdr.serverid = router->masterid; - hdr.event_size = 0x2f; - hdr.next_pos = slave->binlog_pos; - hdr.flags = 0; + hdr.event_size = len; + hdr.next_pos = 0; + hdr.flags = 0x20; ptr = blr_build_header(resp, &hdr); encode_value(ptr, slave->binlog_pos, 64); ptr += 8; memcpy(ptr, slave->binlogfile, BINLOG_FNAMELEN); ptr += BINLOG_FNAMELEN; - /* - * Now add the CRC to the fake binlog rotate event. - * - * The algorithm is first to compute the checksum of an empty buffer - * and then the checksum of the event portion of the message, ie we do not - * include the length, sequence number and ok byte that makes up the first - * 5 bytes of the message. We also do not include the 4 byte checksum itself. - */ - chksum = crc32(0L, NULL, 0); - chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4); - encode_value(ptr, chksum, 32); + if (!slave->nocrc) + { + /* + * Now add the CRC to the fake binlog rotate event. + * + * The algorithm is first to compute the checksum of an empty buffer + * and then the checksum of the event portion of the message, ie we do not + * include the length, sequence number and ok byte that makes up the first + * 5 bytes of the message. We also do not include the 4 byte checksum itself. + */ + chksum = crc32(0L, NULL, 0); + chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4); + encode_value(ptr, chksum, 32); + } rval = slave->dcb->func.write(slave->dcb, resp); @@ -532,8 +568,16 @@ uint32_t chksum; slave->dcb->low_water = router->low_water; slave->dcb->high_water = router->high_water; dcb_add_callback(slave->dcb, DCB_REASON_LOW_WATER, blr_slave_callback, slave); + dcb_add_callback(slave->dcb, DCB_REASON_DRAINED, blr_slave_callback, slave); - rval = blr_slave_catchup(router, slave); + if (slave->binlog_pos != router->binlog_position || + strcmp(slave->binlogfile, router->binlog_name) != 0) + { + spinlock_acquire(&slave->catch_lock); + slave->cstate &= ~CS_UPTODATE; + spinlock_release(&slave->catch_lock); + rval = blr_slave_catchup(router, slave); + } return rval; } @@ -655,6 +699,7 @@ struct timespec req; spinlock_acquire(&slave->catch_lock); slave->cstate &= ~CS_EXPECTCB; spinlock_release(&slave->catch_lock); +doitagain: /* * We have a slightly complex syncronisation mechansim here, * we need to make sure that we do not have multiple threads @@ -670,9 +715,9 @@ struct timespec req; * in the outer loop and the CS_INNERLOOP, to say we are in * the inner loop. * - * If just CS_READING is set the thread other may be about to + * If just CS_READING is set the other thread may be about to * enter the inner loop or may be about to exit the function - * completely. therefore we have to wait to see if CS_READING + * completely. Therefore we have to wait to see if CS_READING * is cleared or CS_INNERLOOP is set. * * If CS_READING gets cleared then this thread should proceed @@ -687,24 +732,57 @@ struct timespec req; req.tv_sec = 0; req.tv_nsec = 1000; spinlock_acquire(&slave->catch_lock); - if (slave->cstate & CS_READING) + if (slave->cstate & CS_UPTODATE) { + LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, + "blr_slave_catchup called with up to date slave %d at " + "%s@%d. Reading position %s@%d\n", + slave->serverid, slave->binlogfile, + slave->binlog_pos, router->binlog_name, + router->binlog_position))); + slave->stats.n_alreadyupd++; + spinlock_release(&slave->catch_lock); + return 1; + } + while (slave->cstate & CS_READING) + { + // Wait until we know what the other thread is doing while ((slave->cstate & (CS_READING|CS_INNERLOOP)) == CS_READING) { spinlock_release(&slave->catch_lock); nanosleep(&req, NULL); spinlock_acquire(&slave->catch_lock); } - if (slave->cstate & CS_READING) + // Other thread is in the innerloop + if ((slave->cstate & (CS_READING|CS_INNERLOOP)) == (CS_READING|CS_INNERLOOP)) { spinlock_release(&slave->catch_lock); + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "blr_slave_catchup thread returning due to " + "lock being held by another thread. %s@%d\n", + slave->binlogfile, + slave->binlog_pos))); + slave->stats.n_catchupnr++; return 1; // We cheat here and return 1 because otherwise // an error would be sent and we do not want that } + + /* Release the lock for a short time to allow the other + * thread to exit the outer reading loop. + */ + spinlock_release(&slave->catch_lock); + nanosleep(&req, NULL); + spinlock_acquire(&slave->catch_lock); } + if (slave->pthread) + LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, "Multiple threads sending to same thread.\n"))); + slave->pthread = pthread_self(); slave->cstate |= CS_READING; spinlock_release(&slave->catch_lock); + if (DCB_ABOVE_HIGH_WATER(slave->dcb)) + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "blr_slave_catchup above high water on entry.\n"))); do { if ((fd = blr_open_binlog(router, slave->binlogfile)) == -1) @@ -725,6 +803,7 @@ struct timespec req; while ((!DCB_ABOVE_HIGH_WATER(slave->dcb)) && (record = blr_read_binlog(fd, slave->binlog_pos, &hdr)) != NULL) { +if (hdr.event_size > DEF_HIGH_WATER) slave->stats.n_above++; head = gwbuf_alloc(5); ptr = GWBUF_DATA(head); encode_value(ptr, hdr.event_size + 1, 24); @@ -754,15 +833,14 @@ struct timespec req; atomic_add(&slave->stats.n_events, 1); burst++; } + if (record == NULL) + slave->stats.n_failed_read++; spinlock_acquire(&slave->catch_lock); slave->cstate &= ~CS_INNERLOOP; spinlock_release(&slave->catch_lock); close(fd); } while (record && DCB_BELOW_LOW_WATER(slave->dcb)); - spinlock_acquire(&slave->catch_lock); - slave->cstate &= ~CS_READING; - spinlock_release(&slave->catch_lock); if (record) { atomic_add(&slave->stats.n_flows, 1); @@ -772,14 +850,39 @@ struct timespec req; } else { + int state_change = 0; spinlock_acquire(&slave->catch_lock); - slave->cstate |= CS_UPTODATE; + if ((slave->cstate & CS_UPTODATE) == 0) + { + atomic_add(&slave->stats.n_upd, 1); + slave->cstate |= CS_UPTODATE; + state_change = 1; + } spinlock_release(&slave->catch_lock); - LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, - "blr_slave_catchup slave is up to date %s, %u\n", + if (state_change) + LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, + "blr_slave_catchup slave is up to date %s, %u\n", slave->binlogfile, slave->binlog_pos))); } - return rval; + spinlock_acquire(&slave->catch_lock); +#if 0 +if (slave->pthread != pthread_self()) +{ + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Multple threads in catchup for same slave: %x and %x\n", slave->pthread, pthread_self()))); +abort(); +} +#endif + slave->pthread = 0; +#if 0 +if (DCB_BELOW_LOW_WATER(slave->dcb) && slave->binlog_pos != router->binlog_position) abort(); +#endif + slave->cstate &= ~CS_READING; + spinlock_release(&slave->catch_lock); +if (DCB_BELOW_LOW_WATER(slave->dcb) && slave->binlog_pos != router->binlog_position) +{ + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Expected to be above low water\n"))); +goto doitagain; +} } /** @@ -798,13 +901,27 @@ blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data) ROUTER_SLAVE *slave = (ROUTER_SLAVE *)data; ROUTER_INSTANCE *router = slave->router; - if (reason != DCB_REASON_LOW_WATER) - return 0; - - if (slave->state == BLRS_DUMPING) + if (reason == DCB_REASON_DRAINED) { - atomic_add(&slave->stats.n_events, 1); - blr_slave_catchup(router, slave); + if (slave->state == BLRS_DUMPING && + slave->binlog_pos != router->binlog_position) + { + atomic_add(&slave->stats.n_dcb, 1); + blr_slave_catchup(router, slave); + } + } + + if (reason == DCB_REASON_LOW_WATER) + { + if (slave->state == BLRS_DUMPING) + { + atomic_add(&slave->stats.n_cb, 1); + blr_slave_catchup(router, slave); + } + else + { + atomic_add(&slave->stats.n_cbna, 1); + } } return 0; } diff --git a/server/modules/routing/cli.c b/server/modules/routing/cli.c new file mode 100644 index 000000000..ac525f695 --- /dev/null +++ b/server/modules/routing/cli.c @@ -0,0 +1,297 @@ +/* + * 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 cli.c - A "routing module" that in fact merely gives access + * to a command line interface + * + * @verbatim + * Revision History + * + * Date Who Description + * 18/06/13 Mark Riddoch Initial implementation + * 13/06/14 Mark Riddoch Creted from the debugcli + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_BETA_RELEASE, + ROUTER_VERSION, + "The admin user interface" +}; + +extern int lm_enabled_logfiles_bitmask; + +static char *version_str = "V1.0.0"; + +/* The router entry points */ +static ROUTER *createInstance(SERVICE *service, char **options); +static void *newSession(ROUTER *instance, SESSION *session); +static void closeSession(ROUTER *instance, void *router_session); +static void freeSession(ROUTER *instance, void *router_session); +static int execute(ROUTER *instance, void *router_session, GWBUF *queue); +static void diagnostics(ROUTER *instance, DCB *dcb); +static uint8_t getCapabilities (ROUTER* inst, void* router_session); + +/** The module object definition */ +static ROUTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + execute, + diagnostics, + NULL, + NULL, + getCapabilities +}; + +extern int execute_cmd(CLI_SESSION *cli); + +static SPINLOCK instlock; +static CLI_INSTANCE *instances; + +/** + * 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 CLI router module %s.\n", + version_str))); + spinlock_init(&instlock); + instances = NULL; +} + +/** + * 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 Any array of options for the query router + * + * @return The instance data for this new instance + */ +static ROUTER * +createInstance(SERVICE *service, char **options) +{ +CLI_INSTANCE *inst; +int i; + + if ((inst = malloc(sizeof(CLI_INSTANCE))) == NULL) + return NULL; + + inst->service = service; + spinlock_init(&inst->lock); + inst->sessions = NULL; + inst->mode = CLIM_USER; + + if (options) + { + for (i = 0; options[i]; i++) + { + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Unknown option for CLI '%s'\n", + options[i]))); + } + } + } + + /* + * We have completed the creation of the instance data, so now + * insert this router instance into the linked list of routers + * that have been created with this module. + */ + spinlock_acquire(&instlock); + inst->next = instances; + instances = inst; + spinlock_release(&instlock); + + 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) +{ +CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; +CLI_SESSION *client; + + if ((client = (CLI_SESSION *)malloc(sizeof(CLI_SESSION))) == NULL) + { + return NULL; + } + client->session = session; + + memset(client->cmdbuf, 0, 80); + + spinlock_acquire(&inst->lock); + client->next = inst->sessions; + inst->sessions = client; + spinlock_release(&inst->lock); + + session->state = SESSION_STATE_READY; + client->mode = inst->mode; + + return (void *)client; +} + +/** + * 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 router_session The session being closed + */ +static void +closeSession(ROUTER *instance, void *router_session) +{ +CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; +CLI_SESSION *session = (CLI_SESSION *)router_session; + + + spinlock_acquire(&inst->lock); + if (inst->sessions == session) + inst->sessions = session->next; + else + { + CLI_SESSION *ptr = inst->sessions; + while (ptr && ptr->next != session) + ptr = ptr->next; + if (ptr) + ptr->next = session->next; + } + spinlock_release(&inst->lock); + /** + * Router session is freed in session.c:session_close, when session who + * owns it, is freed. + */ +} + +/** + * Free a debugcli session + * + * @param router_instance The router session + * @param router_client_session The router session as returned from newSession + */ +static void freeSession( + ROUTER* router_instance, + void* router_client_session) +{ + free(router_client_session); + return; +} + +/** + * We have data from the client, we must route it to the backend. + * This is simply a case of sending it to the connection that was + * chosen when we started the client session. + * + * @param instance The router instance + * @param router_session The router session returned from the newSession call + * @param queue The queue of data buffers to route + * @return The number of bytes sent + */ +static int +execute(ROUTER *instance, void *router_session, GWBUF *queue) +{ +CLI_SESSION *session = (CLI_SESSION *)router_session; + + /* Extract the characters */ + while (queue) + { + strncat(session->cmdbuf, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); + queue = gwbuf_consume(queue, GWBUF_LENGTH(queue)); + } + + execute_cmd(session); + return 1; +} + +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static void +diagnostics(ROUTER *instance, DCB *dcb) +{ + return; /* Nothing to do currently */ +} + +static uint8_t getCapabilities( + ROUTER* inst, + void* router_session) +{ + return 0; +} diff --git a/server/modules/routing/debugcli.c b/server/modules/routing/debugcli.c index bf2404c08..aa0e82ef8 100644 --- a/server/modules/routing/debugcli.c +++ b/server/modules/routing/debugcli.c @@ -47,7 +47,7 @@ MODULE_INFO info = { MODULE_API_ROUTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, ROUTER_VERSION, "The debug user interface" }; @@ -295,7 +295,7 @@ CLI_SESSION *session = (CLI_SESSION *)router_session; if (execute_cmd(session)) dcb_printf(session->session->client, "MaxScale> "); else - session->session->client->func.close(session->session->client); + dcb_close(session->session->client); } return 1; } diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 4bd19e957..a9a5da12a 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -128,6 +128,10 @@ struct subcommand showoptions[] = { "Show all currently loaded modules", "Show all currently loaded modules", {0, 0, 0} }, + { "monitor", 1, monitorShow, + "Show the monitor details", + "Show the monitor details", + {ARG_TYPE_MONITOR, 0, 0} }, { "monitors", 0, monitorShowAll, "Show the monitors that are configured", "Show the monitors that are configured", @@ -168,6 +172,10 @@ struct subcommand showoptions[] = { * The subcommands of the list command */ struct subcommand listoptions[] = { + { "clients", 0, dListClients, + "List all the client connections to MaxScale", + "List all the client connections to MaxScale", + {0, 0, 0} }, { "dcbs", 0, dListDCBs, "List all the DCBs active within MaxScale", "List all the DCBs active within MaxScale", @@ -181,8 +189,12 @@ struct subcommand listoptions[] = { "List all the listeners defined within MaxScale", {0, 0, 0} }, { "modules", 0, dprintAllModules, - "Show all currently loaded modules", - "Show all currently loaded modules", + "List all currently loaded modules", + "List all currently loaded modules", + {0, 0, 0} }, + { "monitors", 0, monitorList, + "List all monitors", + "List all monitors", {0, 0, 0} }, { "services", 0, dListServices, "List all the services defined within MaxScale", @@ -300,18 +312,30 @@ struct subcommand reloadoptions[] = { { "dbusers", 1, reload_dbusers, "Reload the dbuser data for a service. E.g. reload dbusers \"splitter service\"", "Reload the dbuser data for a service. E.g. reload dbusers 0x849420", - {ARG_TYPE_DBUSERS, 0, 0} }, + {ARG_TYPE_SERVICE, 0, 0} }, { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; static void enable_log_action(DCB *, char *); static void disable_log_action(DCB *, char *); +static void enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor); +static void disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor); +static void enable_service_root(DCB *dcb, SERVICE *service); +static void disable_service_root(DCB *dcb, SERVICE *service); /** * * The subcommands of the enable command * */ struct subcommand enableoptions[] = { + { + "heartbeat", + 1, + enable_monitor_replication_heartbeat, + "Enable the monitor replication heartbeat, pass a monitor name as argument", + "Enable the monitor replication heartbeat, pass a monitor name as argument", + {ARG_TYPE_MONITOR, 0, 0} + }, { "log", 1, @@ -322,6 +346,14 @@ struct subcommand enableoptions[] = { "message E.g. enable log message.", {ARG_TYPE_STRING, 0, 0} }, + { + "root", + 1, + enable_service_root, + "Enable root access to a service, pass a service name to enable root access", + "Enable root access to a service, pass a service name to enable root access", + {ARG_TYPE_SERVICE, 0, 0} + }, { NULL, 0, @@ -337,24 +369,40 @@ struct subcommand enableoptions[] = { * * The subcommands of the disable command * */ struct subcommand disableoptions[] = { - { - "log", - 1, - disable_log_action, - "Disable Log for MaxScale, Options: debug | trace | error | message " - "E.g. disable log debug", - "Disable Log for MaxScale, Options: debug | trace | error | message " - "E.g. disable log debug", - {ARG_TYPE_STRING, 0, 0} - }, - { + { + "heartbeat", + 1, + disable_monitor_replication_heartbeat, + "Disable the monitor replication heartbeat", + "Disable the monitor replication heartbeat", + {ARG_TYPE_MONITOR, 0, 0} + }, + { + "log", + 1, + disable_log_action, + "Disable Log for MaxScale, Options: debug | trace | error | message " + "E.g. disable log debug", + "Disable Log for MaxScale, Options: debug | trace | error | message " + "E.g. disable log debug", + {ARG_TYPE_STRING, 0, 0} + }, + { + "root", + 1, + disable_service_root, + "Disable root access to a service", + "Disable root access to a service", + {ARG_TYPE_SERVICE, 0, 0} + }, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} - } + } }; #if defined(SS_DEBUG) @@ -601,6 +649,8 @@ char *ptr, *lptr; if (args[0] == NULL || *args[0] == 0) return 1; + for (i = 0; args[i] && *args[i]; i++) + ; argc = i - 2; /* The number of extra arguments to commands */ @@ -848,7 +898,7 @@ unsigned int bitvalue; static void reload_dbusers(DCB *dcb, SERVICE *service) { - dcb_printf(dcb, "Loaded %d database users for server %s.\n", + dcb_printf(dcb, "Loaded %d database users for service %s.\n", reload_mysql_users(service), service->name); } @@ -956,6 +1006,55 @@ restart_monitor(DCB *dcb, MONITOR *monitor) monitorStart(monitor); } +/** + * Enable replication heartbeat for a monitor + * + * @param dcb Connection to user interface + * @param monitor The monitor + */ +static void +enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor) +{ + monitorSetReplicationHeartbeat(monitor, 1); +} + +/** + * Disable replication heartbeat for a monitor + * + * @param dcb Connection to user interface + * @param monitor The monitor + */ +static void +disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor) +{ + monitorSetReplicationHeartbeat(monitor, 0); +} + +/** + * Enable root access to a service + * + * @param dcb Connection to user interface + * @param service The service + */ +static void +enable_service_root(DCB *dcb, SERVICE *service) +{ + serviceEnableRootUser(service, 1); +} + +/** + * Disable root access to a service + * + * @param dcb Connection to user interface + * @param service The service + */ +static void +disable_service_root(DCB *dcb, SERVICE *service) +{ + serviceEnableRootUser(service, 0); +} + + /** * The log enable action */ diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index 0652f9f0c..0fc5fd4ce 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -65,6 +65,8 @@ * or take different actions such as open a new backend connection * 20/02/2014 Massimiliano Pinto If router_options=slave, route traffic to master if no slaves available * 06/03/2014 Massimiliano Pinto Server connection counter is now updated in closeSession + * 24/06/2014 Massimiliano Pinto New rules for selecting the Master server + * 27/06/2014 Mark Riddoch Addition of server weighting * * @endverbatim */ @@ -91,12 +93,12 @@ extern int lm_enabled_logfiles_bitmask; MODULE_INFO info = { MODULE_API_ROUTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, ROUTER_VERSION, "A connection based router to load balance based on connections" }; -static char *version_str = "V1.0.2"; +static char *version_str = "V1.1.0"; /* The router entry points */ static ROUTER *createInstance(SERVICE *service, char **options); @@ -110,12 +112,13 @@ static void clientReply( void *router_session, GWBUF *queue, DCB *backend_dcb); -static void errorReply( - ROUTER *instance, - void *router_session, - char *message, - DCB *backend_dcb, - int action); +static void handleError( + ROUTER *instance, + void *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + error_action_t action, + bool *succp); static uint8_t getCapabilities (ROUTER* inst, void* router_session); @@ -128,7 +131,7 @@ static ROUTER_OBJECT MyObject = { routeQuery, diagnostics, clientReply, - errorReply, + handleError, getCapabilities }; @@ -138,6 +141,9 @@ static bool rses_begin_locked_router_action( static void rses_end_locked_router_action( ROUTER_CLIENT_SES* rses); +static BACKEND *get_root_master( + BACKEND **servers); + static SPINLOCK instlock; static ROUTER_INSTANCE *instances; @@ -195,6 +201,8 @@ createInstance(SERVICE *service, char **options) ROUTER_INSTANCE *inst; SERVER *server; int i, n; +BACKEND *backend; +char *weightby; if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; @@ -230,10 +238,55 @@ int i, n; } inst->servers[n]->server = server; inst->servers[n]->current_connection_count = 0; + inst->servers[n]->weight = 1000; n++; } inst->servers[n] = NULL; + if ((weightby = serviceGetWeightingParameter(service)) != NULL) + { + int total = 0; + for (n = 0; inst->servers[n]; n++) + { + backend = inst->servers[n]; + total += atoi(serverGetParameter(backend->server, + weightby)); + } + if (total == 0) + { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "WARNING: Weighting Parameter for service '%s' " + "will be ignored as no servers have values " + "for the parameter '%s'.\n", + service->name, weightby))); + } + else + { + for (n = 0; inst->servers[n]; n++) + { + int perc; + backend = inst->servers[n]; + perc = (atoi(serverGetParameter(backend->server, + weightby)) * 1000) / total; + if (perc == 0) + perc = 1; + backend->weight = perc; + if (perc == 0) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Server '%s' has no value " + "for weighting parameter '%s', " + "no queries will be routed to " + "this server.\n", + server->unique_name, + weightby))); + } + + } + } + } + /* * Process the options */ @@ -261,11 +314,11 @@ int i, n; else { LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "* Warning : Unsupported router " - "option \'%s\' for readconnroute. " - "Expected router options are " - "[slave|master|synced]", + LOGFILE_MESSAGE, + "* Warning : Unsupported router " + "option \'%s\' for readconnroute. " + "Expected router options are " + "[slave|master|synced]", options[i]))); } } @@ -298,7 +351,7 @@ ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES *client_rses; BACKEND *candidate = NULL; int i; -int master_host = -1; +BACKEND *master_host = NULL; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, @@ -320,6 +373,11 @@ int master_host = -1; client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; #endif + /** + * Find the Master host from available servers + */ + master_host = get_root_master(inst->servers); + /** * Find a backend server to connect to. This is the extent of the * load balancing algorithm we need to implement for this simple @@ -355,36 +413,60 @@ int master_host = -1; if (SERVER_IN_MAINT(inst->servers[i]->server)) continue; - /* - * If router_options=slave, get the running master - * It will be used if there are no running slaves at all - */ - if (inst->bitvalue == SERVER_SLAVE) { - if (master_host < 0 && (SERVER_IS_MASTER(inst->servers[i]->server))) { - master_host = i; - } - } - + /* Check server status bits against bitvalue from router_options */ if (inst->servers[i] && - SERVER_IS_RUNNING(inst->servers[i]->server) && - (inst->servers[i]->server->status & inst->bitmask) == - inst->bitvalue) + SERVER_IS_RUNNING(inst->servers[i]->server) && + (inst->servers[i]->server->status & inst->bitmask & inst->bitvalue)) { + if (master_host) { + if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_SLAVE)) { + /* skip root Master here, as it could also be slave of an external server + * that is not in the configuration. + * Intermediate masters (Relay Servers) are also slave and will be selected + * as Slave(s) + */ + + continue; + } + if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_MASTER)) { + /* If option is "master" return only the root Master as there + * could be intermediate masters (Relay Servers) + * and they must not be selected. + */ + + candidate = master_host; + break; + } + } else { + /* master_host is NULL, no master server. + * If requested router_option is 'master' + * candidate wll be NULL. + */ + if (inst->bitvalue & SERVER_MASTER) { + candidate = NULL; + break; + } + } + /* If no candidate set, set first running server as our initial candidate server */ if (candidate == NULL) { candidate = inst->servers[i]; } - else if (inst->servers[i]->current_connection_count < - candidate->current_connection_count) + else if ((inst->servers[i]->current_connection_count + * 1000) / inst->servers[i]->weight < + (candidate->current_connection_count * + 1000) / candidate->weight) { /* This running server has fewer connections, set it as a new candidate */ candidate = inst->servers[i]; } - else if (inst->servers[i]->current_connection_count == - candidate->current_connection_count && + else if ((inst->servers[i]->current_connection_count + * 1000) / inst->servers[i]->weight == + (candidate->current_connection_count * + 1000) / candidate->weight && inst->servers[i]->server->stats.n_connections < candidate->server->stats.n_connections) { @@ -402,8 +484,8 @@ int master_host = -1; * Otherwise, just clean up and return NULL */ if (!candidate) { - if (master_host >= 0) { - candidate = inst->servers[master_host]; + if (master_host) { + candidate = master_host; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -551,7 +633,7 @@ DCB* backend_dcb; */ if (backend_dcb != NULL) { CHK_DCB(backend_dcb); - backend_dcb->func.close(backend_dcb); + dcb_close(backend_dcb); } } } @@ -648,6 +730,8 @@ diagnostics(ROUTER *router, DCB *dcb) ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router; ROUTER_CLIENT_SES *session; int i = 0; +BACKEND *backend; +char *weightby; spinlock_acquire(&router_inst->lock); session = router_inst->connections; @@ -663,6 +747,24 @@ int i = 0; dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", i); dcb_printf(dcb, "\tNumber of queries forwarded: %d\n", router_inst->stats.n_queries); + if ((weightby = serviceGetWeightingParameter(router_inst->service)) + != NULL) + { + dcb_printf(dcb, "\tConnection distribution based on %s " + "server parameter.\n", + weightby); + dcb_printf(dcb, + "\t\tServer Target %% Connections\n"); + for (i = 0; router_inst->servers[i]; i++) + { + backend = router_inst->servers[i]; + dcb_printf(dcb, "\t\t%-20s %3.1f%% %d\n", + backend->server->unique_name, + (float)backend->weight / 10, + backend->current_connection_count); + } + + } } /** @@ -688,14 +790,13 @@ clientReply( ss_dassert(client != NULL); - client->func.write(client, queue); + SESSION_ROUTE_REPLY(backend_dcb->session, queue); } /** - * Error Reply routine + * Error Handler routine * - * The routine will reply to client errors and/or closing the session - * or try to open a new backend connection. + * The routine will handle errors that occurred in backend writes. * * @param instance The router instance * @param router_session The router session @@ -705,12 +806,13 @@ clientReply( * */ static void -errorReply( - ROUTER *instance, - void *router_session, - char *message, - DCB *backend_dcb, - int action) +handleError( + ROUTER *instance, + void *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + error_action_t action, + bool *succp) { DCB *client = NULL; SESSION *session = backend_dcb->session; @@ -784,3 +886,34 @@ static uint8_t getCapabilities( { return 0; } + +/******************************** + * This routine returns the root master server from MySQL replication tree + * Get the root Master rule: + * + * find server with the lowest replication depth level + * and the SERVER_MASTER bitval + * Servers are checked even if they are in 'maintenance' + * + * @param servers The list of servers + * @return The Master found + * + */ + +static BACKEND *get_root_master(BACKEND **servers) { + int i = 0; + BACKEND *master_host = NULL; + + for (i = 0; servers[i]; i++) { + if (servers[i] && (servers[i]->server->status & (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) { + if (master_host && servers[i]->server->depth < master_host->server->depth) { + master_host = servers[i]; + } else { + if (master_host == NULL) { + master_host = servers[i]; + } + } + } + } + return master_host; +} diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile index 2d4aba7f0..c60f2ffd1 100644 --- a/server/modules/routing/readwritesplit/Makefile +++ b/server/modules/routing/readwritesplit/Makefile @@ -60,7 +60,7 @@ depend: cc -M $(CFLAGS) $(SRCS) > depend.mk install: $(MODULES) - install -D $(MODULES) $(DEST)/MaxScale/modules + install -D $(MODULES) $(DEST)/modules cleantests: $(MAKE) -C test cleantest diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 9fec7e1bd..218972ab4 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -31,13 +31,17 @@ #include #include #include +#include MODULE_INFO info = { MODULE_API_ROUTER, - MODULE_ALPHA_RELEASE, + MODULE_BETA_RELEASE, ROUTER_VERSION, "A Read/Write splitting router for enhancement read scalability" }; +#if defined(SS_DEBUG) +# include +#endif extern int lm_enabled_logfiles_bitmask; @@ -72,13 +76,41 @@ 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 void clientReply( ROUTER* instance, void* router_session, GWBUF* queue, DCB* backend_dcb); + +static void handleError( + ROUTER* instance, + void* router_session, + GWBUF* errmsgbuf, + DCB* backend_dcb, + error_action_t action, + bool* succp); + +static void print_error_packet(ROUTER_CLIENT_SES* rses, GWBUF* buf, DCB* dcb); +static int router_get_servercount(ROUTER_INSTANCE* router); +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 uint8_t getCapabilities (ROUTER* inst, void* router_session); +#if defined(NOT_USED) +static bool router_option_configured( + ROUTER_INSTANCE* router, + const char* optionstr, + void* data); +#endif + +#if defined(PREP_STMT_CACHING) +static prep_stmt_t* prep_stmt_init(prep_stmt_type_t type, void* id); +static void prep_stmt_done(prep_stmt_t* pstmt); +#endif /*< PREP_STMT_CACHING */ + int bref_cmp_global_conn( const void* bref1, const void* bref2); @@ -91,12 +123,21 @@ int bref_cmp_behind_master( const void* bref1, const void* bref2); +int bref_cmp_current_load( + const void* bref1, + const void* bref2); + +/** + * The order of functions _must_ match with the order the select criteria are + * listed in select_criteria_t definition in readwritesplit.h + */ int (*criteria_cmpfun[LAST_CRITERIA])(const void*, const void*)= { NULL, bref_cmp_global_conn, bref_cmp_router_conn, - bref_cmp_behind_master + bref_cmp_behind_master, + bref_cmp_current_load }; static bool select_connect_backend_servers( @@ -104,6 +145,7 @@ static bool select_connect_backend_servers( backend_ref_t* backend_ref, int router_nservers, int max_nslaves, + int max_rlag, select_criteria_t select_criteria, SESSION* session, ROUTER_INSTANCE* router); @@ -113,7 +155,7 @@ static bool get_dcb( ROUTER_CLIENT_SES* rses, backend_type_t btype); -static void rwsplit_process_options( +static void rwsplit_process_router_options( ROUTER_INSTANCE* router, char** options); @@ -127,7 +169,7 @@ static ROUTER_OBJECT MyObject = { routeQuery, diagnostic, clientReply, - NULL, + handleError, getCapabilities }; static bool rses_begin_locked_router_action( @@ -161,9 +203,15 @@ static void rses_property_done( static mysql_sescmd_t* rses_property_get_sescmd( rses_property_t* prop); +static bool execute_sescmd_history(backend_ref_t* bref); + static bool execute_sescmd_in_backend( backend_ref_t* backend_ref); +static void sescmd_cursor_reset(sescmd_cursor_t* scur); + +static bool sescmd_cursor_history_empty(sescmd_cursor_t* scur); + static void sescmd_cursor_set_active( sescmd_cursor_t* sescmd_cursor, bool value); @@ -180,10 +228,7 @@ static mysql_sescmd_t* sescmd_cursor_get_command( static bool sescmd_cursor_next( sescmd_cursor_t* scur); -static GWBUF* sescmd_cursor_process_replies( - DCB* client_dcb, - GWBUF* replybuf, - sescmd_cursor_t* scur); +static GWBUF* sescmd_cursor_process_replies(GWBUF* replybuf, backend_ref_t* bref); static void tracelog_routed_query( ROUTER_CLIENT_SES* rses, @@ -202,6 +247,28 @@ static void refreshInstance( ROUTER_INSTANCE* router, CONFIG_PARAMETER* param); +static void bref_clear_state(backend_ref_t* bref, bref_state_t state); +static void bref_set_state(backend_ref_t* bref, bref_state_t state); +static sescmd_cursor_t* backend_ref_get_sescmd_cursor (backend_ref_t* bref); + +static int router_handle_state_switch(DCB* dcb, DCB_REASON reason, void* data); +static bool handle_error_new_connection( + ROUTER_INSTANCE* inst, + ROUTER_CLIENT_SES* rses, + DCB* backend_dcb, + GWBUF* errmsg); +static bool handle_error_reply_client(SESSION* ses, GWBUF* errmsg); + +static BACKEND* get_root_master( + backend_ref_t* servers, + int router_nservers); + +static bool have_enough_servers( + ROUTER_CLIENT_SES** rses, + const int nsrv, + int router_nsrv, + ROUTER_INSTANCE* router); + static SPINLOCK instlock; static ROUTER_INSTANCE* instances; @@ -245,35 +312,107 @@ ROUTER_OBJECT* GetModuleObject() static void refreshInstance( ROUTER_INSTANCE* router, - CONFIG_PARAMETER* param) + CONFIG_PARAMETER* singleparam) { - config_param_type_t paramtype; + CONFIG_PARAMETER* param; + bool refresh_single; - paramtype = config_get_paramtype(param); - - if (paramtype == COUNT_TYPE) + if (singleparam != NULL) { - if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + param = singleparam; + refresh_single = true; + } + else + { + param = router->service->svc_config_param; + refresh_single = false; + } + + while (param != NULL) + { + config_param_type_t paramtype; + + paramtype = config_get_paramtype(param); + + if (paramtype == COUNT_TYPE) { - router->rwsplit_config.rw_max_slave_conn_percent = 0; - router->rwsplit_config.rw_max_slave_conn_count = - config_get_valint(param, NULL, paramtype); + if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + { + router->rwsplit_config.rw_max_slave_conn_percent = 0; + router->rwsplit_config.rw_max_slave_conn_count = + config_get_valint(param, NULL, paramtype); + } + else if (strncmp(param->name, + "max_slave_replication_lag", + MAX_PARAM_LEN) == 0) + { + router->rwsplit_config.rw_max_slave_replication_lag = + config_get_valint(param, NULL, paramtype); + } } - } - else if (paramtype == PERCENT_TYPE) - { - if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + else if (paramtype == PERCENT_TYPE) { - router->rwsplit_config.rw_max_slave_conn_count = 0; - router->rwsplit_config.rw_max_slave_conn_percent = - config_get_valint(param, NULL, paramtype); + if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) + { + router->rwsplit_config.rw_max_slave_conn_count = 0; + router->rwsplit_config.rw_max_slave_conn_percent = + config_get_valint(param, NULL, paramtype); + } + } + + if (refresh_single) + { + break; + } + param = param->next; + } + +#if defined(NOT_USED) /*< can't read monitor config parameters */ + if ((*router->servers)->backend_server->rlag == -2) + { + rlag_enabled = false; + } + else + { + rlag_enabled = true; + } + /** + * If replication lag detection is not enabled the measure can't be + * used in slave selection. + */ + if (!rlag_enabled) + { + if (rlag_limited) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning : Configuration Failed, max_slave_replication_lag " + "is set to %d,\n\t\t but detect_replication_lag " + "is not enabled. Replication lag will not be checked.", + router->rwsplit_config.rw_max_slave_replication_lag))); + } + + if (router->rwsplit_config.rw_slave_select_criteria == + LEAST_BEHIND_MASTER) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning : Configuration Failed, router option " + "\n\t\t slave_selection_criteria=LEAST_BEHIND_MASTER " + "is specified, but detect_replication_lag " + "is not enabled.\n\t\t " + "slave_selection_criteria=%s will be used instead.", + STRCRITERIA(DEFAULT_CRITERIA)))); + + router->rwsplit_config.rw_slave_select_criteria = + DEFAULT_CRITERIA; } } +#endif /*< NOT_USED */ } - /** - * Create an instance of read/write statemtn router within the MaxScale. + * Create an instance of read/write statement router within the MaxScale. * * * @param service The service this router is being create for @@ -281,15 +420,15 @@ static void refreshInstance( * * @return NULL in failure, pointer to router in success. */ -static ROUTER* createInstance( - SERVICE* service, - char** options) +static ROUTER * +createInstance(SERVICE *service, char **options) { ROUTER_INSTANCE* router; SERVER* server; int nservers; int i; CONFIG_PARAMETER* param; + char *weightby; if ((router = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { return NULL; @@ -335,6 +474,7 @@ static ROUTER* createInstance( router->servers[nservers]->backend_server = server; router->servers[nservers]->backend_conn_count = 0; router->servers[nservers]->be_valid = false; + router->servers[nservers]->weight = 1000; #if defined(SS_DEBUG) router->servers[nservers]->be_chk_top = CHK_NUM_BACKEND; router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND; @@ -343,6 +483,59 @@ static ROUTER* createInstance( server = server->nextdb; } router->servers[nservers] = NULL; + + /* + * If server weighting has been defined calculate the percentage + * of load that will be sent to each server. This is only used for + * calculating the least connections, either globally or within a + * service, or the numebr of current operations on a server. + */ + if ((weightby = serviceGetWeightingParameter(service)) != NULL) + { + int n, total = 0; + BACKEND *backend; + + for (n = 0; router->servers[n]; n++) + { + backend = router->servers[n]; + total += atoi(serverGetParameter( + backend->backend_server, weightby)); + } + if (total == 0) + { + LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, + "WARNING: Weighting Parameter for service '%s' " + "will be ignored as no servers have values " + "for the parameter '%s'.\n", + service->name, weightby))); + } + else + { + for (n = 0; router->servers[n]; n++) + { + int perc; + backend = router->servers[n]; + perc = (atoi(serverGetParameter( + backend->backend_server, + weightby)) * 1000) / total; + if (perc == 0) + perc = 1; + backend->weight = perc; + if (perc == 0) + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Server '%s' has no value " + "for weighting parameter '%s', " + "no queries will be routed to " + "this server.\n", + server->unique_name, + weightby))); + } + + } + } + } /** * vraa : is this necessary for readwritesplit ? @@ -355,9 +548,11 @@ static ROUTER* createInstance( */ router->bitmask = 0; router->bitvalue = 0; + + /** Call this before refreshInstance */ if (options) { - rwsplit_process_options(router, options); + rwsplit_process_router_options(router, options); } /** * Set default value for max_slave_connections and for slave selection @@ -370,18 +565,28 @@ static ROUTER* createInstance( { router->rwsplit_config.rw_slave_select_criteria = DEFAULT_CRITERIA; } - - /** + /** * Copy all config parameters from service to router instance. * Finally, copy version number to indicate that configs match. */ - param = config_get_param(service->svc_config_param, "max_slave_connections"); + param = config_get_param(service->svc_config_param, "max_slave_connections"); if (param != NULL) { refreshInstance(router, param); - router->rwsplit_version = service->svc_config_version; } + /** + * Read default value for slave replication lag upper limit and then + * configured value if it exists. + */ + router->rwsplit_config.rw_max_slave_replication_lag = CONFIG_MAX_SLAVE_RLAG; + param = config_get_param(service->svc_config_param, "max_slave_replication_lag"); + + if (param != NULL) + { + refreshInstance(router, param); + } + router->rwsplit_version = service->svc_config_version; /** * We have completed the creation of the router data, so now * insert this router into the linked list of routers @@ -410,17 +615,15 @@ static void* newSession( SESSION* session) { backend_ref_t* backend_ref; /*< array of backend references (DCB,BACKEND,cursor) */ - backend_ref_t* master_ref = NULL; /*< pointer to selected master */ - BACKEND** b; + backend_ref_t* master_ref = NULL; /*< pointer to selected master */ ROUTER_CLIENT_SES* client_rses = NULL; ROUTER_INSTANCE* router = (ROUTER_INSTANCE *)router_inst; bool succp; int router_nservers = 0; /*< # of servers in total */ int max_nslaves; /*< max # of slaves used in this session */ - int conf_max_nslaves; /*< value from configuration file */ + int max_slave_rlag; /*< max allowed replication lag for any slave */ int i; const int min_nservers = 1; /*< hard-coded for now */ - static uint64_t router_client_ses_seq; /*< ID for client session */ client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); @@ -438,23 +641,18 @@ static void* newSession( * router instance first. */ spinlock_acquire(&router->lock); + if (router->service->svc_config_version > router->rwsplit_version) { - CONFIG_PARAMETER* param = router->service->svc_config_param; - - while (param != NULL) - { - refreshInstance(router, param); - param = param->next; - } + /** re-read all parameters to rwsplit config structure */ + refreshInstance(router, NULL); /*< scan through all parameters */ + /** increment rwsplit router's config version number */ router->rwsplit_version = router->service->svc_config_version; /** Read options */ - rwsplit_process_options(router, router->service->routerOptions); + rwsplit_process_router_options(router, router->service->routerOptions); } /** Copy config struct from router instance */ client_rses->rses_config = router->rwsplit_config; - /** Create ID for the new client (router_client_ses) session */ - client_rses->rses_id = router_client_ses_seq += 1; spinlock_release(&router->lock); /** @@ -463,65 +661,19 @@ static void* newSession( client_rses->rses_autocommit_enabled = true; client_rses->rses_transaction_active = false; - /** count servers */ - b = router->servers; - while (*(b++) != NULL) router_nservers++; + router_nservers = router_get_servercount(router); - /** With too few servers session is not created */ - if (router_nservers < min_nservers || - MAX(client_rses->rses_config.rw_max_slave_conn_count, - (router_nservers*client_rses->rses_config.rw_max_slave_conn_percent)/100) - < min_nservers) + if (!have_enough_servers(&client_rses, + min_nservers, + router_nservers, + router)) { - if (router_nservers < min_nservers) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to start %s service. There are " - "too few backend servers available. Found %d " - "when %d is required.", - router->service->name, - router_nservers, - min_nservers))); - } - else - { - double pct = client_rses->rses_config.rw_max_slave_conn_percent/100; - double nservers = (double)router_nservers*pct; - - if (client_rses->rses_config.rw_max_slave_conn_count < - min_nservers) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to start %s service. There are " - "too few backend servers configured in " - "MaxScale.cnf. Found %d when %d is required.", - router->service->name, - client_rses->rses_config.rw_max_slave_conn_count, - min_nservers))); - } - if (nservers < min_nservers) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to start %s service. There are " - "too few backend servers configured in " - "MaxScale.cnf. Found %d%% when at least %.0f%% " - "would be required.", - router->service->name, - client_rses->rses_config.rw_max_slave_conn_percent, - min_nservers/(((double)router_nservers)/100)))); - } - } - free(client_rses); - client_rses = NULL; goto return_rses; } /** * Create backend reference objects for this session. */ - backend_ref = (backend_ref_t *)calloc (1, router_nservers*sizeof(backend_ref_t)); + backend_ref = (backend_ref_t *)calloc(1, router_nservers*sizeof(backend_ref_t)); if (backend_ref == NULL) { @@ -543,6 +695,7 @@ static void* newSession( backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; #endif + backend_ref[i].bref_state = 0; backend_ref[i].bref_backend = router->servers[i]; /** store pointers to sescmd list to both cursors */ backend_ref[i].bref_sescmd_cur.scmd_cur_rses = client_rses; @@ -550,36 +703,29 @@ static void* newSession( backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; - } - /** - * Find out the number of read backend servers. - * Depending on the configuration value type, either copy direct count - * of slave connections or calculate the count from percentage value. - */ - if (client_rses->rses_config.rw_max_slave_conn_count > 0) - { - conf_max_nslaves = client_rses->rses_config.rw_max_slave_conn_count; - } - else - { - conf_max_nslaves = - (router_nservers*client_rses->rses_config.rw_max_slave_conn_percent)/100; - } - max_nslaves = MIN(router_nservers-1, MAX(1, conf_max_nslaves)); - + } + max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); + max_slave_rlag = rses_get_max_replication_lag(client_rses); + spinlock_init(&client_rses->rses_lock); client_rses->rses_backend_ref = backend_ref; /** * Find a backend servers to connect to. + * This command requires that rsession's lock is held. */ + rses_begin_locked_router_action(client_rses); + succp = select_connect_backend_servers(&master_ref, backend_ref, router_nservers, max_nslaves, + max_slave_rlag, client_rses->rses_config.rw_slave_select_criteria, session, router); + + rses_end_locked_router_action(client_rses); /** Both Master and at least 1 slave must be found */ if (!succp) { @@ -590,11 +736,13 @@ static void* newSession( } /** Copy backend pointers to router session. */ client_rses->rses_master_ref = master_ref; + /* assert with master_host */ + ss_dassert(master_ref && (master_ref->bref_backend->backend_server && SERVER_MASTER)); + client_rses->rses_capabilities = RCAP_TYPE_STMT_INPUT; client_rses->rses_backend_ref = backend_ref; client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ - client_rses->rses_capabilities = RCAP_TYPE_STMT_INPUT; router->stats.n_sessions += 1; - + /** * Version is bigger than zero once initialized. */ @@ -633,7 +781,15 @@ static void closeSession( { ROUTER_CLIENT_SES* router_cli_ses; backend_ref_t* backend_ref; - + + /** + * router session can be NULL if newSession failed and it is discarding + * its connections and DCB's. + */ + if (router_session == NULL) + { + return; + } router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); @@ -664,17 +820,27 @@ static void closeSession( for (i=0; irses_nbackends; i++) { - DCB* dcb = backend_ref[i].bref_dcb; + backend_ref_t* bref = &backend_ref[i]; + DCB* dcb = bref->bref_dcb; /** Close those which had been connected */ - if (dcb != NULL) + if (BREF_IS_IN_USE(bref)) { CHK_DCB(dcb); - backend_ref[i].bref_dcb = NULL; /*< prevent new uses of DCB */ - dcb->func.close(dcb); + /** Clean operation counter in bref and in SERVER */ + while (BREF_IS_WAITING_RESULT(bref)) + { + bref_clear_state(bref, BREF_WAITING_RESULT); + } + bref_clear_state(bref, BREF_IN_USE); + bref_set_state(bref, BREF_CLOSED); + /** + * closes protocol and dcb + */ + dcb_close(dcb); /** decrease server current connection counters */ - atomic_add(&backend_ref[i].bref_backend->backend_server->stats.n_current, -1); - atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); + atomic_add(&bref->bref_backend->backend_server->stats.n_current, -1); + atomic_add(&bref->bref_backend->backend_conn_count, -1); } } /** Unlock */ @@ -697,12 +863,10 @@ static void freeSession( for (i=0; irses_nbackends; i++) { - if (backend_ref[i].bref_dcb == NULL) + if (!BREF_IS_IN_USE((&backend_ref[i]))) { continue; } - ss_dassert(backend_ref[i].bref_backend->backend_conn_count > 0); - atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); } spinlock_acquire(&router->lock); @@ -747,7 +911,10 @@ static void freeSession( return; } - +/** + * Provide a pointer to a suitable backend dcb. + * Detect failures in server statuses and reselect backends if necessary. + */ static bool get_dcb( DCB** p_dcb, ROUTER_CLIENT_SES* rses, @@ -757,6 +924,7 @@ static bool get_dcb( int smallest_nconn = -1; int i; bool succp = false; + BACKEND *master_host = NULL; CHK_CLIENT_RSES(rses); ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); @@ -767,35 +935,42 @@ static bool get_dcb( } backend_ref = rses->rses_backend_ref; + /* get root master from availbal servers */ + master_host = get_root_master(backend_ref, rses->rses_nbackends); + if (btype == BE_SLAVE) { for (i=0; irses_nbackends; i++) { BACKEND* b = backend_ref[i].bref_backend; - - if (backend_ref[i].bref_dcb != NULL && - SERVER_IS_SLAVE(b->backend_server) && + /* 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)) { *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); } } if (!succp) { backend_ref = rses->rses_master_ref; - - if (backend_ref->bref_dcb != NULL) + + if (BREF_IS_IN_USE(backend_ref)) { *p_dcb = backend_ref->bref_dcb; succp = true; + + ss_dassert(backend_ref->bref_dcb->state != DCB_STATE_ZOMBIE); ss_dassert( - SERVER_IS_MASTER(backend_ref->bref_backend->backend_server) && - smallest_nconn == -1); + (master_host && (backend_ref->bref_backend->backend_server == master_host->backend_server)) && + smallest_nconn == -1); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -813,9 +988,9 @@ static bool get_dcb( for (i=0; irses_nbackends; i++) { BACKEND* b = backend_ref[i].bref_backend; - - if (backend_ref[i].bref_dcb != NULL && - (SERVER_IS_MASTER(b->backend_server))) + + if (BREF_IS_IN_USE((&backend_ref[i])) && + (master_host && (b->backend_server == master_host->backend_server))) { *p_dcb = backend_ref[i].bref_dcb; succp = true; @@ -842,7 +1017,13 @@ return_succp: * @param session The session associated with the client * @param queue Gateway buffer queue with the packets received * - * @return The number of queries forwarded + * @return if succeed 1, otherwise 0 + * If routeQuery fails, it means that router session has failed. + * In any tolerated failure, handleError is called and if necessary, + * an error message is sent to the client. + * + * For now, routeQuery don't tolerate errors, so any error will close + * the session. vraa 14.6.14 */ static int routeQuery( ROUTER* instance, @@ -853,7 +1034,7 @@ static int routeQuery( GWBUF* plainsqlbuf = NULL; char* querystr = NULL; char* startpos; - unsigned char packet_type; + mysql_server_cmd_t packet_type; uint8_t* packet; int ret = 0; DCB* master_dcb = NULL; @@ -862,6 +1043,7 @@ static int routeQuery( ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; bool rses_is_closed = false; size_t len; + MYSQL* mysql = NULL; CHK_CLIENT_RSES(router_cli_ses); @@ -870,22 +1052,32 @@ static int routeQuery( { rses_is_closed = true; } + + ss_dassert(!GWBUF_IS_TYPE_UNDEFINED(querybuf)); + packet = GWBUF_DATA(querybuf); packet_type = packet[4]; if (rses_is_closed) { - LOGIF(LE, - (skygw_log_write_flush( - LOGFILE_ERROR, - "Error: Failed to route %s:%s:\"%s\" to " - "backend server. %s.", - STRPACKETTYPE(packet_type), - STRQTYPE(qtype), - (querystr == NULL ? "(empty)" : querystr), - (rses_is_closed ? "Router was closed" : - "Router has no backend servers where to " - "route to")))); + /** + * MYSQL_COM_QUIT may have sent by client and as a part of backend + * closing procedure. + */ + if (packet_type != MYSQL_COM_QUIT) + { + LOGIF(LE, + (skygw_log_write_flush( + LOGFILE_ERROR, + "Error: Failed to route %s:%s:\"%s\" to " + "backend server. %s.", + STRPACKETTYPE(packet_type), + STRQTYPE(qtype), + (querystr == NULL ? "(empty)" : querystr), + (rses_is_closed ? "Router was closed" : + "Router has no backend servers where to " + "route to")))); + } goto return_ret; } inst->stats.n_queries++; @@ -895,21 +1087,24 @@ static int routeQuery( CHK_DCB(master_dcb); switch(packet_type) { - case COM_QUIT: /**< 1 QUIT will close all sessions */ - case COM_INIT_DB: /**< 2 DDL must go to the master */ - case COM_REFRESH: /**< 7 - I guess this is session but not sure */ - case COM_DEBUG: /**< 0d all servers dump debug info to stdout */ - case COM_PING: /**< 0e all servers are pinged */ - case COM_CHANGE_USER: /**< 11 all servers change it accordingly */ + case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ + case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ + case MYSQL_COM_REFRESH: /*< 7 - I guess this is session but not sure */ + case MYSQL_COM_DEBUG: /*< 0d all servers dump debug info to stdout */ + case MYSQL_COM_PING: /*< 0e all servers are pinged */ + case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ + case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ + case MYSQL_COM_STMT_SEND_LONG_DATA: /*< send data to column */ + case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ qtype = QUERY_TYPE_SESSION_WRITE; break; - case COM_CREATE_DB: /**< 5 DDL must go to the master */ - case COM_DROP_DB: /**< 6 DDL must go to the master */ + case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ + case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ qtype = QUERY_TYPE_WRITE; break; - case COM_QUERY: + case MYSQL_COM_QUERY: plainsqlbuf = gwbuf_clone_transform(querybuf, GWBUF_TYPE_PLAINSQL); len = GWBUF_LENGTH(plainsqlbuf); @@ -917,44 +1112,52 @@ static int routeQuery( querystr = (char *)malloc(len+1); memcpy(querystr, startpos, len); memset(&querystr[len], 0, 1); - // querystr = (char *)GWBUF_DATA(plainsqlbuf); - /* - * querystr = master_dcb->func.getquerystr( - * (void *) gwbuf_clone(querybuf), - * &querystr_is_copy); - */ - - qtype = skygw_query_classifier_get_type(querystr, 0); + /** + * Use mysql handle to query information from parse tree. + * call skygw_query_classifier_free before exit! + */ + qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); break; - case COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ - case COM_STATISTICS: /**< 9 ? */ - case COM_PROCESS_INFO: /**< 0a ? */ - case COM_CONNECT: /**< 0b ? */ - case COM_PROCESS_KILL: /**< 0c ? */ - case COM_TIME: /**< 0f should this be run in gateway ? */ - case COM_DELAYED_INSERT: /**< 10 ? */ - case COM_DAEMON: /**< 1d ? */ + case MYSQL_COM_STMT_PREPARE: + plainsqlbuf = gwbuf_clone_transform(querybuf, + GWBUF_TYPE_PLAINSQL); + len = GWBUF_LENGTH(plainsqlbuf); + /** unnecessary if buffer includes additional terminating null */ + querystr = (char *)malloc(len+1); + memcpy(querystr, startpos, len); + memset(&querystr[len], 0, 1); + qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); + qtype |= QUERY_TYPE_PREPARE_STMT; + break; + + case MYSQL_COM_STMT_EXECUTE: + /** Parsing is not needed for this type of packet */ +#if defined(NOT_USED) + plainsqlbuf = gwbuf_clone_transform(querybuf, + GWBUF_TYPE_PLAINSQL); + len = GWBUF_LENGTH(plainsqlbuf); + /** unnecessary if buffer includes additional terminating null */ + querystr = (char *)malloc(len+1); + memcpy(querystr, startpos, len); + memset(&querystr[len], 0, 1); + qtype = skygw_query_classifier_get_type(querystr, 0, &mysql); +#endif + qtype = QUERY_TYPE_EXEC_STMT; + break; + + case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ + case MYSQL_COM_STATISTICS: /**< 9 ? */ + case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ + case MYSQL_COM_CONNECT: /**< 0b ? */ + case MYSQL_COM_PROCESS_KILL: /**< 0c ? */ + case MYSQL_COM_TIME: /**< 0f should this be run in gateway ? */ + case MYSQL_COM_DELAYED_INSERT: /**< 10 ? */ + case MYSQL_COM_DAEMON: /**< 1d ? */ default: break; } /**< switch by packet type */ -#if 0 - LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, - "String\t\"%s\"", - querystr == NULL ? "(empty)" : querystr))); - LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, - "Packet type\t%s", - STRPACKETTYPE(packet_type)))); -#endif -#if defined(AUTOCOMMIT_OPT) - if ((QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) && - !router_cli_ses->rses_autocommit_enabled) || - (QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) && - router_cli_ses->rses_autocommit_enabled)) - { - /** reply directly to client */ - } -#endif + /** * If autocommit is disabled or transaction is explicitly started * transaction becomes active and master gets all statements until @@ -994,8 +1197,14 @@ static int routeQuery( /** * Session update is always routed in the same way. */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE)) + 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)) { + /** + * It is not sure if the session command in question requires + * response. Statement is examined in route_session_write. + */ bool succp = route_session_write( router_cli_ses, querybuf, @@ -1007,8 +1216,6 @@ static int routeQuery( { ret = 1; } - ss_dassert(succp); - ss_dassert(ret == 1); goto return_ret; } else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && @@ -1018,9 +1225,8 @@ static int routeQuery( LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, - "[%s.%d]\tRead-only query, routing to Slave.", - inst->service->name, - router_cli_ses->rses_id))); + "[%s]\tRead-only query, routing to Slave.", + inst->service->name))); ss_dassert(QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)); /** Lock router session */ @@ -1028,14 +1234,21 @@ static int routeQuery( { goto return_ret; } - succp = get_dcb(&slave_dcb, router_cli_ses, BE_SLAVE); if (succp) { if ((ret = slave_dcb->func.write(slave_dcb, querybuf)) == 1) { + 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); + bref_set_state(bref, BREF_QUERY_ACTIVE); + bref_set_state(bref, BREF_WAITING_RESULT); } else { @@ -1053,7 +1266,7 @@ static int routeQuery( else { bool succp = true; - + if (LOG_IS_ENABLED(LOGFILE_TRACE)) { if (router_cli_ses->rses_transaction_active) /*< all to master */ @@ -1080,12 +1293,21 @@ static int routeQuery( { succp = get_dcb(&master_dcb, router_cli_ses, BE_MASTER); } + if (succp) { - if ((ret = master_dcb->func.write(master_dcb, 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); @@ -1108,6 +1330,10 @@ return_ret: { free(querystr); } + if (mysql != NULL) + { + skygw_query_classifier_free(mysql); + } return ret; } @@ -1136,6 +1362,7 @@ static bool rses_begin_locked_router_action( CHK_CLIENT_RSES(rses); if (rses->rses_closed) { + goto return_succp; } spinlock_acquire(&rses->rses_lock); @@ -1146,10 +1373,6 @@ static bool rses_begin_locked_router_action( succp = true; return_succp: - if (!succp) - { - /** log that router session was closed */ - } return succp; } @@ -1186,9 +1409,11 @@ static void rses_end_locked_router_action( static void diagnostic(ROUTER *instance, DCB *dcb) { - ROUTER_CLIENT_SES *router_cli_ses; - ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; - int i = 0; +ROUTER_CLIENT_SES *router_cli_ses; +ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; +int i = 0; +BACKEND *backend; +char *weightby; spinlock_acquire(&router->lock); router_cli_ses = router->connections; @@ -1217,6 +1442,30 @@ diagnostic(ROUTER *instance, DCB *dcb) dcb_printf(dcb, "\tNumber of queries forwarded to all: %d\n", router->stats.n_all); + if ((weightby = serviceGetWeightingParameter(router->service)) != NULL) + { + dcb_printf(dcb, + "\tConnection distribution based on %s " + "server parameter.\n", weightby); + dcb_printf(dcb, + "\t\tServer Target %% Connections " + "Operations\n"); + dcb_printf(dcb, + "\t\t Global Router\n"); + for (i = 0; router->servers[i]; i++) + { + backend = router->servers[i]; + dcb_printf(dcb, + "\t\t%-20s %3.1f%% %-6d %-6d %d\n", + backend->backend_server->unique_name, + (float)backend->weight / 10, + backend->backend_server->stats.n_current, + backend->backend_conn_count, + backend->backend_server->stats.n_current_ops); + } + + } + } /** @@ -1229,7 +1478,7 @@ diagnostic(ROUTER *instance, DCB *dcb) * @param backend_dcb The backend DCB * @param queue The GWBUF with reply data */ -static void clientReply( +static void clientReply ( ROUTER* instance, void* router_session, GWBUF* writebuf, @@ -1238,8 +1487,7 @@ static void clientReply( DCB* client_dcb; ROUTER_CLIENT_SES* router_cli_ses; sescmd_cursor_t* scur = NULL; - backend_ref_t* backend_ref; - int i; + backend_ref_t* bref; router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); @@ -1250,9 +1498,7 @@ static void clientReply( */ if (!rses_begin_locked_router_action(router_cli_ses)) { - while ((writebuf = gwbuf_consume( - writebuf, - GWBUF_LENGTH(writebuf))) != NULL); + print_error_packet(router_cli_ses, writebuf, backend_dcb); goto lock_failed; } /** Holding lock ensures that router session remains open */ @@ -1284,56 +1530,106 @@ static void clientReply( /** Log to debug that router was closed */ goto lock_failed; } - backend_ref = router_cli_ses->rses_backend_ref; - - /** find backend_dcb's corresponding BACKEND */ - i = 0; - while (irses_nbackends && - backend_ref[i].bref_dcb != backend_dcb) - { - i++; - } - ss_dassert(backend_ref[i].bref_dcb == backend_dcb); + bref = get_bref_from_dcb(router_cli_ses, backend_dcb); - LOGIF(LT, tracelog_routed_query(router_cli_ses, - "reply_by_statement", - &backend_ref[i], - gwbuf_clone(writebuf))); - - scur = &backend_ref[i].bref_sescmd_cur; - /** + CHK_BACKEND_REF(bref); + scur = &bref->bref_sescmd_cur; + /** * Active cursor means that reply is from session command - * execution. Majority of the time there are no session commands - * being executed. + * execution. */ if (sescmd_cursor_is_active(scur)) { - writebuf = sescmd_cursor_process_replies(client_dcb, - writebuf, - scur); + 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); + char* cmdstr = (char *)malloc(len+1); + + snprintf(cmdstr, len+1, "%s", &buf[5]); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to execute %s in %s:%d.", + cmdstr, + bref->bref_backend->backend_server->name, + bref->bref_backend->backend_server->port))); + + free(cmdstr); + } + + if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf)) + { + /** + * Discard all those responses that have already been sent to + * the client. Return with buffer including response that + * needs to be sent to client or NULL. + */ + writebuf = sescmd_cursor_process_replies(writebuf, bref); + } + /** + * If response will be sent to client, decrease waiter count. + * This applies to session commands only. Counter decrement + * for other type of queries is done outside this block. + */ + if (writebuf != NULL && client_dcb != NULL) + { + /** Set response status as replied */ + bref_clear_state(bref, BREF_WAITING_RESULT); + } } - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - + /** + * Clear BREF_QUERY_ACTIVE flag and decrease waiter counter. + * This applies for queries other than session commands. + */ + else if (BREF_IS_QUERY_ACTIVE(bref)) + { + bref_clear_state(bref, BREF_QUERY_ACTIVE); + /** Set response status as replied */ + bref_clear_state(bref, BREF_WAITING_RESULT); + } + if (writebuf != NULL && client_dcb != NULL) { /** Write reply to client DCB */ - client_dcb->func.write(client_dcb, writebuf); - - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [clientReply:rwsplit] client dcb %p, " - "backend dcb %p. End of normal reply.", - pthread_self(), - client_dcb, - backend_dcb))); + SESSION_ROUTE_REPLY(backend_dcb->session, writebuf); } + /** Unlock router session */ + rses_end_locked_router_action(router_cli_ses); + + /** Lock router session */ + if (!rses_begin_locked_router_action(router_cli_ses)) + { + /** Log to debug that router was closed */ + goto lock_failed; + } + /** There is one pending session command to be executed. */ + if (sescmd_cursor_is_active(scur)) + { + bool succp; + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Backend %s:%d processed reply and starts to execute " + "active cursor.", + bref->bref_backend->backend_server->name, + bref->bref_backend->backend_server->port))); + + succp = execute_sescmd_in_backend(bref); + + ss_dassert(succp); + } + /** Unlock router session */ + rses_end_locked_router_action(router_cli_ses); lock_failed: return; } - +/** Compare nunmber of connections from this router in backend servers */ int bref_cmp_router_conn( const void* bref1, const void* bref2) @@ -1341,10 +1637,11 @@ int bref_cmp_router_conn( BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((b1->backend_conn_count < b2->backend_conn_count) ? -1 : - ((b1->backend_conn_count > b2->backend_conn_count) ? 1 : 0)); + return ((1000 * b1->backend_conn_count) / b1->weight) + - ((1000 * b2->backend_conn_count) / b2->weight); } +/** Compare nunmber of global connections in backend servers */ int bref_cmp_global_conn( const void* bref1, const void* bref2) @@ -1352,16 +1649,88 @@ int bref_cmp_global_conn( BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; - return ((b1->backend_server->stats.n_current < b2->backend_server->stats.n_current) ? -1 : - ((b1->backend_server->stats.n_current > b2->backend_server->stats.n_current) ? 1 : 0)); + return ((1000 * b1->backend_server->stats.n_current) / b1->weight) + - ((1000 * b2->backend_server->stats.n_current) / b2->weight); } +/** Compare relication lag between backend servers */ int bref_cmp_behind_master( const void* bref1, const void* bref2) { - return 1; + BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; + BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + + return ((b1->backend_server->rlag < b2->backend_server->rlag) ? -1 : + ((b1->backend_server->rlag > b2->backend_server->rlag) ? 1 : 0)); +} + +/** Compare nunmber of current operations in backend servers */ +int bref_cmp_current_load( + const void* bref1, + const void* bref2) +{ + SERVER* s1 = ((backend_ref_t *)bref1)->bref_backend->backend_server; + SERVER* s2 = ((backend_ref_t *)bref2)->bref_backend->backend_server; + BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; + BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; + + return ((1000 * s1->stats.n_current_ops) - b1->weight) + - ((1000 * s2->stats.n_current_ops) - b2->weight); +} + +static void bref_clear_state( + backend_ref_t* bref, + bref_state_t state) +{ + if (state != BREF_WAITING_RESULT) + { + bref->bref_state &= ~state; + } + else + { + int prev1; + int prev2; + + /** Decrease waiter count */ + prev1 = atomic_add(&bref->bref_num_result_wait, -1); + + if (prev1 <= 0) { + atomic_add(&bref->bref_num_result_wait, 1); + } + else + { + /** Decrease global operation count */ + prev2 = atomic_add( + &bref->bref_backend->backend_server->stats.n_current_ops, -1); + ss_dassert(prev2 > 0); + } + } +} + +static void bref_set_state( + backend_ref_t* bref, + bref_state_t state) +{ + if (state != BREF_WAITING_RESULT) + { + bref->bref_state |= state; + } + else + { + int prev1; + int prev2; + + /** Increase waiter count */ + prev1 = atomic_add(&bref->bref_num_result_wait, 1); + ss_dassert(prev1 >= 0); + + /** Increase global operation count */ + prev2 = atomic_add( + &bref->bref_backend->backend_server->stats.n_current_ops, 1); + ss_dassert(prev2 >= 0); + } } /** @@ -1382,6 +1751,9 @@ int bref_cmp_behind_master( * @param max_nslaves - in, use * Upper limit for the number of slaves. Configuration parameter or default. * + * @param max_slave_rlag - in, use + * Maximum allowed replication lag for any slave. Configuration parameter or default. + * * @param session - in, use * MaxScale session pointer used when connection to backend is established. * @@ -1393,25 +1765,29 @@ int bref_cmp_behind_master( * * @details It is assumed that there is only one master among servers of * a router instance. As a result, the first master found is chosen. + * There will possibly be more backend references than connected backends + * because only those in correct state are connected to. */ static bool select_connect_backend_servers( backend_ref_t** p_master_ref, backend_ref_t* backend_ref, int router_nservers, int max_nslaves, + int max_slave_rlag, select_criteria_t select_criteria, SESSION* session, ROUTER_INSTANCE* router) -{ +{ bool succp = true; - bool master_found = false; - bool master_connected = false; + bool master_found; + bool master_connected; int slaves_found = 0; int slaves_connected = 0; int i; const int min_nslaves = 0; /*< not configurable at the time */ bool is_synced_master; int (*p)(const void *, const void *); + BACKEND *master_host = NULL; if (p_master_ref == NULL || backend_ref == NULL) { @@ -1419,6 +1795,40 @@ static bool select_connect_backend_servers( succp = false; goto return_succp; } + + /* get the root Master */ + master_host = get_root_master(backend_ref, router_nservers); + + /** Master is already chosen and connected. This is slave failure case */ + if (*p_master_ref != NULL && + BREF_IS_IN_USE((*p_master_ref))) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [select_connect_backend_servers] Master %p fd %d found.", + pthread_self(), + (*p_master_ref)->bref_dcb, + (*p_master_ref)->bref_dcb->fd))); + + master_found = true; + master_connected = true; + /* assert with master_host */ + ss_dassert(master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && SERVER_MASTER); + } + /** New session or master failure case */ + else + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [select_connect_backend_servers] Session %p doesn't " + "currently have a master chosen. Proceeding to master " + "selection.", + pthread_self(), + session))); + + master_found = false; + master_connected = false; + } /** Check slave selection criteria and set compare function */ p = criteria_cmpfun[select_criteria]; @@ -1431,36 +1841,45 @@ static bool select_connect_backend_servers( if (router->bitvalue != 0) /*< 'synced' is the only bitvalue in rwsplit */ { is_synced_master = true; - } + } else { is_synced_master = false; - } - -#if 0 + } + +#if defined(EXTRA_SS_DEBUG) LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Servers and conns before ordering:"))); + for (i=0; ibackend_server->name, b->backend_server->port, b->backend_conn_count))); } #endif + /* assert with master_host */ + ss_dassert(!master_connected || + (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 * beginning of the list. */ - qsort((void *)backend_ref, (size_t)router_nservers, sizeof(backend_ref_t), p); + qsort(backend_ref, (size_t)router_nservers, sizeof(backend_ref_t), p); if (LOG_IS_ENABLED(LOGFILE_TRACE)) { if (select_criteria == LEAST_GLOBAL_CONNECTIONS || - select_criteria == LEAST_ROUTER_CONNECTIONS) + select_criteria == LEAST_ROUTER_CONNECTIONS || + select_criteria == LEAST_BEHIND_MASTER || + select_criteria == LEAST_CURRENT_OPERATIONS) { LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Servers and %s connection counts:", @@ -1474,7 +1893,7 @@ static bool select_connect_backend_servers( switch(select_criteria) { case LEAST_GLOBAL_CONNECTIONS: LOGIF(LT, (skygw_log_write_flush(LOGFILE_TRACE, - "%s %d:%d", + "%s:%d MaxScale connections : %d", b->backend_server->name, b->backend_server->port, b->backend_server->stats.n_current))); @@ -1482,20 +1901,35 @@ static bool select_connect_backend_servers( case LEAST_ROUTER_CONNECTIONS: LOGIF(LT, (skygw_log_write_flush(LOGFILE_TRACE, - "%s %d:%d", + "%s:%d RWSplit connections : %d", b->backend_server->name, b->backend_server->port, b->backend_conn_count))); break; + case LEAST_CURRENT_OPERATIONS: + LOGIF(LT, (skygw_log_write_flush(LOGFILE_TRACE, + "%s:%d current operations : %d", + b->backend_server->name, + b->backend_server->port, + b->backend_server->stats.n_current_ops))); + break; + + case LEAST_BEHIND_MASTER: + LOGIF(LT, (skygw_log_write_flush(LOGFILE_TRACE, + "%s:%d replication lag : %d", + b->backend_server->name, + b->backend_server->port, + b->backend_server->rlag))); default: break; } } } - } + } /*< log only */ + /** - * Choose at least 1+1 (master and slave) and at most 1+max_nslaves + * Choose at least 1+min_nslaves (master and slave) and at most 1+max_nslaves * servers from the sorted list. First master found is selected. */ for (i=0; @@ -1503,58 +1937,85 @@ static bool select_connect_backend_servers( i++) { BACKEND* b = backend_ref[i].bref_backend; - - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "%lu [select_backend_servers] Examine server " - "%s:%d with %d connections. Status is %d, " - "router->bitvalue is %d", - pthread_self(), - b->backend_server->name, - b->backend_server->port, - b->backend_conn_count, - b->backend_server->status, - router->bitmask))); - + if (SERVER_IS_RUNNING(b->backend_server) && ((b->backend_server->status & router->bitmask) == router->bitvalue)) { + /* check also for relay servers and don't take the master_host */ if (slaves_found < max_nslaves && - SERVER_IS_SLAVE(b->backend_server)) + (max_slave_rlag == -2 || + (b->backend_server->rlag != -1 && /*< information currently 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))) { slaves_found += 1; - backend_ref[i].bref_dcb = dcb_connect( - b->backend_server, - session, - b->backend_server->protocol); - if (backend_ref[i].bref_dcb != NULL) + /** Slave is already connected */ + if (BREF_IS_IN_USE((&backend_ref[i]))) { slaves_connected += 1; - /** - * Increase backend connection counter. - * Server's stats are _increased_ in - * dcb.c:dcb_alloc ! - * But decreased in the calling function - * of dcb_close. - */ - atomic_add(&b->backend_conn_count, 1); } + /** New slave connection is taking place */ else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to establish " - "connection with slave %s:%d", - b->backend_server->name, - b->backend_server->port))); - /* handle connect error */ + backend_ref[i].bref_dcb = dcb_connect( + b->backend_server, + session, + b->backend_server->protocol); + + if (backend_ref[i].bref_dcb != NULL) + { + slaves_connected += 1; + /** + * Start executing session command + * history. + */ + execute_sescmd_history(&backend_ref[i]); + /** + * When server fails, this callback + * is called. + */ + dcb_add_callback( + backend_ref[i].bref_dcb, + DCB_REASON_NOT_RESPONDING, + &router_handle_state_switch, + (void *)&backend_ref[i]); + backend_ref[i].bref_state = 0; + bref_set_state(&backend_ref[i], + BREF_IN_USE); + /** + * Increase backend connection counter. + * Server's stats are _increased_ in + * dcb.c:dcb_alloc ! + * But decreased in the calling function + * of dcb_close. + */ + atomic_add(&b->backend_conn_count, 1); + } + else + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to establish " + "connection with slave %s:%d", + b->backend_server->name, + b->backend_server->port))); + /* handle connect error */ + } } } - else if (!master_connected && - (SERVER_IS_MASTER(b->backend_server))) + /* take the master_host for master */ + else if (master_host && + (b->backend_server == master_host->backend_server)) { + *p_master_ref = &backend_ref[i]; + + if (master_connected) + { + continue; + } master_found = true; backend_ref[i].bref_dcb = dcb_connect( @@ -1562,14 +2023,23 @@ 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; - *p_master_ref = &backend_ref[i]; - /** Increase backend connection counter */ - /** Increase backend connection counter */ - atomic_add(&b->backend_server->stats.n_current, 1); - atomic_add(&b->backend_server->stats.n_connections, 1); + /** + * When server fails, this callback + * is called. + */ + dcb_add_callback( + backend_ref[i].bref_dcb, + DCB_REASON_NOT_RESPONDING, + &router_handle_state_switch, + (void *)&backend_ref[i]); + + backend_ref[i].bref_state = 0; + bref_set_state(&backend_ref[i], + BREF_IN_USE); + /** Increase backend connection counters */ atomic_add(&b->backend_conn_count, 1); } else @@ -1581,12 +2051,33 @@ static bool select_connect_backend_servers( "connection with master %s:%d", b->backend_server->name, b->backend_server->port))); - /* handle connect error */ + /** handle connect error */ } - } + } } } /*< for */ +#if defined(EXTRA_SS_DEBUG) + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "Servers and conns after ordering:"))); + + for (i=0; ibackend_server->name, + b->backend_server->port, + b->backend_conn_count))); + } + /* assert with master_host */ + ss_dassert(!master_connected || + (master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && SERVER_MASTER)); +#endif + /** * Successful cases */ @@ -1644,8 +2135,8 @@ static bool select_connect_backend_servers( for (i=0; ibackend_conn_count > 0); + /** disconnect opened connections */ - backend_ref[i].bref_dcb->func.close(backend_ref[i].bref_dcb); + dcb_close(backend_ref[i].bref_dcb); + bref_clear_state(&backend_ref[i], BREF_IN_USE); + /** Decrease backend's connection counter. */ atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1); } } @@ -1786,7 +2282,7 @@ static void rses_property_done( mysql_sescmd_done(&prop->rses_prop_data.sescmd); break; default: - LOGIF(LD, (skygw_log_write_flush( + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [rses_property_done] Unknown property type %d " "in property %p", @@ -1928,64 +2424,48 @@ static void mysql_sescmd_done( * 9. s+q+ */ static GWBUF* sescmd_cursor_process_replies( - DCB* client_dcb, GWBUF* replybuf, - sescmd_cursor_t* scur) + backend_ref_t* bref) { - const size_t headerlen = 4; /*< mysql packet header */ - uint8_t* packet; - size_t packetlen; - mysql_sescmd_t* scmd; + mysql_sescmd_t* scmd; + sescmd_cursor_t* scur; + scur = &bref->bref_sescmd_cur; ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock))); - scmd = sescmd_cursor_get_command(scur); - CHK_DCB(client_dcb); CHK_GWBUF(replybuf); /** * Walk through packets in the message and the list of session - *commands. + * commands. */ while (scmd != NULL && replybuf != NULL) - { + { + /** Faster backend has already responded to client : discard */ if (scmd->my_sescmd_is_replied) { - /** - * Discard heading packets if their related command is - * already replied. - */ + bool last_packet = false; + CHK_GWBUF(replybuf); - packet = (uint8_t *)GWBUF_DATA(replybuf); - packetlen = packet[0]+packet[1]*256+packet[2]*256*256; - replybuf = gwbuf_consume(replybuf, packetlen+headerlen); -/* - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [sescmd_cursor_process_replies] cmd %p " - "is already replied. Discarded %d bytes from " - "the %s replybuffer.", - pthread_self(), - scmd, - packetlen+headerlen, - STRBETYPE(scur->scmd_cur_be_type)))); - */ + + while (!last_packet) + { + int buflen; + + buflen = GWBUF_LENGTH(replybuf); + last_packet = GWBUF_IS_TYPE_RESPONSE_END(replybuf); + /** discard packet */ + replybuf = gwbuf_consume(replybuf, buflen); + } + /** Set response status received */ + bref_clear_state(bref, BREF_WAITING_RESULT); } - else + /** Response is in the buffer and it will be sent to client. */ + else if (replybuf != NULL) { /** Mark the rest session commands as replied */ scmd->my_sescmd_is_replied = true; - /* - LOGIF(LT, (skygw_log_write_flush( - LOGFILE_TRACE, - "%lu [sescmd_cursor_process_replies] Marked " - "cmd %p to as replied. Left message to %s's " - "buffer for reply.", - pthread_self(), - scmd, - STRBETYPE(scur->scmd_cur_be_type)))); - */ } if (sescmd_cursor_next(scur)) @@ -1998,7 +2478,7 @@ static GWBUF* sescmd_cursor_process_replies( /** All session commands are replied */ scur->scmd_cur_active = false; } - } + } ss_dassert(replybuf == NULL || *scur->scmd_cur_ptr_property == NULL); return replybuf; @@ -2063,6 +2543,64 @@ static GWBUF* sescmd_cursor_clone_querybuf( return buf; } +static bool sescmd_cursor_history_empty( + sescmd_cursor_t* scur) +{ + bool succp; + + CHK_SESCMD_CUR(scur); + + if (scur->scmd_cur_rses->rses_properties[RSES_PROP_TYPE_SESCMD] == NULL) + { + succp = true; + } + else + { + succp = false; + } + + return succp; +} + + +static void sescmd_cursor_reset( + sescmd_cursor_t* scur) +{ + ROUTER_CLIENT_SES* rses; + CHK_SESCMD_CUR(scur); + CHK_CLIENT_RSES(scur->scmd_cur_rses); + rses = scur->scmd_cur_rses; + + scur->scmd_cur_ptr_property = &rses->rses_properties[RSES_PROP_TYPE_SESCMD]; + + CHK_RSES_PROP((*scur->scmd_cur_ptr_property)); + scur->scmd_cur_active = false; + scur->scmd_cur_cmd = &(*scur->scmd_cur_ptr_property)->rses_prop_data.sescmd; +} + +static bool execute_sescmd_history( + backend_ref_t* bref) +{ + bool succp; + sescmd_cursor_t* scur; + CHK_BACKEND_REF(bref); + + scur = &bref->bref_sescmd_cur; + CHK_SESCMD_CUR(scur); + + if (!sescmd_cursor_history_empty(scur)) + { + sescmd_cursor_reset(scur); + succp = execute_sescmd_in_backend(bref); + } + else + { + succp = true; + } + + return succp; +} + /** * If session command cursor is passive, sends the command to backend for * execution. @@ -2077,12 +2615,13 @@ static bool execute_sescmd_in_backend( backend_ref_t* backend_ref) { DCB* dcb; - bool succp = true; + bool succp; int rc = 0; sescmd_cursor_t* scur; - - if (backend_ref->bref_dcb == NULL) + + if (BREF_IS_CLOSED(backend_ref)) { + succp = false; goto return_succp; } dcb = backend_ref->bref_dcb; @@ -2099,6 +2638,10 @@ static bool execute_sescmd_in_backend( if (sescmd_cursor_get_command(scur) == NULL) { succp = false; + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "Cursor had no pending session commands."))); + goto return_succp; } @@ -2107,14 +2650,29 @@ static bool execute_sescmd_in_backend( /** Cursor is left active when function returns. */ sescmd_cursor_set_active(scur, true); } - +#if defined(SS_DEBUG) LOGIF(LT, tracelog_routed_query(scur->scmd_cur_rses, "execute_sescmd_in_backend", backend_ref, sescmd_cursor_clone_querybuf(scur))); - + + { + GWBUF* tmpbuf = sescmd_cursor_clone_querybuf(scur); + uint8_t* ptr = GWBUF_DATA(tmpbuf); + unsigned char cmd = MYSQL_GET_COMMAND(ptr); + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [execute_sescmd_in_backend] Just before write, fd " + "%d : cmd %s.", + pthread_self(), + dcb->fd, + STRPACKETTYPE(cmd)))); + gwbuf_free(tmpbuf); + } +#endif /*< SS_DEBUG */ switch (scur->scmd_cur_cmd->my_sescmd_packet_type) { - case COM_CHANGE_USER: + case MYSQL_COM_CHANGE_USER: rc = dcb->func.auth( dcb, NULL, @@ -2122,9 +2680,14 @@ static bool execute_sescmd_in_backend( sescmd_cursor_clone_querybuf(scur)); break; - case COM_QUERY: - case COM_INIT_DB: + case MYSQL_COM_QUERY: + case MYSQL_COM_INIT_DB: default: + /** + * Mark session command buffer, it triggers writing + * MySQL command to protocol + */ + gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); rc = dcb->func.write( dcb, sescmd_cursor_clone_querybuf(scur)); @@ -2135,9 +2698,13 @@ static bool execute_sescmd_in_backend( "%lu [execute_sescmd_in_backend] Routed %s cmd %p.", pthread_self(), STRPACKETTYPE(scur->scmd_cur_cmd->my_sescmd_packet_type), - scur->scmd_cur_cmd))); + scur->scmd_cur_cmd))); - if (rc != 1) + if (rc == 1) + { + succp = true; + } + else { succp = false; } @@ -2247,7 +2814,7 @@ static void tracelog_routed_query( be_type = BACKEND_TYPE(b); - if (GWBUF_TYPE(buf) == GWBUF_TYPE_MYSQL) + if (GWBUF_IS_TYPE_MYSQL(buf)) { len = packet[0]; len += 256*packet[1]; @@ -2271,6 +2838,28 @@ static void tracelog_routed_query( dcb))); free(querystr); } + else if (packet_type == '\x22' || + packet_type == 0x22 || + packet_type == '\x26' || + packet_type == 0x26 || + true) + { + querystr = (char *)malloc(len); + memcpy(querystr, startpos, len-1); + querystr[len-1] = '\0'; + LOGIF(LT, (skygw_log_write_flush( + LOGFILE_TRACE, + "%lu [%s] %d bytes long buf, \"%s\" -> %s:%d %s dcb %p", + pthread_self(), + funcname, + buflen, + querystr, + b->backend_server->name, + b->backend_server->port, + STRBETYPE(be_type), + dcb))); + free(querystr); + } } gwbuf_free(buf); } @@ -2334,12 +2923,14 @@ static bool route_session_write( backend_ref = router_cli_ses->rses_backend_ref; /** - * COM_QUIT is one-way message. Server doesn't respond to that. + * These are one-way messages and server doesn't respond to them. * Therefore reply processing is unnecessary and session - * command property is not needed. It is just routed to both + * command property is not needed. It is just routed to all available * backends. */ - if (packet_type == COM_QUIT) + if (packet_type == MYSQL_COM_STMT_SEND_LONG_DATA || + packet_type == MYSQL_COM_QUIT || + packet_type == MYSQL_COM_STMT_CLOSE) { int rc; @@ -2354,9 +2945,9 @@ static bool route_session_write( for (i=0; irses_nbackends; i++) { - DCB* dcb = backend_ref[i].bref_dcb; - - if (dcb != NULL) + DCB* dcb = backend_ref[i].bref_dcb; + + if (BREF_IS_IN_USE((&backend_ref[i]))) { rc = dcb->func.write(dcb, gwbuf_clone(querybuf)); @@ -2370,33 +2961,67 @@ static bool route_session_write( gwbuf_free(querybuf); goto return_succp; } - prop = rses_property_init(RSES_PROP_TYPE_SESCMD); - /** - * Additional reference is created to querybuf to - * prevent it from being released before properties - * are cleaned up as a part of router sessionclean-up. - */ - mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); - /** Lock router session */ if (!rses_begin_locked_router_action(router_cli_ses)) { rses_property_done(prop); succp = false; goto return_succp; - } + } + /** + * Additional reference is created to querybuf to + * prevent it from being released before properties + * are cleaned up as a part of router sessionclean-up. + */ + prop = rses_property_init(RSES_PROP_TYPE_SESCMD); + mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); + /** Add sescmd property to router client session */ rses_property_add(router_cli_ses, prop); - + for (i=0; irses_nbackends; i++) { - succp = execute_sescmd_in_backend(&backend_ref[i]); - - if (!succp) + if (BREF_IS_IN_USE((&backend_ref[i]))) { - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - goto return_succp; + sescmd_cursor_t* scur; + + scur = backend_ref_get_sescmd_cursor(&backend_ref[i]); + + /** + * Add one waiter to backend reference. + */ + bref_set_state(get_bref_from_dcb(router_cli_ses, + backend_ref[i].bref_dcb), + BREF_WAITING_RESULT); + /** + * Start execution if cursor is not already executing. + * Otherwise, cursor will execute pending commands + * when it completes with previous commands. + */ + if (sescmd_cursor_is_active(scur)) + { + succp = true; + + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Backend %s:%d already executing sescmd.", + backend_ref[i].bref_backend->backend_server->name, + backend_ref[i].bref_backend->backend_server->port))); + } + else + { + succp = execute_sescmd_in_backend(&backend_ref[i]); + + if (!succp) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to execute session " + "command in %s:%d", + backend_ref[i].bref_backend->backend_server->name, + backend_ref[i].bref_backend->backend_server->port))); + } + } } } /** Unlock router session */ @@ -2408,7 +3033,44 @@ return_succp: return succp; } -static void rwsplit_process_options( +#if defined(NOT_USED) +static bool router_option_configured( + ROUTER_INSTANCE* router, + const char* optionstr, + void* data) +{ + bool succp = false; + char** option; + + option = router->service->routerOptions; + + while (option != NULL) + { + char* value; + + if ((value = strchr(options[i], '=')) == NULL) + { + break; + } + else + { + *value = 0; + value++; + if (strcmp(options[i], "slave_selection_criteria") == 0) + { + if (GET_SELECT_CRITERIA(value) == (select_criteria_t *)*data) + { + succp = true; + break; + } + } + } + } + return succp; +} +#endif /*< NOT_USED */ + +static void rwsplit_process_router_options( ROUTER_INSTANCE* router, char** options) { @@ -2437,6 +3099,7 @@ static void rwsplit_process_options( c == LEAST_GLOBAL_CONNECTIONS || c == LEAST_ROUTER_CONNECTIONS || c == LEAST_BEHIND_MASTER || + c == LEAST_CURRENT_OPERATIONS || c == UNDEFINED_CRITERIA); if (c == UNDEFINED_CRITERIA) @@ -2444,9 +3107,10 @@ static void rwsplit_process_options( LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "Warning : Unknown " "slave selection criteria \"%s\". " - "Allowed values are \"LEAST_GLOBAL_CONNECTIONS\", " + "Allowed values are LEAST_GLOBAL_CONNECTIONS, " "LEAST_ROUTER_CONNECTIONS, " - "and \"LEAST_ROUTER_CONNECTIONS\".", + "LEAST_BEHIND_MASTER," + "and LEAST_CURRENT_OPERATIONS.", STRCRITERIA(router->rwsplit_config.rw_slave_select_criteria)))); } else @@ -2457,3 +3121,528 @@ static void rwsplit_process_options( } } /*< for */ } + +/** + * Error Handler routine to resolve backend failures. If it succeeds then there + * are enough operative backends available and connected. Otherwise it fails, + * and session is terminated. + * + * @param instance The router instance + * @param router_session The router session + * @param message The error message to reply + * @param backend_dcb The backend DCB + * @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION + * @param succp Result of action. + * + * Even if succp == true connecting to new slave may have failed. succp is to + * tell whether router has enough master/slave connections to continue work. + */ + +static void handleError ( + ROUTER* instance, + void* router_session, + GWBUF* errmsgbuf, + DCB* backend_dcb, + error_action_t action, + bool* succp) +{ + SESSION* session; + ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; + ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *)router_session; + + CHK_DCB(backend_dcb); +#if defined(SS_DEBUG) + backend_dcb->dcb_errhandle_called = true; +#endif + session = backend_dcb->session; + + if (session != NULL) + CHK_SESSION(session); + + switch (action) { + case ERRACT_NEW_CONNECTION: + { + if (rses != NULL) + CHK_CLIENT_RSES(rses); + + if (!rses_begin_locked_router_action(rses)) + { + *succp = false; + return; + } + + *succp = handle_error_new_connection(inst, + rses, + backend_dcb, + errmsgbuf); + rses_end_locked_router_action(rses); + break; + } + + case ERRACT_REPLY_CLIENT: + { + *succp = handle_error_reply_client(session, errmsgbuf); + break; + } + + default: + *succp = false; + break; + } +} + + +static bool handle_error_reply_client( + SESSION* ses, + GWBUF* errmsg) +{ + session_state_t sesstate; + DCB* client_dcb; + bool succp; + + spinlock_acquire(&ses->ses_lock); + sesstate = ses->state; + client_dcb = ses->client; + spinlock_release(&ses->ses_lock); + + if (sesstate == SESSION_STATE_ROUTER_READY) + { + CHK_DCB(client_dcb); + client_dcb->func.write(client_dcb, errmsg); + } + succp = false; /** false because new servers aren's selected. */ + + return succp; +} + +/** + * This must be called with router lock + */ +static bool handle_error_new_connection( + ROUTER_INSTANCE* inst, + ROUTER_CLIENT_SES* rses, + DCB* backend_dcb, + GWBUF* errmsg) +{ + SESSION* ses; + int router_nservers; + int max_nslaves; + int max_slave_rlag; + backend_ref_t* bref; + bool succp; + + ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); + + ses = backend_dcb->session; + CHK_SESSION(ses); + + bref = get_bref_from_dcb(rses, backend_dcb); + + /** failed DCB has already been replaced */ + if (bref == NULL) + { + succp = true; + goto return_succp; + } + /** + * Error handler is already called for this DCB because + * it's not polling anymore. It can be assumed that + * it succeed because rses isn't closed. + */ + if (backend_dcb->state != DCB_STATE_POLLING) + { + succp = true; + goto return_succp; + } + + CHK_BACKEND_REF(bref); + + if (BREF_IS_WAITING_RESULT(bref)) + { + DCB* client_dcb; + client_dcb = ses->client; + client_dcb->func.write(client_dcb, errmsg); + bref_clear_state(bref, BREF_WAITING_RESULT); + } + bref_clear_state(bref, BREF_IN_USE); + bref_set_state(bref, BREF_CLOSED); + /** + * Remove callback because this DCB won't be used + * unless it is reconnected later, and then the callback + * is set again. + */ + dcb_remove_callback(backend_dcb, + DCB_REASON_NOT_RESPONDING, + &router_handle_state_switch, + (void *)bref); + + router_nservers = router_get_servercount(inst); + max_nslaves = rses_get_max_slavecount(rses, router_nservers); + max_slave_rlag = rses_get_max_replication_lag(rses); + /** + * Try to get replacement slave or at least the minimum + * number of slave connections for router session. + */ + succp = select_connect_backend_servers( + &rses->rses_master_ref, + rses->rses_backend_ref, + router_nservers, + max_nslaves, + max_slave_rlag, + rses->rses_config.rw_slave_select_criteria, + ses, + inst); + +return_succp: + return succp; +} + + +static void print_error_packet( + ROUTER_CLIENT_SES* rses, + GWBUF* buf, + DCB* dcb) +{ +#if defined(SS_DEBUG) + if (GWBUF_IS_TYPE_MYSQL(buf)) + { + while (gwbuf_length(buf) > 0) + { + /** + * This works with MySQL protocol only ! + * Protocol specific packet print functions would be nice. + */ + uint8_t* ptr = GWBUF_DATA(buf); + size_t len = MYSQL_GET_PACKET_LEN(ptr); + + if (MYSQL_GET_COMMAND(ptr) == 0xff) + { + SERVER* srv = NULL; + backend_ref_t* bref = rses->rses_backend_ref; + int i; + char* bufstr; + + for (i=0; irses_nbackends; i++) + { + if (bref[i].bref_dcb == dcb) + { + srv = bref[i].bref_backend->backend_server; + } + } + ss_dassert(srv != NULL); + + bufstr = strndup(&ptr[7], len-3); + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Backend server %s:%d responded with " + "error : %s", + srv->name, + srv->port, + bufstr))); + free(bufstr); + } + buf = gwbuf_consume(buf, len+4); + } + } + else + { + while ((buf = gwbuf_consume(buf, GWBUF_LENGTH(buf))) != NULL); + } +#endif /*< SS_DEBUG */ +} + +static int router_get_servercount( + ROUTER_INSTANCE* inst) +{ + int router_nservers = 0; + BACKEND** b = inst->servers; + /** count servers */ + while (*(b++) != NULL) router_nservers++; + + return router_nservers; +} + +static bool have_enough_servers( + ROUTER_CLIENT_SES** p_rses, + const int min_nsrv, + int router_nsrv, + ROUTER_INSTANCE* router) +{ + bool succp; + + /** With too few servers session is not created */ + if (router_nsrv < min_nsrv || + MAX((*p_rses)->rses_config.rw_max_slave_conn_count, + (router_nsrv*(*p_rses)->rses_config.rw_max_slave_conn_percent)/100) + < min_nsrv) + { + if (router_nsrv < min_nsrv) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers available. Found %d " + "when %d is required.", + router->service->name, + router_nsrv, + min_nsrv))); + } + else + { + double pct = (*p_rses)->rses_config.rw_max_slave_conn_percent/100; + double nservers = (double)router_nsrv*pct; + + if ((*p_rses)->rses_config.rw_max_slave_conn_count < min_nsrv) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers configured in " + "MaxScale.cnf. Found %d when %d is required.", + router->service->name, + (*p_rses)->rses_config.rw_max_slave_conn_count, + min_nsrv))); + } + if (nservers < min_nsrv) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to start %s service. There are " + "too few backend servers configured in " + "MaxScale.cnf. Found %d%% when at least %.0f%% " + "would be required.", + router->service->name, + (*p_rses)->rses_config.rw_max_slave_conn_percent, + min_nsrv/(((double)router_nsrv)/100)))); + } + } + free(*p_rses); + *p_rses = NULL; + succp = false; + } + else + { + succp = true; + } + return succp; +} + +/** + * Find out the number of read backend servers. + * Depending on the configuration value type, either copy direct count + * of slave connections or calculate the count from percentage value. + */ +static int rses_get_max_slavecount( + ROUTER_CLIENT_SES* rses, + int router_nservers) +{ + int conf_max_nslaves; + int max_nslaves; + + CHK_CLIENT_RSES(rses); + + if (rses->rses_config.rw_max_slave_conn_count > 0) + { + conf_max_nslaves = rses->rses_config.rw_max_slave_conn_count; + } + else + { + conf_max_nslaves = + (router_nservers*rses->rses_config.rw_max_slave_conn_percent)/100; + } + max_nslaves = MIN(router_nservers-1, MAX(1, conf_max_nslaves)); + + return max_nslaves; +} + + +static int rses_get_max_replication_lag( + ROUTER_CLIENT_SES* rses) +{ + int conf_max_rlag; + + CHK_CLIENT_RSES(rses); + + /** if there is no configured value, then longest possible int is used */ + if (rses->rses_config.rw_max_slave_replication_lag > 0) + { + conf_max_rlag = rses->rses_config.rw_max_slave_replication_lag; + } + else + { + conf_max_rlag = ~(1<<31); + } + + return conf_max_rlag; +} + + +static backend_ref_t* get_bref_from_dcb( + ROUTER_CLIENT_SES* rses, + DCB* dcb) +{ + backend_ref_t* bref; + int i = 0; + CHK_DCB(dcb); + CHK_CLIENT_RSES(rses); + + bref = rses->rses_backend_ref; + + while (irses_nbackends) + { + if (bref->bref_dcb == dcb) + { + break; + } + bref++; + i += 1; + } + + if (i == rses->rses_nbackends) + { + bref = NULL; + } + return bref; +} + +static int router_handle_state_switch( + DCB* dcb, + DCB_REASON reason, + void* data) +{ + backend_ref_t* bref; + int rc = 1; + ROUTER_CLIENT_SES* rses; + SESSION* ses; + SERVER* srv; + + CHK_DCB(dcb); + bref = (backend_ref_t *)data; + CHK_BACKEND_REF(bref); + + srv = bref->bref_backend->backend_server; + + if (SERVER_IS_RUNNING(srv) && SERVER_IS_IN_CLUSTER(srv)) + { + goto return_rc; + } + ses = dcb->session; + CHK_SESSION(ses); + + rses = (ROUTER_CLIENT_SES *)dcb->session->router_session; + CHK_CLIENT_RSES(rses); + + switch (reason) { + case DCB_REASON_NOT_RESPONDING: + dcb->func.hangup(dcb); + break; + + default: + break; + } + +return_rc: + return rc; +} + +static sescmd_cursor_t* backend_ref_get_sescmd_cursor ( + backend_ref_t* bref) +{ + sescmd_cursor_t* scur; + CHK_BACKEND_REF(bref); + + scur = &bref->bref_sescmd_cur; + CHK_SESCMD_CUR(scur); + + return scur; +} + +#if defined(PREP_STMT_CACHING) +#define MAX_STMT_LEN 1024 + +static prep_stmt_t* prep_stmt_init( + prep_stmt_type_t type, + void* id) +{ + prep_stmt_t* pstmt; + + pstmt = (prep_stmt_t *)calloc(1, sizeof(prep_stmt_t)); + + if (pstmt != NULL) + { +#if defined(SS_DEBUG) + pstmt->pstmt_chk_top = CHK_NUM_PREP_STMT; + pstmt->pstmt_chk_tail = CHK_NUM_PREP_STMT; +#endif + pstmt->pstmt_state = PREP_STMT_ALLOC; + pstmt->pstmt_type = type; + + if (type == PREP_STMT_NAME) + { + pstmt->pstmt_id.name = strndup((char *)id, MAX_STMT_LEN); + } + else + { + pstmt->pstmt_id.seq = 0; + } + } + CHK_PREP_STMT(pstmt); + return pstmt; +} + +static void prep_stmt_done( + prep_stmt_t* pstmt) +{ + CHK_PREP_STMT(pstmt); + + if (pstmt->pstmt_type == PREP_STMT_NAME) + { + free(pstmt->pstmt_id.name); + } + free(pstmt); +} + +static bool prep_stmt_drop( + prep_stmt_t* pstmt) +{ + CHK_PREP_STMT(pstmt); + + pstmt->pstmt_state = PREP_STMT_DROPPED; + return true; +} +#endif /*< PREP_STMT_CACHING */ + +/******************************** + * This routine returns the root master server from MySQL replication tree + * Get the root Master rule: + * + * find server with the lowest replication depth level + * and the SERVER_MASTER bitval + * Servers are checked even if they are in 'maintenance' + * + * @param servers The list of servers + * @param router_nservers The number of servers + * @return The Master found + * + */ +static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers) { + int i = 0; + BACKEND * master_host = NULL; + + for (i = 0; i< router_nservers; i++) { + BACKEND* b = NULL; + b = servers[i].bref_backend; + if (b && (b->backend_server->status & (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) { + if (master_host && b->backend_server->depth < master_host->backend_server->depth) { + master_host = b; + } else { + if (master_host == NULL) { + master_host = b; + } + } + } + } + return master_host; +} + diff --git a/server/modules/routing/readwritesplit/test/rwsplit.sh b/server/modules/routing/readwritesplit/test/rwsplit.sh index 6c5268d46..36199e384 100755 --- a/server/modules/routing/readwritesplit/test/rwsplit.sh +++ b/server/modules/routing/readwritesplit/test/rwsplit.sh @@ -191,3 +191,42 @@ else echo "$TINPUT PASSED">>$TLOG ; fi + +TINPUT=test_sescmd.sql +TRETVAL=2 +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi +a=`$RUNCMD < ./$TINPUT` +if [ "$a" != "$TRETVAL" ]; then + echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG; +else + echo "$TINPUT PASSED">>$TLOG ; +fi diff --git a/server/modules/routing/readwritesplit/test/test_sescmd.sql b/server/modules/routing/readwritesplit/test/test_sescmd.sql new file mode 100644 index 000000000..7bc6edcf9 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_sescmd.sql @@ -0,0 +1,4 @@ +use test; +set autocommit=1; +use mysql; +select count(*) from user where user='maxuser' diff --git a/server/test/makefile b/server/test/makefile index d812494fc..7f9c36b84 100644 --- a/server/test/makefile +++ b/server/test/makefile @@ -10,7 +10,7 @@ include ../../test.inc TEST_ROOT := $(ROOT_PATH)/test PARENT_DIR := $(ROOT_PATH)/server CUR_DIR := $(PARENT_DIR)/test -export MAXSCALE_HOME=$(CUR_DIR)/MaxScale +export MAXSCALE_HOME=$(CUR_DIR) CC=cc TESTLOG := $(CUR_DIR)/testserver.log diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 3910429a2..43a609a40 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -122,7 +122,8 @@ typedef enum skygw_chk_t { CHK_NUM_ROUTER_PROPERTY, CHK_NUM_SESCMD_CUR, CHK_NUM_BACKEND, - CHK_NUM_BACKEND_REF + CHK_NUM_BACKEND_REF, + CHK_NUM_PREP_STMT } skygw_chk_t; # define STRBOOL(b) ((b) ? "true" : "false") @@ -140,23 +141,26 @@ typedef enum skygw_chk_t { ((i) == LOGFILE_DEBUG ? "LOGFILE_DEBUG" : \ "Unknown logfile type")))) -#define STRPACKETTYPE(p) ((p) == COM_INIT_DB ? "COM_INIT_DB" : \ - ((p) == COM_CREATE_DB ? "COM_CREATE_DB" : \ - ((p) == COM_DROP_DB ? "COM_DROP_DB" : \ - ((p) == COM_REFRESH ? "COM_REFRESH" : \ - ((p) == COM_DEBUG ? "COM_DEBUG" : \ - ((p) == COM_PING ? "COM_PING" : \ - ((p) == COM_CHANGE_USER ? "COM_CHANGE_USER" : \ - ((p) == COM_QUERY ? "COM_QUERY" : \ - ((p) == COM_SHUTDOWN ? "COM_SHUTDOWN" : \ - ((p) == COM_PROCESS_INFO ? "COM_PROCESS_INFO" : \ - ((p) == COM_CONNECT ? "COM_CONNECT" : \ - ((p) == COM_PROCESS_KILL ? "COM_PROCESS_KILL" : \ - ((p) == COM_TIME ? "COM_TIME" : \ - ((p) == COM_DELAYED_INSERT ? "COM_DELAYED_INSERT" : \ - ((p) == COM_DAEMON ? "COM_DAEMON" : \ - ((p) == COM_QUIT ? "COM_QUIT" : \ - "UNKNOWN MYSQL PACKET TYPE")))))))))))))))) +#define STRPACKETTYPE(p) ((p) == MYSQL_COM_INIT_DB ? "COM_INIT_DB" : \ + ((p) == MYSQL_COM_CREATE_DB ? "COM_CREATE_DB" : \ + ((p) == MYSQL_COM_DROP_DB ? "COM_DROP_DB" : \ + ((p) == MYSQL_COM_REFRESH ? "COM_REFRESH" : \ + ((p) == MYSQL_COM_DEBUG ? "COM_DEBUG" : \ + ((p) == MYSQL_COM_PING ? "COM_PING" : \ + ((p) == MYSQL_COM_CHANGE_USER ? "COM_CHANGE_USER" : \ + ((p) == MYSQL_COM_QUERY ? "COM_QUERY" : \ + ((p) == MYSQL_COM_SHUTDOWN ? "COM_SHUTDOWN" : \ + ((p) == MYSQL_COM_PROCESS_INFO ? "COM_PROCESS_INFO" : \ + ((p) == MYSQL_COM_CONNECT ? "COM_CONNECT" : \ + ((p) == MYSQL_COM_PROCESS_KILL ? "COM_PROCESS_KILL" : \ + ((p) == MYSQL_COM_TIME ? "COM_TIME" : \ + ((p) == MYSQL_COM_DELAYED_INSERT ? "COM_DELAYED_INSERT" : \ + ((p) == MYSQL_COM_DAEMON ? "COM_DAEMON" : \ + ((p) == MYSQL_COM_QUIT ? "COM_QUIT" : \ + ((p) == MYSQL_COM_STMT_PREPARE ? "MYSQL_COM_STMT_PREPARE" : \ + ((p) == MYSQL_COM_STMT_EXECUTE ? "MYSQL_COM_STMT_EXECUTE" : \ + ((p) == MYSQL_COM_UNDEFINED ? "MYSQL_COM_UNDEFINED" : \ + "UNKNOWN MYSQL PACKET TYPE"))))))))))))))))))) #define STRDCBSTATE(s) ((s) == DCB_STATE_ALLOC ? "DCB_STATE_ALLOC" : \ ((s) == DCB_STATE_POLLING ? "DCB_STATE_POLLING" : \ @@ -180,10 +184,7 @@ typedef enum skygw_chk_t { ((s) == MYSQL_AUTH_RECV ? "MYSQL_AUTH_RECV" : \ ((s) == MYSQL_AUTH_FAILED ? "MYSQL_AUTH_FAILED" : \ ((s) == MYSQL_IDLE ? "MYSQL_IDLE" : \ - ((s) == MYSQL_ROUTING ? "MYSQL_ROUTING" : \ - ((s) == MYSQL_WAITING_RESULT ? "MYSQL_WAITING_RESULT" : \ - ((s) == MYSQL_SESSION_CHANGE ? "MYSQL_SESSION_CHANGE" : \ - "UNKNOWN MYSQL STATE")))))))))) + "UNKNOWN MYSQL STATE"))))))) #define STRITEMTYPE(t) ((t) == Item::FIELD_ITEM ? "FIELD_ITEM" : \ ((t) == Item::FUNC_ITEM ? "FUNC_ITEM" : \ @@ -227,7 +228,8 @@ typedef enum skygw_chk_t { #define STRCRITERIA(c) ((c) == UNDEFINED_CRITERIA ? "UNDEFINED_CRITERIA" : \ ((c) == LEAST_GLOBAL_CONNECTIONS ? "LEAST_GLOBAL_CONNECTIONS" : \ ((c) == LEAST_ROUTER_CONNECTIONS ? "LEAST_ROUTER_CONNECTIONS" : \ - ((c) == LEAST_BEHIND_MASTER ? "LEAST_BEHIND_MASTER" : "Unknown criteria")))) + ((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" : \ @@ -478,6 +480,12 @@ typedef enum skygw_chk_t { "Backend reference has invalid check fields"); \ } +#define CHK_PREP_STMT(p) { \ + ss_info_dassert((p)->pstmt_chk_top == CHK_NUM_PREP_STMT && \ + (p)->pstmt_chk_tail == CHK_NUM_PREP_STMT, \ + "Prepared statement struct has invalid check fields"); \ +} + #if defined(SS_DEBUG) bool conn_open[10240];