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:
commit
81e1dd8719
Binary file not shown.
BIN
Documentation/MaxScale 1.0beta Release Notes.pdf
Normal file
BIN
Documentation/MaxScale 1.0beta Release Notes.pdf
Normal file
Binary file not shown.
Binary file not shown.
BIN
Documentation/MaxScale HA with Corosync and Pacemaker.pdf
Normal file
BIN
Documentation/MaxScale HA with Corosync and Pacemaker.pdf
Normal file
Binary file not shown.
BIN
Documentation/filters/QLA Filter.pdf
Normal file
BIN
Documentation/filters/QLA Filter.pdf
Normal file
Binary file not shown.
BIN
Documentation/filters/Regex Filter.pdf
Normal file
BIN
Documentation/filters/Regex Filter.pdf
Normal file
Binary file not shown.
BIN
Documentation/filters/Tee Filter.pdf
Normal file
BIN
Documentation/filters/Tee Filter.pdf
Normal file
Binary file not shown.
BIN
Documentation/filters/Top Filter.pdf
Normal file
BIN
Documentation/filters/Top Filter.pdf
Normal file
Binary file not shown.
4
Makefile
4
Makefile
@ -39,21 +39,25 @@ all:
|
||||
(cd log_manager; make)
|
||||
(cd query_classifier; make)
|
||||
(cd server; make)
|
||||
(cd client; make)
|
||||
|
||||
clean:
|
||||
(cd log_manager; make clean)
|
||||
(cd query_classifier; make clean)
|
||||
(cd server; make clean)
|
||||
(cd client; touch depend.mk; make clean)
|
||||
|
||||
depend:
|
||||
(cd log_manager; make depend)
|
||||
(cd query_classifier; make depend)
|
||||
(cd server; make depend)
|
||||
(cd client; touch depend.mk; make depend)
|
||||
|
||||
install:
|
||||
(cd server; make DEST=$(DEST) install)
|
||||
(cd log_manager; make DEST=$(DEST) install)
|
||||
(cd query_classifier; make DEST=$(DEST) install)
|
||||
(cd client; make DEST=$(DEST) install)
|
||||
|
||||
cleantests:
|
||||
$(MAKE) -C test cleantests
|
||||
|
@ -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
79
client/Makefile
Normal 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
554
client/maxadmin.c
Normal 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
10
debian/changelog
vendored
Normal 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
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
||||
8
|
15
debian/control
vendored
Normal file
15
debian/control
vendored
Normal 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
3
debian/install
vendored
Normal 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
4
debian/postinst
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
ln -s /lib64/libaio.so.1 /lib64/libaio.so
|
||||
/sbin/ldconfig
|
11
debian/rules
vendored
Executable file
11
debian/rules
vendored
Executable 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
90
etc/init.d/maxscale
Normal file → Executable 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
145
etc/ubuntu/init.d/maxscale
Executable 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
|
12
makefile.inc
12
makefile.inc
@ -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
|
||||
|
@ -1,2 +1,2 @@
|
||||
/usr/local/sbin/MaxScale/modules
|
||||
/usr/local/sbin/lib
|
||||
/usr/local/skysql/maxscale/modules
|
||||
/usr/local/skysql/maxscale/lib
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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"))
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 */
|
||||
|
@ -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 *);
|
||||
|
@ -38,7 +38,8 @@ typedef enum {
|
||||
MODULE_IN_DEVELOPMENT = 0,
|
||||
MODULE_ALPHA_RELEASE,
|
||||
MODULE_BETA_RELEASE,
|
||||
MODULE_GA
|
||||
MODULE_GA,
|
||||
MODULE_EXPERIMENTAL
|
||||
} MODULE_STATUS;
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 *);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
467
server/modules/filter/tee.c
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
||||
|
578
server/modules/filter/topfilter.c
Normal file
578
server/modules/filter/topfilter.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
server/modules/include/maxscaled.h
Normal file
46
server/modules/include/maxscaled.h
Normal 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
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
381
server/modules/protocol/maxscaled.c
Normal file
381
server/modules/protocol/maxscaled.c
Normal 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
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
297
server/modules/routing/cli.c
Normal file
297
server/modules/routing/cli.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
@ -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
|
||||
|
@ -0,0 +1,4 @@
|
||||
use test;
|
||||
set autocommit=1;
|
||||
use mysql;
|
||||
select count(*) from user where user='maxuser'
|
@ -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
|
||||
|
@ -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];
|
||||
|
Loading…
x
Reference in New Issue
Block a user