Merge branch 'develop' into blr

Add instrumentation

Remove mutexes

Improve gwbuf_append performance

Conflicts:
	server/core/dcb.c
	server/modules/protocol/mysql_backend.c
This commit is contained in:
Mark Riddoch 2014-08-28 11:41:26 +01:00
commit 81e1dd8719
96 changed files with 9409 additions and 1986 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -1 +1 @@
0.7.0
1.0.0-beta

View File

@ -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

79
client/Makefile Normal file
View File

@ -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

554
client/maxadmin.c Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <dirent.h>
#include <locale.h>
#include <errno.h>
#ifdef HISTORY
#include <histedit.h>
#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] [<command file> | <command>]\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");
}

10
debian/changelog vendored Normal file
View File

@ -0,0 +1,10 @@
maxscale (1.0-beta) UNRELEASED; urgency=low
* Beta release
-- Timofey Turenko <timofey.turenko@skysql.com> Fri, 05 Jul 2014 14:00:00 +0200
maxscale (0.7-1) UNRELEASED; urgency=low
* Initial release. (Closes: #XXXXXX)
-- Timofey Turenko <timofey.turenko@skysql.com> Tue, 11 Mar 2014 22:59:35 +0200

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
8

15
debian/control vendored Normal file
View File

@ -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.

3
debian/install vendored Normal file
View File

@ -0,0 +1,3 @@
maxscale.conf etc/ld.so.conf.d/
etc/ubuntu/init.d/maxscale etc/init.d/
binaries/* /usr/local/skysql/maxscale/

4
debian/postinst vendored Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
ln -s /lib64/libaio.so.1 /lib64/libaio.so
/sbin/ldconfig

11
debian/rules vendored Executable file
View File

@ -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:

90
etc/init.d/maxscale Normal file → Executable file
View File

@ -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

145
etc/ubuntu/init.d/maxscale Executable file
View File

@ -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

View File

@ -40,4 +40,14 @@ endif
ifdef PROF
CFLAGS := $(CFLAGS) -DSS_PROF
endif
endif
ifeq "$(PROFILE)" "Y"
CFLAGS += -pg
LDFLAGS += -pg
endif
ifeq "$(GCOV)" "Y"
CFLAGS += -fprofile-arcs -ftest-coverage
LIBS += -lgcov
endif

View File

@ -1,2 +1,2 @@
/usr/local/sbin/MaxScale/modules
/usr/local/sbin/lib
/usr/local/skysql/maxscale/modules
/usr/local/skysql/maxscale/lib

View File

@ -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

View File

@ -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<char*>(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(
}
/**<! fall through */
case SQLCOM_CHANGE_DB:
case SQLCOM_DEALLOCATE_PREPARE:
type |= QUERY_TYPE_SESSION_WRITE;
break;
@ -520,6 +538,11 @@ static skygw_query_type_t resolve_query_type(
goto return_qtype;
break;
case SQLCOM_PREPARE:
type |= QUERY_TYPE_PREPARE_NAMED_STMT;
goto return_qtype;
break;
default:
break;
}
@ -785,3 +808,11 @@ static int is_autocommit_stmt(
return_rc:
return rc;
}
char* skygw_query_classifier_get_stmtname(
MYSQL* mysql)
{
return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str;
}

View File

@ -19,6 +19,7 @@ Copyright SkySQL Ab
/** getpid */
#include <unistd.h>
#include <mysql.h>
#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

View File

@ -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)

View File

@ -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);
}
/**

View File

@ -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)

View File

@ -42,6 +42,15 @@ passwd=maxpwd
# version_string=<specific string for server handshake,
# default is the MariaDB embedded library version>
#
# router_options=<option[=value]>,<option[=value]>,...
# where value=[master|slave|synced]
#
# Read/Write Split Router specific options are:
#
# max_slave_connections=<exact number or percentage of all slaves>
# max_slave_replication_lag=<allowed lag in seconds for a slave>
# 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

View File

@ -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

View File

@ -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;
}

View File

@ -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 <int> 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 <int> 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"))

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <poll.h>
#include <stdlib.h>
#include <unistd.h>
#include <mysql.h>
#include <monitor.h>
#include <version.h>
@ -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;
}

View File

@ -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.
*

View File

@ -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");
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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]);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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
*/

View File

@ -24,6 +24,8 @@
#include <skygw_utils.h>
#include <netinet/in.h>
#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 */

View File

@ -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 *);

View File

@ -38,7 +38,8 @@ typedef enum {
MODULE_IN_DEVELOPMENT = 0,
MODULE_ALPHA_RELEASE,
MODULE_BETA_RELEASE,
MODULE_GA
MODULE_GA,
MODULE_EXPERIMENTAL
} MODULE_STATUS;
/**

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 *);

View File

@ -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 <thread.h>
#include <stdbool.h>
#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

View File

@ -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

View File

@ -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 <stdio.h>
#include <fcntl.h>
#include <filter.h>
#include <modinfo.h>
#include <modutil.h>
#include <string.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <time.h>
#include <sys/time.h>
#include <regex.h>
#include <string.h>
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);
}

View File

@ -19,9 +19,13 @@
#include <filter.h>
#include <modinfo.h>
#include <modutil.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <string.h>
#include <regex.h>
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=<regular expression>
* replace=<replacement text>
* Two optional parameters
* source=<source address to limit filter>
* user=<username to limit filter>
*
* 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);
}
/**

467
server/modules/filter/tee.c Normal file
View File

@ -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 <stdio.h>
#include <fcntl.h>
#include <filter.h>
#include <modinfo.h>
#include <modutil.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <sys/time.h>
#include <regex.h>
#include <string.h>
#include <service.h>
#include <router.h>
#include <dcb.h>
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);
}
}

View File

@ -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,
};

View File

@ -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 <stdio.h>
#include <fcntl.h>
#include <filter.h>
#include <modinfo.h>
#include <modutil.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <regex.h>
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);
}
}
}
}

View File

@ -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 <dcb.h>
/**
* 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

View File

@ -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);

View File

@ -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;
/**

View File

@ -30,6 +30,38 @@
*/
#include <dcb.h>
#include <hashtable.h>
#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;

View File

@ -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

View File

@ -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))

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dcb.h>
#include <buffer.h>
#include <service.h>
#include <session.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <router.h>
#include <poll.h>
#include <atomic.h>
#include <gw.h>
#include <adminusers.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <modinfo.h>
#include <maxscaled.h>
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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 <npackets> 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);
}

View File

@ -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;
}
/**

View File

@ -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,

View File

@ -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)

View File

@ -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);

View File

@ -30,6 +30,7 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
@ -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);

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <service.h>
#include <session.h>
#include <router.h>
#include <modules.h>
#include <modinfo.h>
#include <atomic.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <debugcli.h>
#include <skygw_utils.h>
#include <log_manager.h>
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;
}

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,4 @@
use test;
set autocommit=1;
use mysql;
select count(*) from user where user='maxuser'

View File

@ -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

View File

@ -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];