brand release-1.0beta-refresh merged
brand release-1.0beta-refresh merged
This commit is contained in:
@ -13,7 +13,7 @@ threads=1
|
||||
# Define a monitor that can be used to determine the state and role of
|
||||
# the servers.
|
||||
#
|
||||
# Valid options are:
|
||||
# Valid options for all monitors are:
|
||||
#
|
||||
# module=<name of module to load>
|
||||
# servers=<server name>,<server name>,...
|
||||
@ -29,6 +29,16 @@ module=mysqlmon
|
||||
servers=server1,server2,server3
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
#
|
||||
# options for mysql_monitor only
|
||||
#
|
||||
# detect_replication_lag=<enable detection of replication slaves lag
|
||||
# via replication_heartbeat table,
|
||||
# default value is 0>
|
||||
# detect_stale_master=<if the replication is stopped or misconfigured
|
||||
# the previous detected master will be still available
|
||||
# until monitor or MaxSclale restart,
|
||||
# default value is 0>
|
||||
|
||||
# A series of service definition
|
||||
#
|
||||
@ -41,7 +51,8 @@ passwd=maxpwd
|
||||
# enable_root_user=<0 or 1, default is 0>
|
||||
# version_string=<specific string for server handshake,
|
||||
# default is the MariaDB embedded library version>
|
||||
#
|
||||
#
|
||||
# use_sql_variables_in=[master|all] (default all)
|
||||
# router_options=<option[=value]>,<option[=value]>,...
|
||||
# where value=[master|slave|synced]
|
||||
#
|
||||
@ -60,6 +71,7 @@ router=readwritesplit
|
||||
servers=server1,server2,server3
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
use_sql_variables_in=all
|
||||
max_slave_connections=50%
|
||||
max_slave_replication_lag=30
|
||||
router_options=slave_selection_criteria=LEAST_BEHIND_MASTER
|
||||
|
@ -33,7 +33,9 @@
|
||||
# 29/06/13 Vilho Raatikka Reverted Query classifier changes because
|
||||
# gateway needs mysql client lib, not qc.
|
||||
# 24/07/13 Mark Ridoch Addition of encryption routines
|
||||
# 30/05/14 Mark Ridoch Filter API added
|
||||
# 30/05/14 Mark Riddoch Filter API added
|
||||
# 25/07/14 Mark Riddoch Addition of hints
|
||||
# 29/08/14 Mark Riddoch Added housekeeper
|
||||
|
||||
include ../../build_gateway.inc
|
||||
|
||||
@ -47,17 +49,23 @@ 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 \
|
||||
monitor.c adminusers.c secrets.c filter.c modutil.c
|
||||
monitor.c adminusers.c secrets.c filter.c modutil.c hint.c housekeeper.c
|
||||
|
||||
HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.h \
|
||||
../include/gw.h ../modules/include/mysql_client_server_protocol.h \
|
||||
@ -65,18 +73,13 @@ HDRS= ../include/atomic.h ../include/buffer.h ../include/dcb.h \
|
||||
../include/modules.h ../include/poll.h ../include/config.h \
|
||||
../include/users.h ../include/hashtable.h ../include/gwbitmask.h \
|
||||
../include/adminusers.h ../include/version.h ../include/maxscale.h \
|
||||
../include/filter.h modutil.h
|
||||
../include/filter.h ../include/modutil.h ../hint.h ../include/housekeeper.h
|
||||
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
|
||||
KOBJS=maxkeys.o secrets.o utils.o
|
||||
POBJS=maxpasswd.o secrets.o utils.o
|
||||
|
||||
LIBS=-L$(EMBEDDED_LIB) \
|
||||
-lmysqld \
|
||||
-lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \
|
||||
-L../inih/extra -linih -lssl -lstdc++
|
||||
|
||||
all: maxscale maxkeys maxpasswd
|
||||
|
||||
cleantests:
|
||||
|
@ -32,6 +32,9 @@
|
||||
* 11/07/13 Mark Riddoch Add reference count mechanism
|
||||
* 16/07/2013 Massimiliano Pinto Added command type to gwbuf struct
|
||||
* 24/06/2014 Mark Riddoch Addition of gwbuf_trim
|
||||
* 15/07/2014 Mark Riddoch Addition of properties
|
||||
* 28/08/2014 Mark Riddoch Adition of tail pointer to speed
|
||||
* the gwbuf_append process
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -40,6 +43,11 @@
|
||||
#include <atomic.h>
|
||||
#include <skygw_debug.h>
|
||||
|
||||
static buffer_object_t* gwbuf_remove_buffer_object(
|
||||
GWBUF* buf,
|
||||
buffer_object_t* bufobj);
|
||||
|
||||
|
||||
/**
|
||||
* Allocate a new gateway buffer structure of size bytes.
|
||||
*
|
||||
@ -77,13 +85,18 @@ SHARED_BUF *sbuf;
|
||||
free(sbuf);
|
||||
return NULL;
|
||||
}
|
||||
spinlock_init(&rval->gwbuf_lock);
|
||||
rval->start = sbuf->data;
|
||||
rval->end = rval->start + size;
|
||||
sbuf->refcount = 1;
|
||||
rval->sbuf = sbuf;
|
||||
rval->next = NULL;
|
||||
rval->tail = rval;
|
||||
rval->hint = NULL;
|
||||
rval->properties = NULL;
|
||||
rval->gwbuf_type = GWBUF_TYPE_UNDEFINED;
|
||||
rval->command = 0;
|
||||
rval->gwbuf_info = GWBUF_INFO_NONE;
|
||||
rval->gwbuf_bufobj = NULL;
|
||||
CHK_GWBUF(rval);
|
||||
return rval;
|
||||
}
|
||||
@ -96,12 +109,38 @@ SHARED_BUF *sbuf;
|
||||
void
|
||||
gwbuf_free(GWBUF *buf)
|
||||
{
|
||||
BUF_PROPERTY *prop;
|
||||
|
||||
buffer_object_t* bo;
|
||||
|
||||
CHK_GWBUF(buf);
|
||||
if (atomic_add(&buf->sbuf->refcount, -1) == 1)
|
||||
{
|
||||
free(buf->sbuf->data);
|
||||
free(buf->sbuf);
|
||||
bo = buf->gwbuf_bufobj;
|
||||
|
||||
while (bo != NULL)
|
||||
{
|
||||
bo = gwbuf_remove_buffer_object(buf, bo);
|
||||
}
|
||||
|
||||
}
|
||||
while (buf->properties)
|
||||
{
|
||||
prop = buf->properties;
|
||||
buf->properties = prop->next;
|
||||
free(prop->name);
|
||||
free(prop->value);
|
||||
free(prop);
|
||||
}
|
||||
/** Release the hint */
|
||||
while (buf->hint)
|
||||
{
|
||||
HINT* h = buf->hint;
|
||||
buf->hint = buf->hint->next;
|
||||
hint_free(h);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
@ -130,7 +169,12 @@ GWBUF *rval;
|
||||
rval->start = buf->start;
|
||||
rval->end = buf->end;
|
||||
rval->gwbuf_type = buf->gwbuf_type;
|
||||
rval->properties = NULL;
|
||||
rval->hint = NULL;
|
||||
rval->gwbuf_info = buf->gwbuf_info;
|
||||
rval->gwbuf_bufobj = buf->gwbuf_bufobj;
|
||||
rval->next = NULL;
|
||||
rval->tail = rval;
|
||||
CHK_GWBUF(rval);
|
||||
return rval;
|
||||
}
|
||||
@ -156,7 +200,12 @@ GWBUF *gwbuf_clone_portion(
|
||||
clonebuf->start = (void *)((char*)buf->start)+start_offset;
|
||||
clonebuf->end = (void *)((char *)clonebuf->start)+length;
|
||||
clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone the type for now */
|
||||
clonebuf->properties = NULL;
|
||||
clonebuf->hint = NULL;
|
||||
clonebuf->gwbuf_info = buf->gwbuf_info;
|
||||
clonebuf->gwbuf_bufobj = buf->gwbuf_bufobj;
|
||||
clonebuf->next = NULL;
|
||||
clonebuf->tail = clonebuf;
|
||||
CHK_GWBUF(clonebuf);
|
||||
return clonebuf;
|
||||
|
||||
@ -233,11 +282,8 @@ GWBUF *ptr = head;
|
||||
if (!head)
|
||||
return tail;
|
||||
CHK_GWBUF(head);
|
||||
while (ptr->next)
|
||||
{
|
||||
ptr = ptr->next;
|
||||
}
|
||||
ptr->next = tail;
|
||||
head->tail->next = tail;
|
||||
head->tail = tail->tail;
|
||||
|
||||
return head;
|
||||
}
|
||||
@ -262,6 +308,7 @@ GWBUF *
|
||||
gwbuf_consume(GWBUF *head, unsigned int length)
|
||||
{
|
||||
GWBUF *rval = head;
|
||||
|
||||
CHK_GWBUF(head);
|
||||
GWBUF_CONSUME(head, length);
|
||||
CHK_GWBUF(head);
|
||||
@ -269,8 +316,13 @@ GWBUF *rval = head;
|
||||
if (GWBUF_EMPTY(head))
|
||||
{
|
||||
rval = head->next;
|
||||
if (head->next)
|
||||
head->next->tail = head->tail;
|
||||
|
||||
gwbuf_free(head);
|
||||
}
|
||||
|
||||
ss_dassert(rval == NULL || (rval->end > rval->start));
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -302,6 +354,8 @@ int rval = 0;
|
||||
* buffer has n_bytes or less then it will be freed and
|
||||
* NULL will be returned.
|
||||
*
|
||||
* This routine assumes the buffer is not part of a chain
|
||||
*
|
||||
* @param buf The buffer to trim
|
||||
* @param n_bytes The number of bytes to trim off
|
||||
* @return The buffer chain or NULL if buffer has <= n_bytes
|
||||
@ -309,6 +363,8 @@ int rval = 0;
|
||||
GWBUF *
|
||||
gwbuf_trim(GWBUF *buf, unsigned int n_bytes)
|
||||
{
|
||||
ss_dassert(buf->next == NULL);
|
||||
|
||||
if (GWBUF_LENGTH(buf) <= n_bytes)
|
||||
{
|
||||
gwbuf_consume(buf, GWBUF_LENGTH(buf));
|
||||
@ -338,5 +394,192 @@ void gwbuf_set_type(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a buffer object to GWBUF buffer.
|
||||
*
|
||||
* @param buf GWBUF where object is added
|
||||
* @param id Type identifier for object
|
||||
* @param data Object data
|
||||
* @param donefun_dp Clean-up function to be executed before buffer is freed.
|
||||
*/
|
||||
void gwbuf_add_buffer_object(
|
||||
GWBUF* buf,
|
||||
bufobj_id_t id,
|
||||
void* data,
|
||||
void (*donefun_fp)(void *))
|
||||
{
|
||||
buffer_object_t** p_b;
|
||||
buffer_object_t* newb;
|
||||
|
||||
CHK_GWBUF(buf);
|
||||
newb = (buffer_object_t *)malloc(sizeof(buffer_object_t));
|
||||
newb->bo_id = id;
|
||||
newb->bo_data = data;
|
||||
newb->bo_donefun_fp = donefun_fp;
|
||||
newb->bo_next = NULL;
|
||||
/** Lock */
|
||||
spinlock_acquire(&buf->gwbuf_lock);
|
||||
p_b = &buf->gwbuf_bufobj;
|
||||
/** Search the end of the list and add there */
|
||||
while (*p_b != NULL)
|
||||
{
|
||||
p_b = &(*p_b)->bo_next;
|
||||
}
|
||||
*p_b = newb;
|
||||
/** Set flag */
|
||||
buf->gwbuf_info |= GWBUF_INFO_PARSED;
|
||||
/** Unlock */
|
||||
spinlock_release(&buf->gwbuf_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search buffer object which matches with the id.
|
||||
*
|
||||
* @param buf GWBUF to be searched
|
||||
* @param id Identifier for the object
|
||||
*
|
||||
* @return Searched buffer object or NULL if not found
|
||||
*/
|
||||
void* gwbuf_get_buffer_object_data(
|
||||
GWBUF* buf,
|
||||
bufobj_id_t id)
|
||||
{
|
||||
buffer_object_t* bo;
|
||||
|
||||
CHK_GWBUF(buf);
|
||||
/** Lock */
|
||||
spinlock_acquire(&buf->gwbuf_lock);
|
||||
bo = buf->gwbuf_bufobj;
|
||||
|
||||
while (bo != NULL && bo->bo_id != id)
|
||||
{
|
||||
bo = bo->bo_next;
|
||||
}
|
||||
/** Unlock */
|
||||
spinlock_release(&buf->gwbuf_lock);
|
||||
|
||||
return bo->bo_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return pointer to next buffer object or NULL
|
||||
*/
|
||||
static buffer_object_t* gwbuf_remove_buffer_object(
|
||||
GWBUF* buf,
|
||||
buffer_object_t* bufobj)
|
||||
{
|
||||
buffer_object_t* next;
|
||||
|
||||
next = bufobj->bo_next;
|
||||
/** Call corresponding clean-up function to clean buffer object's data */
|
||||
bufobj->bo_donefun_fp(bufobj->bo_data);
|
||||
free(bufobj);
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add a property to a buffer.
|
||||
*
|
||||
* @param buf The buffer to add the property to
|
||||
* @param name The property name
|
||||
* @param value The property value
|
||||
* @return Non-zero on success
|
||||
*/
|
||||
int
|
||||
gwbuf_add_property(GWBUF *buf, char *name, char *value)
|
||||
{
|
||||
BUF_PROPERTY *prop;
|
||||
|
||||
if ((prop = malloc(sizeof(BUF_PROPERTY))) == NULL)
|
||||
return 0;
|
||||
|
||||
prop->name = strdup(name);
|
||||
prop->value = strdup(value);
|
||||
spinlock_acquire(&buf->gwbuf_lock);
|
||||
prop->next = buf->properties;
|
||||
buf->properties = prop;
|
||||
spinlock_release(&buf->gwbuf_lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of a buffer property
|
||||
* @param buf The buffer itself
|
||||
* @param name The name of the property to return
|
||||
* @return The property value or NULL if the property was not found.
|
||||
*/
|
||||
char *
|
||||
gwbuf_get_property(GWBUF *buf, char *name)
|
||||
{
|
||||
BUF_PROPERTY *prop;
|
||||
|
||||
spinlock_acquire(&buf->gwbuf_lock);
|
||||
prop = buf->properties;
|
||||
while (prop && strcmp(prop->name, name) != 0)
|
||||
prop = prop->next;
|
||||
spinlock_release(&buf->gwbuf_lock);
|
||||
if (prop)
|
||||
return prop->value;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a chain of GWBUF structures into a single GWBUF structure
|
||||
*
|
||||
* @param orig The chain to convert
|
||||
* @return The contiguous buffer
|
||||
*/
|
||||
GWBUF *
|
||||
gwbuf_make_contiguous(GWBUF *orig)
|
||||
{
|
||||
GWBUF *newbuf;
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
if (orig->next == NULL)
|
||||
return orig;
|
||||
|
||||
if ((newbuf = gwbuf_alloc(gwbuf_length(orig))) != NULL)
|
||||
{
|
||||
ptr = GWBUF_DATA(newbuf);
|
||||
while (orig)
|
||||
{
|
||||
len = GWBUF_LENGTH(orig);
|
||||
memcpy(ptr, GWBUF_DATA(orig), len);
|
||||
ptr += len;
|
||||
orig = gwbuf_consume(orig, len);
|
||||
}
|
||||
}
|
||||
return newbuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hint to a buffer.
|
||||
*
|
||||
* @param buf The buffer to add the hint to
|
||||
* @param hint The hint itself
|
||||
* @return Non-zero on success
|
||||
*/
|
||||
int
|
||||
gwbuf_add_hint(GWBUF *buf, HINT *hint)
|
||||
{
|
||||
HINT *ptr;
|
||||
|
||||
spinlock_acquire(&buf->gwbuf_lock);
|
||||
if (buf->hint)
|
||||
{
|
||||
ptr = buf->hint;
|
||||
while (ptr->next)
|
||||
ptr = ptr->next;
|
||||
ptr->next = hint;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf->hint = hint;
|
||||
}
|
||||
spinlock_release(&buf->gwbuf_lock);
|
||||
return 1;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
* 29/05/14 Mark Riddoch Addition of filter definition
|
||||
* 23/05/14 Massimiliano Pinto Added automatic set of maxscale-id: first listening ipv4_raw + port + pid
|
||||
* 28/05/14 Massimiliano Pinto Added detect_replication_lag parameter
|
||||
* 28/08/14 Massimiliano Pinto Added detect_stale_master parameter
|
||||
* 09/09/14 Massimiliano Pinto Added localhost_match_any parameter
|
||||
*
|
||||
* @endverbatim
|
||||
@ -41,6 +42,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <ini.h>
|
||||
#include <config.h>
|
||||
#include <service.h>
|
||||
@ -264,18 +266,28 @@ int error_count = 0;
|
||||
{
|
||||
char* max_slave_conn_str;
|
||||
char* max_slave_rlag_str;
|
||||
char *user;
|
||||
char *auth;
|
||||
char *enable_root_user;
|
||||
char *weightby;
|
||||
char *version_string;
|
||||
bool is_rwsplit = false;
|
||||
|
||||
obj->element = service_alloc(obj->object, router);
|
||||
char *user =
|
||||
config_get_value(obj->parameters, "user");
|
||||
char *auth =
|
||||
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");
|
||||
user = config_get_value(obj->parameters, "user");
|
||||
auth = config_get_value(obj->parameters, "passwd");
|
||||
enable_root_user = config_get_value(
|
||||
obj->parameters,
|
||||
"enable_root_user");
|
||||
weightby = config_get_value(obj->parameters, "weightby");
|
||||
|
||||
char *version_string = config_get_value(obj->parameters, "version_string");
|
||||
version_string = config_get_value(obj->parameters,
|
||||
"version_string");
|
||||
/** flag for rwsplit-specific parameters */
|
||||
if (strncmp(router, "readwritesplit", strlen("readwritesplit")+1) == 0)
|
||||
{
|
||||
is_rwsplit = true;
|
||||
}
|
||||
|
||||
char *allow_localhost_match_any =
|
||||
config_get_value(obj->parameters, "localhost_match_any");
|
||||
@ -347,13 +359,20 @@ int error_count = 0;
|
||||
param = config_get_param(obj->parameters,
|
||||
"max_slave_connections");
|
||||
|
||||
succp = service_set_param_value(
|
||||
obj->element,
|
||||
param,
|
||||
max_slave_conn_str,
|
||||
COUNT_ATMOST,
|
||||
(COUNT_TYPE|PERCENT_TYPE));
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = service_set_param_value(
|
||||
obj->element,
|
||||
param,
|
||||
max_slave_conn_str,
|
||||
COUNT_ATMOST,
|
||||
(COUNT_TYPE|PERCENT_TYPE));
|
||||
}
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
@ -379,13 +398,20 @@ int error_count = 0;
|
||||
obj->parameters,
|
||||
"max_slave_replication_lag");
|
||||
|
||||
succp = service_set_param_value(
|
||||
obj->element,
|
||||
param,
|
||||
max_slave_rlag_str,
|
||||
COUNT_ATMOST,
|
||||
COUNT_TYPE);
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = service_set_param_value(
|
||||
obj->element,
|
||||
param,
|
||||
max_slave_rlag_str,
|
||||
COUNT_ATMOST,
|
||||
COUNT_TYPE);
|
||||
}
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
@ -399,7 +425,51 @@ int error_count = 0;
|
||||
param->value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Parameters for rwsplit router only */
|
||||
if (is_rwsplit)
|
||||
{
|
||||
CONFIG_PARAMETER* param;
|
||||
char* use_sql_variables_in;
|
||||
bool succp;
|
||||
|
||||
use_sql_variables_in =
|
||||
config_get_value(obj->parameters,
|
||||
"use_sql_variables_in");
|
||||
|
||||
if (use_sql_variables_in != NULL)
|
||||
{
|
||||
param = config_get_param(
|
||||
obj->parameters,
|
||||
"use_sql_variables_in");
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = service_set_param_value(obj->element,
|
||||
param,
|
||||
use_sql_variables_in,
|
||||
COUNT_NONE,
|
||||
SQLVAR_TARGET_TYPE);
|
||||
}
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"* Warning : invalid value type "
|
||||
"for parameter \'%s.%s = %s\'\n\tExpected "
|
||||
"type is [master|all] for "
|
||||
"use sql variables in.",
|
||||
((SERVICE*)obj->element)->name,
|
||||
param->name,
|
||||
param->value)));
|
||||
}
|
||||
}
|
||||
} /*< if (rw_split) */
|
||||
} /*< if (router) */
|
||||
else
|
||||
{
|
||||
obj->element = NULL;
|
||||
@ -555,17 +625,29 @@ int error_count = 0;
|
||||
while (s)
|
||||
{
|
||||
CONFIG_CONTEXT *obj1 = context;
|
||||
int found = 0;
|
||||
while (obj1)
|
||||
{
|
||||
if (strcmp(trim(s), obj1->object) == 0 &&
|
||||
obj->element && obj1->element)
|
||||
{
|
||||
found = 1;
|
||||
serviceAddBackend(
|
||||
obj->element,
|
||||
obj1->element);
|
||||
}
|
||||
obj1 = obj1->next;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Unable to find "
|
||||
"server '%s' that is "
|
||||
"configured as part of "
|
||||
"service '%s'.",
|
||||
s, obj->object)));
|
||||
}
|
||||
s = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
@ -573,7 +655,7 @@ int error_count = 0;
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : The service '%s' is missing a "
|
||||
"Warning: The service '%s' is missing a "
|
||||
"definition of the servers that provide "
|
||||
"the service.",
|
||||
obj->object)));
|
||||
@ -676,6 +758,7 @@ int error_count = 0;
|
||||
char *passwd;
|
||||
unsigned long interval = 0;
|
||||
int replication_heartbeat = 0;
|
||||
int detect_stale_master = 0;
|
||||
|
||||
module = config_get_value(obj->parameters, "module");
|
||||
servers = config_get_value(obj->parameters, "servers");
|
||||
@ -689,6 +772,10 @@ int error_count = 0;
|
||||
replication_heartbeat = atoi(config_get_value(obj->parameters, "detect_replication_lag"));
|
||||
}
|
||||
|
||||
if (config_get_value(obj->parameters, "detect_stale_master")) {
|
||||
detect_stale_master = atoi(config_get_value(obj->parameters, "detect_stale_master"));
|
||||
}
|
||||
|
||||
if (module)
|
||||
{
|
||||
obj->element = monitor_alloc(obj->object, module);
|
||||
@ -712,22 +799,38 @@ int error_count = 0;
|
||||
if(replication_heartbeat == 1)
|
||||
monitorSetReplicationHeartbeat(obj->element, replication_heartbeat);
|
||||
|
||||
/* detect stale master */
|
||||
if(detect_stale_master == 1)
|
||||
monitorDetectStaleMaster(obj->element, detect_stale_master);
|
||||
|
||||
/* get the servers to monitor */
|
||||
s = strtok(servers, ",");
|
||||
while (s)
|
||||
{
|
||||
CONFIG_CONTEXT *obj1 = context;
|
||||
int found = 0;
|
||||
while (obj1)
|
||||
{
|
||||
if (strcmp(s, obj1->object) == 0 &&
|
||||
obj->element && obj1->element)
|
||||
{
|
||||
found = 1;
|
||||
monitorAddServer(
|
||||
obj->element,
|
||||
obj1->element);
|
||||
}
|
||||
obj1 = obj1->next;
|
||||
}
|
||||
if (!found)
|
||||
LOGIF(LE,
|
||||
(skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Unable to find "
|
||||
"server '%s' that is "
|
||||
"configured in the "
|
||||
"monitor '%s'.",
|
||||
s, obj->object)));
|
||||
|
||||
s = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
@ -829,12 +932,15 @@ config_param_type_t config_get_paramtype(
|
||||
return param->qfd_param_type;
|
||||
}
|
||||
|
||||
int config_get_valint(
|
||||
bool config_get_valint(
|
||||
int* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name, /*< if NULL examine current param only */
|
||||
config_param_type_t ptype)
|
||||
{
|
||||
int val = -1; /*< -1 indicates failure */
|
||||
{
|
||||
bool succp = false;;
|
||||
|
||||
ss_dassert((ptype == COUNT_TYPE || ptype == PERCENT_TYPE) && param != NULL);
|
||||
|
||||
while (param)
|
||||
{
|
||||
@ -842,32 +948,95 @@ int config_get_valint(
|
||||
{
|
||||
switch (ptype) {
|
||||
case COUNT_TYPE:
|
||||
val = param->qfd.valcount;
|
||||
goto return_val;
|
||||
*val = param->qfd.valcount;
|
||||
succp = true;
|
||||
goto return_succp;
|
||||
|
||||
case PERCENT_TYPE:
|
||||
val = param->qfd.valpercent;
|
||||
goto return_val;
|
||||
|
||||
case BOOL_TYPE:
|
||||
val = param->qfd.valbool;
|
||||
goto return_val;
|
||||
|
||||
default:
|
||||
goto return_val;
|
||||
*val = param->qfd.valpercent;
|
||||
succp =true;
|
||||
goto return_succp;
|
||||
|
||||
default:
|
||||
goto return_succp;
|
||||
}
|
||||
}
|
||||
else if (name == NULL)
|
||||
{
|
||||
goto return_val;
|
||||
}
|
||||
param = param->next;
|
||||
}
|
||||
return_val:
|
||||
return val;
|
||||
return_succp:
|
||||
return succp;
|
||||
}
|
||||
|
||||
|
||||
bool config_get_valbool(
|
||||
bool* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name,
|
||||
config_param_type_t ptype)
|
||||
{
|
||||
bool succp;
|
||||
|
||||
ss_dassert(ptype == BOOL_TYPE);
|
||||
ss_dassert(param != NULL);
|
||||
|
||||
if (ptype != BOOL_TYPE || param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
while (param)
|
||||
{
|
||||
if (name == NULL || !strncmp(param->name, name, MAX_PARAM_LEN))
|
||||
{
|
||||
*val = param->qfd.valbool;
|
||||
succp = true;
|
||||
goto return_succp;
|
||||
}
|
||||
param = param->next;
|
||||
}
|
||||
succp = false;
|
||||
|
||||
return_succp:
|
||||
return succp;
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool config_get_valtarget(
|
||||
target_t* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name,
|
||||
config_param_type_t ptype)
|
||||
{
|
||||
bool succp;
|
||||
|
||||
ss_dassert(ptype == SQLVAR_TARGET_TYPE);
|
||||
ss_dassert(param != NULL);
|
||||
|
||||
if (ptype != SQLVAR_TARGET_TYPE || param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
while (param)
|
||||
{
|
||||
if (name == NULL || !strncmp(param->name, name, MAX_PARAM_LEN))
|
||||
{
|
||||
*val = param->qfd.valtarget;
|
||||
succp = true;
|
||||
goto return_succp;
|
||||
}
|
||||
param = param->next;
|
||||
}
|
||||
succp = false;
|
||||
|
||||
return_succp:
|
||||
return succp;
|
||||
|
||||
}
|
||||
|
||||
CONFIG_PARAMETER* config_clone_param(
|
||||
CONFIG_PARAMETER* param)
|
||||
{
|
||||
@ -932,6 +1101,15 @@ config_threadcount()
|
||||
return gateway.n_threads;
|
||||
}
|
||||
|
||||
static struct {
|
||||
char *logname;
|
||||
logfile_id_t logfile;
|
||||
} lognames[] = {
|
||||
{ "log_messages", LOGFILE_MESSAGE },
|
||||
{ "log_trace", LOGFILE_TRACE },
|
||||
{ "log_debug", LOGFILE_DEBUG },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
/**
|
||||
* Configuration handler for items in the global [MaxScale] section
|
||||
*
|
||||
@ -942,10 +1120,20 @@ config_threadcount()
|
||||
static int
|
||||
handle_global_item(const char *name, const char *value)
|
||||
{
|
||||
int i;
|
||||
if (strcmp(name, "threads") == 0) {
|
||||
gateway.n_threads = atoi(value);
|
||||
} else {
|
||||
return 0;
|
||||
for (i = 0; lognames[i].logname; i++)
|
||||
{
|
||||
if (strcasecmp(name, lognames[i].logname) == 0)
|
||||
{
|
||||
if (atoi(value))
|
||||
skygw_log_enable(lognames[i].logfile);
|
||||
else
|
||||
skygw_log_disable(lognames[i].logfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@ -1053,13 +1241,20 @@ SERVER *server;
|
||||
param = config_get_param(obj->parameters,
|
||||
"max_slave_connections");
|
||||
|
||||
succp = service_set_param_value(
|
||||
service,
|
||||
param,
|
||||
max_slave_conn_str,
|
||||
COUNT_ATMOST,
|
||||
(PERCENT_TYPE|COUNT_TYPE));
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = service_set_param_value(
|
||||
service,
|
||||
param,
|
||||
max_slave_conn_str,
|
||||
COUNT_ATMOST,
|
||||
(PERCENT_TYPE|COUNT_TYPE));
|
||||
}
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
@ -1089,13 +1284,20 @@ SERVER *server;
|
||||
obj->parameters,
|
||||
"max_slave_replication_lag");
|
||||
|
||||
succp = service_set_param_value(
|
||||
service,
|
||||
param,
|
||||
max_slave_rlag_str,
|
||||
COUNT_ATMOST,
|
||||
COUNT_TYPE);
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = service_set_param_value(
|
||||
service,
|
||||
param,
|
||||
max_slave_rlag_str,
|
||||
COUNT_ATMOST,
|
||||
COUNT_TYPE);
|
||||
}
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
@ -1237,11 +1439,13 @@ SERVER *server;
|
||||
while (s)
|
||||
{
|
||||
CONFIG_CONTEXT *obj1 = context;
|
||||
int found = 0;
|
||||
while (obj1)
|
||||
{
|
||||
if (strcmp(s, obj1->object) == 0 &&
|
||||
obj->element && obj1->element)
|
||||
{
|
||||
found = 1;
|
||||
if (!serviceHasBackend(obj->element, obj1->element))
|
||||
{
|
||||
serviceAddBackend(
|
||||
@ -1251,6 +1455,16 @@ SERVER *server;
|
||||
}
|
||||
obj1 = obj1->next;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Unable to find "
|
||||
"server '%s' that is "
|
||||
"configured as part of "
|
||||
"service '%s'.",
|
||||
s, obj->object)));
|
||||
}
|
||||
s = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
@ -1352,6 +1566,7 @@ static char *service_params[] =
|
||||
"localhost_match_any",
|
||||
"max_slave_connections",
|
||||
"max_slave_replication_lag",
|
||||
"use_sql_variables_in", /*< rwsplit only */
|
||||
"version_string",
|
||||
"filters",
|
||||
NULL
|
||||
@ -1388,6 +1603,7 @@ static char *monitor_params[] =
|
||||
"passwd",
|
||||
"monitor_interval",
|
||||
"detect_replication_lag",
|
||||
"detect_stale_master",
|
||||
NULL
|
||||
};
|
||||
/**
|
||||
@ -1474,7 +1690,11 @@ bool config_set_qualified_param(
|
||||
param->qfd.valbool = *(bool *)val;
|
||||
succp = true;
|
||||
break;
|
||||
|
||||
|
||||
case SQLVAR_TARGET_TYPE:
|
||||
param->qfd.valtarget = *(target_t *)val;
|
||||
succp = true;
|
||||
break;
|
||||
default:
|
||||
succp = false;
|
||||
break;
|
||||
|
@ -187,14 +187,6 @@ getUsers(SERVICE *service, struct users *users)
|
||||
if (service_user == NULL || service_passwd == NULL)
|
||||
return -1;
|
||||
|
||||
/** multi-thread environment requires that thread init succeeds. */
|
||||
if (mysql_thread_init()) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : mysql_thread_init failed.")));
|
||||
return -1;
|
||||
}
|
||||
|
||||
con = mysql_init(NULL);
|
||||
|
||||
if (con == NULL) {
|
||||
@ -388,10 +380,9 @@ getUsers(SERVICE *service, struct users *users)
|
||||
memcpy(users->cksum, hash, SHA_DIGEST_LENGTH);
|
||||
|
||||
free(users_data);
|
||||
|
||||
free(key.user);
|
||||
mysql_free_result(result);
|
||||
mysql_close(con);
|
||||
mysql_thread_end();
|
||||
|
||||
return total_users;
|
||||
}
|
||||
|
@ -89,7 +89,13 @@ 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);
|
||||
|
||||
DCB* dcb_get_zombies(void)
|
||||
/**
|
||||
* Return the pointer to the lsit of zombie DCB's
|
||||
*
|
||||
* @return Zombies DCB list
|
||||
*/
|
||||
DCB *
|
||||
dcb_get_zombies(void)
|
||||
{
|
||||
return zombies;
|
||||
}
|
||||
@ -128,6 +134,12 @@ DCB *rval;
|
||||
spinlock_init(&rval->delayqlock);
|
||||
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;
|
||||
@ -376,11 +388,6 @@ DCB_CALLBACK *cb;
|
||||
}
|
||||
spinlock_release(&dcb->cb_lock);
|
||||
|
||||
if (dcb->dcb_readqueue)
|
||||
{
|
||||
GWBUF* queue = dcb->dcb_readqueue;
|
||||
while ((queue = gwbuf_consume(queue, GWBUF_LENGTH(queue))) != NULL);
|
||||
}
|
||||
bitmask_free(&dcb->memdata.bitmask);
|
||||
simple_mutex_done(&dcb->dcb_read_lock);
|
||||
simple_mutex_done(&dcb->dcb_write_lock);
|
||||
@ -399,7 +406,7 @@ DCB_CALLBACK *cb;
|
||||
*
|
||||
* @param threadid The thread ID of the caller
|
||||
*/
|
||||
DCB*
|
||||
DCB *
|
||||
dcb_process_zombies(int threadid)
|
||||
{
|
||||
DCB *ptr, *lptr;
|
||||
@ -815,8 +822,6 @@ int below_water;
|
||||
|
||||
spinlock_acquire(&dcb->writeqlock);
|
||||
|
||||
ss_dassert(dcb->state != DCB_STATE_ZOMBIE);
|
||||
|
||||
if (dcb->writeq != NULL)
|
||||
{
|
||||
/*
|
||||
@ -1187,7 +1192,7 @@ printDCB(DCB *dcb)
|
||||
if (dcb->remote)
|
||||
printf("\tConnected to: %s\n", dcb->remote);
|
||||
if (dcb->user)
|
||||
printf("\tUsername to: %s\n", 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");
|
||||
@ -1204,6 +1209,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
|
||||
@ -1233,6 +1251,12 @@ void dprintAllDCBs(DCB *pdcb)
|
||||
DCB *dcb;
|
||||
|
||||
spinlock_acquire(&dcbspin);
|
||||
#if SPINLOCK_PROFILE
|
||||
dcb_printf(pdcb, "DCB List Spinlock Statistics:\n");
|
||||
spinlock_stats(&dcbspin, spin_reporter, pdcb);
|
||||
dcb_printf(pdcb, "Zombie Queue Lock Statistics:\n");
|
||||
spinlock_stats(&zombiespin, spin_reporter, pdcb);
|
||||
#endif
|
||||
dcb = allDCBs;
|
||||
while (dcb)
|
||||
{
|
||||
@ -1252,12 +1276,16 @@ DCB *dcb;
|
||||
dcb_printf(pdcb, "\tQueued write data: %d\n",
|
||||
gwbuf_length(dcb->writeq));
|
||||
dcb_printf(pdcb, "\tStatistics:\n");
|
||||
dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads);
|
||||
dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes);
|
||||
dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered);
|
||||
dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts);
|
||||
dcb_printf(pdcb, "\t\tNo. of High Water Events: %d\n", dcb->stats.n_high_water);
|
||||
dcb_printf(pdcb, "\t\tNo. of Low Water Events: %d\n", dcb->stats.n_low_water);
|
||||
dcb_printf(pdcb, "\t\tNo. of Reads: %d\n", dcb->stats.n_reads);
|
||||
dcb_printf(pdcb, "\t\tNo. of Writes: %d\n", dcb->stats.n_writes);
|
||||
dcb_printf(pdcb, "\t\tNo. of Buffered Writes: %d\n", dcb->stats.n_buffered);
|
||||
dcb_printf(pdcb, "\t\tNo. of Accepts: %d\n", dcb->stats.n_accepts);
|
||||
dcb_printf(pdcb, "\t\tNo. of 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");
|
||||
dcb = dcb->next;
|
||||
@ -1278,20 +1306,20 @@ DCB *dcb;
|
||||
spinlock_acquire(&dcbspin);
|
||||
dcb = allDCBs;
|
||||
dcb_printf(pdcb, "Descriptor Control Blocks\n");
|
||||
dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n");
|
||||
dcb_printf(pdcb, " %-10s | %-26s | %-20s | %s\n",
|
||||
dcb_printf(pdcb, "------------------+----------------------------+--------------------+----------\n");
|
||||
dcb_printf(pdcb, " %-16s | %-26s | %-18s | %s\n",
|
||||
"DCB", "State", "Service", "Remote");
|
||||
dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n");
|
||||
dcb_printf(pdcb, "------------------+----------------------------+--------------------+----------\n");
|
||||
while (dcb)
|
||||
{
|
||||
dcb_printf(pdcb, " %10p | %-26s | %-20s | %s\n",
|
||||
dcb_printf(pdcb, " %-16p | %-26s | %-18s | %s\n",
|
||||
dcb, gw_dcb_state2string(dcb->state),
|
||||
(dcb->session->service ?
|
||||
dcb->session->service->name : ""),
|
||||
|
||||
((dcb->session && dcb->session->service) ? dcb->session->service->name : ""),
|
||||
(dcb->remote ? dcb->remote : ""));
|
||||
dcb = dcb->next;
|
||||
}
|
||||
dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n\n");
|
||||
dcb_printf(pdcb, "------------------+----------------------------+--------------------+----------\n\n");
|
||||
spinlock_release(&dcbspin);
|
||||
}
|
||||
|
||||
@ -1308,16 +1336,16 @@ 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",
|
||||
dcb_printf(pdcb, "-----------------+------------------+----------------------+------------\n");
|
||||
dcb_printf(pdcb, " %-15s | %-16s | %-20s | %s\n",
|
||||
"Client", "DCB", "Service", "Session");
|
||||
dcb_printf(pdcb, "-----------------+------------+----------------------+------------\n");
|
||||
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_printf(pdcb, " %-15s | %16p | %-20s | %10p\n",
|
||||
(dcb->remote ? dcb->remote : ""),
|
||||
dcb, (dcb->session->service ?
|
||||
dcb->session->service->name : ""),
|
||||
@ -1325,7 +1353,7 @@ DCB *dcb;
|
||||
}
|
||||
dcb = dcb->next;
|
||||
}
|
||||
dcb_printf(pdcb, "-----------------+------------+----------------------+------------\n\n");
|
||||
dcb_printf(pdcb, "-----------------+------------------+----------------------+------------\n\n");
|
||||
spinlock_release(&dcbspin);
|
||||
}
|
||||
|
||||
@ -1342,16 +1370,18 @@ 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_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_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));
|
||||
if (dcb->delayq)
|
||||
dcb_printf(pdcb, "\tDelayed write data: %d\n", gwbuf_length(dcb->delayq));
|
||||
dcb_printf(pdcb, "\tStatistics:\n");
|
||||
dcb_printf(pdcb, "\t\tNo. of Reads: %d\n",
|
||||
dcb->stats.n_reads);
|
||||
@ -1361,12 +1391,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_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
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1719,10 +1767,7 @@ int gw_write(
|
||||
* @return Non-zero (true) if the callback was added
|
||||
*/
|
||||
int
|
||||
dcb_add_callback(
|
||||
DCB *dcb,
|
||||
DCB_REASON reason,
|
||||
int (*callback)(struct dcb *, DCB_REASON, void *), void *userdata)
|
||||
dcb_add_callback(DCB *dcb, DCB_REASON reason, int (*callback)(struct dcb *, DCB_REASON, void *), void *userdata)
|
||||
{
|
||||
DCB_CALLBACK *cb, *ptr;
|
||||
int rval = 1;
|
||||
@ -1754,7 +1799,10 @@ int rval = 1;
|
||||
return 0;
|
||||
}
|
||||
if (cb->next == NULL)
|
||||
{
|
||||
cb->next = ptr;
|
||||
break;
|
||||
}
|
||||
cb = cb->next;
|
||||
}
|
||||
spinlock_release(&dcb->cb_lock);
|
||||
@ -1775,7 +1823,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;
|
||||
@ -1868,8 +1916,102 @@ int rval = 0;
|
||||
return rval;
|
||||
}
|
||||
|
||||
static DCB* dcb_get_next (
|
||||
DCB* dcb)
|
||||
/**
|
||||
* Called by the EPOLLIN event. Take care of calling the protocol
|
||||
* 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
|
||||
dcb_pollin(DCB *dcb, int thread_id)
|
||||
{
|
||||
|
||||
spinlock_acquire(&dcb->pollinlock);
|
||||
if (dcb->pollinbusy == 0)
|
||||
{
|
||||
dcb->pollinbusy = 1;
|
||||
do {
|
||||
if (dcb->readcheck)
|
||||
{
|
||||
dcb->stats.n_readrechecks++;
|
||||
dcb_process_zombies(thread_id);
|
||||
}
|
||||
dcb->readcheck = 0;
|
||||
spinlock_release(&dcb->pollinlock);
|
||||
dcb->func.read(dcb);
|
||||
spinlock_acquire(&dcb->pollinlock);
|
||||
} while (dcb->readcheck);
|
||||
dcb->pollinbusy = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb->stats.n_busypolls++;
|
||||
dcb->readcheck = 1;
|
||||
}
|
||||
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, int thread_id)
|
||||
{
|
||||
|
||||
spinlock_acquire(&dcb->polloutlock);
|
||||
if (dcb->polloutbusy == 0)
|
||||
{
|
||||
dcb->polloutbusy = 1;
|
||||
do {
|
||||
if (dcb->writecheck)
|
||||
{
|
||||
dcb_process_zombies(thread_id);
|
||||
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;
|
||||
|
||||
@ -1903,8 +2045,13 @@ static DCB* dcb_get_next (
|
||||
return dcb;
|
||||
}
|
||||
|
||||
void dcb_call_foreach (
|
||||
DCB_REASON reason)
|
||||
/**
|
||||
* 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:
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include <modules.h>
|
||||
#include <config.h>
|
||||
#include <poll.h>
|
||||
#include <housekeeper.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
@ -1492,11 +1493,16 @@ int main(int argc, char **argv)
|
||||
/* Init MaxScale poll system */
|
||||
poll_init();
|
||||
|
||||
/*<
|
||||
* Start the services that were created above
|
||||
*/
|
||||
/**
|
||||
* Init mysql thread context for main thread as well. Needed when users
|
||||
* are queried from backends.
|
||||
*/
|
||||
mysql_thread_init();
|
||||
|
||||
/** Start the services that were created above */
|
||||
n_services = serviceStartAll();
|
||||
if (n_services == 0)
|
||||
|
||||
if (n_services == 0)
|
||||
{
|
||||
char* logerr = "Failed to start any MaxScale services. Exiting.";
|
||||
print_log_n_stderr(true, !daemon_mode, logerr, logerr, 0);
|
||||
@ -1510,6 +1516,12 @@ int main(int argc, char **argv)
|
||||
log_flush_thr = thread_start(
|
||||
log_flush_cb,
|
||||
(void *)&log_flush_timeout_ms);
|
||||
|
||||
/*
|
||||
* Start the housekeeper thread
|
||||
*/
|
||||
hkinit();
|
||||
|
||||
/*<
|
||||
* Start the polling threads, note this is one less than is
|
||||
* configured as the main thread will also poll.
|
||||
@ -1549,9 +1561,13 @@ int main(int argc, char **argv)
|
||||
|
||||
/*< Stop all the monitors */
|
||||
monitorStopAll();
|
||||
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"MaxScale is shutting down.")));
|
||||
/** Release mysql thread context*/
|
||||
mysql_thread_end();
|
||||
|
||||
datadir_cleanup();
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
|
@ -28,7 +28,7 @@
|
||||
* and value and to free them.
|
||||
*
|
||||
* The hashtable is arrange as a set of linked lists, the number of linked
|
||||
* lists beign the hashsize as requested by the user. Entries are hashed by
|
||||
* lists being the hashsize as requested by the user. Entries are hashed by
|
||||
* calling the hash function that is passed in by the user, this is used as
|
||||
* an index into the array of linked lists, usign modulo hashsize.
|
||||
*
|
||||
@ -63,6 +63,10 @@ static void hashtable_read_lock(HASHTABLE *table);
|
||||
static void hashtable_read_unlock(HASHTABLE *table);
|
||||
static void hashtable_write_lock(HASHTABLE *table);
|
||||
static void hashtable_write_unlock(HASHTABLE *table);
|
||||
static HASHTABLE *hashtable_alloc_real(HASHTABLE* target,
|
||||
int size,
|
||||
int (*hashfn)(),
|
||||
int (*cmpfn)());
|
||||
|
||||
/**
|
||||
* Special null function used as default memory allfunctions in the hashtable
|
||||
@ -93,16 +97,44 @@ nullfn(void *data)
|
||||
HASHTABLE *
|
||||
hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)())
|
||||
{
|
||||
HASHTABLE *rval;
|
||||
return hashtable_alloc_real(NULL, size, hashfn, cmpfn);
|
||||
}
|
||||
|
||||
if ((rval = malloc(sizeof(HASHTABLE))) == NULL)
|
||||
return NULL;
|
||||
HASHTABLE* hashtable_alloc_flat(
|
||||
HASHTABLE* target,
|
||||
int size,
|
||||
int (*hashfn)(),
|
||||
int (*cmpfn)())
|
||||
{
|
||||
return hashtable_alloc_real(target, size, hashfn, cmpfn);
|
||||
}
|
||||
|
||||
static HASHTABLE *
|
||||
hashtable_alloc_real(
|
||||
HASHTABLE* target,
|
||||
int size,
|
||||
int (*hashfn)(),
|
||||
int (*cmpfn)())
|
||||
{
|
||||
HASHTABLE *rval;
|
||||
|
||||
if (target == NULL)
|
||||
{
|
||||
if ((rval = malloc(sizeof(HASHTABLE))) == NULL)
|
||||
return NULL;
|
||||
rval->ht_isflat = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = target;
|
||||
rval->ht_isflat = true;
|
||||
}
|
||||
|
||||
#if defined(SS_DEBUG)
|
||||
rval->ht_chk_top = CHK_NUM_HASHTABLE;
|
||||
rval->ht_chk_tail = CHK_NUM_HASHTABLE;
|
||||
rval->ht_chk_top = CHK_NUM_HASHTABLE;
|
||||
rval->ht_chk_tail = CHK_NUM_HASHTABLE;
|
||||
#endif
|
||||
rval->hashsize = size;
|
||||
rval->hashsize = size > 0 ? size : 1;
|
||||
rval->hashfn = hashfn;
|
||||
rval->cmpfn = cmpfn;
|
||||
rval->kcopyfn = nullfn;
|
||||
@ -112,12 +144,12 @@ HASHTABLE *rval;
|
||||
rval->n_readers = 0;
|
||||
rval->writelock = 0;
|
||||
spinlock_init(&rval->spin);
|
||||
if ((rval->entries = (HASHENTRIES **)calloc(size, sizeof(HASHENTRIES *))) == NULL)
|
||||
if ((rval->entries = (HASHENTRIES **)calloc(rval->hashsize, sizeof(HASHENTRIES *))) == NULL)
|
||||
{
|
||||
free(rval);
|
||||
return NULL;
|
||||
}
|
||||
memset(rval->entries, 0, size * sizeof(HASHENTRIES *));
|
||||
memset(rval->entries, 0, rval->hashsize * sizeof(HASHENTRIES *));
|
||||
|
||||
return rval;
|
||||
}
|
||||
@ -147,7 +179,11 @@ HASHENTRIES *entry, *ptr;
|
||||
}
|
||||
}
|
||||
free(table->entries);
|
||||
free(table);
|
||||
|
||||
if (!table->ht_isflat)
|
||||
{
|
||||
free(table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
153
server/core/hint.c
Normal file
153
server/core/hint.c
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <hint.h>
|
||||
|
||||
/**
|
||||
* @file hint.c generic support routines for hints.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 25/07/14 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Duplicate a list of hints
|
||||
*
|
||||
* @param hint The hint list to duplicate
|
||||
* @return A duplicate of the list
|
||||
*/
|
||||
HINT *
|
||||
hint_dup(HINT *hint)
|
||||
{
|
||||
HINT *nlhead = NULL, *nltail = NULL, *ptr1, *ptr2;
|
||||
|
||||
ptr1 = hint;
|
||||
while (ptr1)
|
||||
{
|
||||
if ((ptr2 = (HINT *)malloc(sizeof(HINT))) == NULL)
|
||||
return nlhead;
|
||||
ptr2->type = ptr1->type;
|
||||
if (ptr1->data)
|
||||
ptr2->data = strdup(ptr1->data);
|
||||
else
|
||||
ptr2->data = NULL;
|
||||
if (ptr1->value)
|
||||
ptr2->value = strdup(ptr1->value);
|
||||
else
|
||||
ptr2->value = NULL;
|
||||
ptr2->next = NULL;
|
||||
if (nltail)
|
||||
{
|
||||
nltail->next = ptr2;
|
||||
nltail = ptr2;
|
||||
}
|
||||
else
|
||||
{
|
||||
nlhead = ptr2;
|
||||
nltail = ptr2;
|
||||
}
|
||||
ptr1 = ptr1->next;
|
||||
}
|
||||
return nlhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ROUTE TO type hint
|
||||
*
|
||||
* @param head The current hint list
|
||||
* @param type The HINT_TYPE
|
||||
* @param data Data may be NULL or the name of a server to route to
|
||||
* @return The result hint list
|
||||
*/
|
||||
HINT *
|
||||
hint_create_route(HINT *head, HINT_TYPE type, char *data)
|
||||
{
|
||||
HINT *hint;
|
||||
|
||||
if ((hint = (HINT *)malloc(sizeof(HINT))) == NULL)
|
||||
return head;
|
||||
hint->next = head;
|
||||
hint->type = type;
|
||||
if (data)
|
||||
hint->data = strdup(data);
|
||||
else
|
||||
hint->data = NULL;
|
||||
hint->value = NULL;
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create name/value parameter hint
|
||||
*
|
||||
* @param head The current hint list
|
||||
* @param pname The parameter name
|
||||
* @param value The parameter value
|
||||
* @return The result hint list
|
||||
*/
|
||||
HINT *
|
||||
hint_create_parameter(HINT *head, char *pname, char *value)
|
||||
{
|
||||
HINT *hint;
|
||||
|
||||
if ((hint = (HINT *)malloc(sizeof(HINT))) == NULL)
|
||||
return head;
|
||||
hint->next = head;
|
||||
hint->type = HINT_PARAMETER;
|
||||
hint->data = pname;
|
||||
hint->value = strdup(value);
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* free_hint - free a hint
|
||||
*
|
||||
* @param hint The hint to free
|
||||
*/
|
||||
void
|
||||
hint_free(HINT *hint)
|
||||
{
|
||||
if (hint->data)
|
||||
free(hint->data);
|
||||
if (hint->value)
|
||||
free(hint->value);
|
||||
free(hint);
|
||||
}
|
||||
|
||||
bool hint_exists(
|
||||
HINT** p_hint,
|
||||
HINT_TYPE type)
|
||||
{
|
||||
bool succp = false;
|
||||
|
||||
while (*p_hint != NULL)
|
||||
{
|
||||
if ((*p_hint)->type == type)
|
||||
{
|
||||
succp = true;
|
||||
}
|
||||
p_hint = &(*p_hint)->next;
|
||||
}
|
||||
return succp;
|
||||
}
|
195
server/core/housekeeper.c
Normal file
195
server/core/housekeeper.c
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* This file is distributed as part of the SkySQL Gateway. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <housekeeper.h>
|
||||
#include <thread.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
/**
|
||||
* @file housekeeper.c Provide a mechanism to run periodic tasks
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 29/08/14 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
/**
|
||||
* List of all tasks that need to be run
|
||||
*/
|
||||
static HKTASK *tasks = NULL;
|
||||
/**
|
||||
* Spinlock to protect the tasks list
|
||||
*/
|
||||
static SPINLOCK tasklock = SPINLOCK_INIT;
|
||||
|
||||
static void hkthread(void *);
|
||||
|
||||
/**
|
||||
* Initialise the housekeeper thread
|
||||
*/
|
||||
void
|
||||
hkinit()
|
||||
{
|
||||
thread_start(hkthread, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new task to the housekeepers lists of tasks that should be
|
||||
* run periodically.
|
||||
*
|
||||
* The task will be first run frequency seconds after this call is
|
||||
* made and will the be executed repeatedly every frequency seconds
|
||||
* until the task is removed.
|
||||
*
|
||||
* Task names must be unique.
|
||||
*
|
||||
* @param name The unique name for this housekeeper task
|
||||
* @param taskfn The function to call for the task
|
||||
* @param data Data to pass to the task function
|
||||
* @param frequency How often to run the task, expressed in seconds
|
||||
* @return Return the tiem in seconds when the task will be first run if the task was added, otherwise 0
|
||||
*/
|
||||
int
|
||||
hktask_add(char *name, void (*taskfn)(void *), void *data, int frequency)
|
||||
{
|
||||
HKTASK *task, *ptr;
|
||||
|
||||
if ((task = (HKTASK *)malloc(sizeof(HKTASK))) == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if ((task->name = strdup(name)) == NULL)
|
||||
{
|
||||
free(task);
|
||||
return 0;
|
||||
}
|
||||
task->task = taskfn;
|
||||
task->data = data;
|
||||
task->frequency = frequency;
|
||||
task->nextdue = time(0) + frequency;
|
||||
task->next = NULL;
|
||||
spinlock_acquire(&tasklock);
|
||||
ptr = tasks;
|
||||
while (ptr && ptr->next)
|
||||
{
|
||||
if (strcmp(ptr->name, name) == 0)
|
||||
{
|
||||
spinlock_release(&tasklock);
|
||||
free(task->name);
|
||||
free(task);
|
||||
return 0;
|
||||
}
|
||||
ptr = ptr->next;
|
||||
}
|
||||
if (ptr)
|
||||
ptr->next = task;
|
||||
else
|
||||
tasks = task;
|
||||
spinlock_release(&tasklock);
|
||||
|
||||
return task->nextdue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a named task from the housekeepers task list
|
||||
*
|
||||
* @param name The task name to remove
|
||||
* @return Returns 0 if the task could not be removed
|
||||
*/
|
||||
int
|
||||
hktask_remove(char *name)
|
||||
{
|
||||
HKTASK *ptr, *lptr = NULL;
|
||||
|
||||
spinlock_acquire(&tasklock);
|
||||
ptr = tasks;
|
||||
while (ptr && strcmp(ptr->name, name) != 0)
|
||||
{
|
||||
lptr = ptr;
|
||||
ptr = ptr->next;
|
||||
}
|
||||
if (ptr && lptr)
|
||||
lptr->next = ptr->next;
|
||||
else if (ptr)
|
||||
tasks = ptr->next;
|
||||
spinlock_release(&tasklock);
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
free(ptr->name);
|
||||
free(ptr);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The housekeeper thread implementation.
|
||||
*
|
||||
* This function is responsible for executing the housekeeper tasks.
|
||||
*
|
||||
* The implementation of the callng of the task functions is such that
|
||||
* the tasks are called without the tasklock spinlock being held. This
|
||||
* allows manipulation of the housekeeper task list during execution of
|
||||
* one of the tasks. The resutl is that upon completion of a task the
|
||||
* search for tasks to run must restart from the start of the queue.
|
||||
* It is vital that the task->nextdue tiem is updated before the task
|
||||
* is run.
|
||||
*
|
||||
* @param data Unused, here to satisfy the thread system
|
||||
*/
|
||||
void
|
||||
hkthread(void *data)
|
||||
{
|
||||
HKTASK *ptr;
|
||||
time_t now;
|
||||
void (*taskfn)(void *);
|
||||
void *taskdata;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
thread_millisleep(1000);
|
||||
now = time(0);
|
||||
spinlock_acquire(&tasklock);
|
||||
ptr = tasks;
|
||||
while (ptr)
|
||||
{
|
||||
if (ptr->nextdue <= now)
|
||||
{
|
||||
ptr->nextdue = now + ptr->frequency;
|
||||
taskfn = ptr->task;
|
||||
taskdata = ptr->data;
|
||||
spinlock_release(&tasklock);
|
||||
(*taskfn)(taskdata);
|
||||
spinlock_acquire(&tasklock);
|
||||
ptr = tasks;
|
||||
}
|
||||
else
|
||||
ptr = ptr->next;
|
||||
}
|
||||
spinlock_release(&tasklock);
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
*/
|
||||
#include <buffer.h>
|
||||
#include <string.h>
|
||||
#include <mysql_client_server_protocol.h>
|
||||
|
||||
/**
|
||||
* Check if a GWBUF structure is a MySQL COM_QUERY packet
|
||||
@ -78,7 +79,7 @@ unsigned char *ptr;
|
||||
*length += (*ptr++ << 8);
|
||||
ptr += 2; // Skip sequence id and COM_QUERY byte
|
||||
*length = *length - 1;
|
||||
*sql = (char *) ptr;
|
||||
*sql = (char *)ptr;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -171,3 +172,57 @@ GWBUF *addition;
|
||||
|
||||
return orig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy query string from GWBUF buffer to separate memory area.
|
||||
*
|
||||
* @param buf GWBUF buffer including the query
|
||||
*
|
||||
* @return Plaint text query if the packet type is COM_QUERY. Otherwise return
|
||||
* a string including the packet type.
|
||||
*/
|
||||
char* modutil_get_query(
|
||||
GWBUF* buf)
|
||||
{
|
||||
uint8_t* packet;
|
||||
mysql_server_cmd_t packet_type;
|
||||
size_t len;
|
||||
char* query_str;
|
||||
|
||||
packet = GWBUF_DATA(buf);
|
||||
packet_type = packet[4];
|
||||
|
||||
switch (packet_type) {
|
||||
case MYSQL_COM_QUIT:
|
||||
len = strlen("[Quit msg]")+1;
|
||||
if ((query_str = (char *)malloc(len+1)) == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
memcpy(query_str, "[Quit msg]", len);
|
||||
memset(&query_str[len], 0, 1);
|
||||
break;
|
||||
|
||||
case MYSQL_COM_QUERY:
|
||||
len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */
|
||||
if ((query_str = (char *)malloc(len+1)) == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
memcpy(query_str, &packet[5], len);
|
||||
memset(&query_str[len], 0, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
len = strlen(STRPACKETTYPE(packet_type))+1;
|
||||
if ((query_str = (char *)malloc(len+1)) == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
memcpy(query_str, STRPACKETTYPE(packet_type), len);
|
||||
memset(&query_str[len], 0, 1);
|
||||
break;
|
||||
} /*< switch */
|
||||
retblock:
|
||||
return query_str;
|
||||
}
|
@ -207,8 +207,7 @@ MONITOR *ptr;
|
||||
/**
|
||||
* Show a single monitor
|
||||
*
|
||||
* @param dcb DCB for printing output
|
||||
* @param monitor The monitor to print information regarding
|
||||
* @param dcb DCB for printing output
|
||||
*/
|
||||
void
|
||||
monitorShow(DCB *dcb, MONITOR *monitor)
|
||||
@ -304,12 +303,26 @@ monitorSetInterval (MONITOR *mon, unsigned long interval)
|
||||
* Enable Replication Heartbeat support in monitor.
|
||||
*
|
||||
* @param mon The monitor instance
|
||||
* @param replication_heartbeat The replication heartbeat
|
||||
* @param enable The enabling value is 1, 0 turns it off
|
||||
*/
|
||||
void
|
||||
monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat)
|
||||
monitorSetReplicationHeartbeat(MONITOR *mon, int enable)
|
||||
{
|
||||
if (mon->module->replicationHeartbeat != NULL) {
|
||||
mon->module->replicationHeartbeat(mon->handle, replication_heartbeat);
|
||||
mon->module->replicationHeartbeat(mon->handle, enable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Stale Master assignement.
|
||||
*
|
||||
* @param mon The monitor instance
|
||||
* @param enable The enabling value is 1, 0 turns it off
|
||||
*/
|
||||
void
|
||||
monitorDetectStaleMaster(MONITOR *mon, int enable)
|
||||
{
|
||||
if (mon->module->detectStaleMaster != NULL) {
|
||||
mon->module->detectStaleMaster(mon->handle, enable);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <gw.h>
|
||||
#include <config.h>
|
||||
#include <housekeeper.h>
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
@ -41,14 +43,63 @@ extern int lm_enabled_logfiles_bitmask;
|
||||
* 19/06/13 Mark Riddoch Initial implementation
|
||||
* 28/06/13 Mark Riddoch Added poll mask support and DCB
|
||||
* zombie management
|
||||
* 29/08/14 Mark Riddoch Addition of thread status data, load average
|
||||
* etc.
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
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 */
|
||||
|
||||
/**
|
||||
* Thread load average, this is the average number of descriptors in each
|
||||
* poll completion, a value of 1 or less is the ideal.
|
||||
*/
|
||||
static double load_average = 0.0;
|
||||
static int load_samples = 0;
|
||||
static int load_nfds = 0;
|
||||
static double current_avg = 0.0;
|
||||
static double *avg_samples = NULL;
|
||||
static int next_sample = 0;
|
||||
static int n_avg_samples;
|
||||
|
||||
/* Thread statistics data */
|
||||
static int n_threads; /*< No. of threads */
|
||||
|
||||
/**
|
||||
* Internal MaxScale thread states
|
||||
*/
|
||||
typedef enum { THREAD_STOPPED, THREAD_IDLE,
|
||||
THREAD_POLLING, THREAD_PROCESSING,
|
||||
THREAD_ZPROCESSING } THREAD_STATE;
|
||||
|
||||
/**
|
||||
* Thread data used to report the current state and activity related to
|
||||
* a thread
|
||||
*/
|
||||
typedef struct {
|
||||
THREAD_STATE state; /*< Current thread state */
|
||||
int n_fds; /*< No. of descriptors thread is processing */
|
||||
DCB *cur_dcb; /*< Current DCB being processed */
|
||||
uint32_t event; /*< Current event being processed */
|
||||
} THREAD_DATA;
|
||||
|
||||
static THREAD_DATA *thread_data = NULL; /*< Status of each thread */
|
||||
|
||||
/**
|
||||
* The number of buckets used to gather statistics about how many
|
||||
* descriptors where processed on each epoll completion.
|
||||
*
|
||||
* An array of wakeup counts is created, with the number of descriptors used
|
||||
* to index that array. Each time a completion occurs the n_fds - 1 value is
|
||||
* used to index this array and increment the count held there.
|
||||
* If n_fds - 1 >= MAXFDS then the count at MAXFDS -1 is incremented.
|
||||
*/
|
||||
#define MAXNFDS 10
|
||||
|
||||
/**
|
||||
* The polling statistics
|
||||
@ -60,8 +111,20 @@ 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;
|
||||
|
||||
/**
|
||||
* How frequently to call the poll_loadav function used to monitor the load
|
||||
* average of the poll subsystem.
|
||||
*/
|
||||
#define POLL_LOAD_FREQ 10
|
||||
/**
|
||||
* Periodic function to collect load data for average calculations
|
||||
*/
|
||||
static void poll_loadav(void *);
|
||||
|
||||
/**
|
||||
* Initialise the polling system we are using for the gateway.
|
||||
@ -71,6 +134,8 @@ static struct {
|
||||
void
|
||||
poll_init()
|
||||
{
|
||||
int i;
|
||||
|
||||
if (epoll_fd != -1)
|
||||
return;
|
||||
if ((epoll_fd = epoll_create(MAX_EVENTS)) == -1)
|
||||
@ -80,7 +145,23 @@ poll_init()
|
||||
}
|
||||
memset(&pollStats, 0, sizeof(pollStats));
|
||||
bitmask_init(&poll_mask);
|
||||
n_threads = config_threadcount();
|
||||
if ((thread_data =
|
||||
(THREAD_DATA *)malloc(n_threads * sizeof(THREAD_DATA))) != NULL)
|
||||
{
|
||||
for (i = 0; i < n_threads; i++)
|
||||
{
|
||||
thread_data[i].state = THREAD_STOPPED;
|
||||
}
|
||||
}
|
||||
simple_mutex_init(&epoll_wait_mutex, "epoll_wait_mutex");
|
||||
|
||||
hktask_add("Load Average", poll_loadav, NULL, POLL_LOAD_FREQ);
|
||||
n_avg_samples = 15 * 60 / POLL_LOAD_FREQ;
|
||||
avg_samples = (double *)malloc(sizeof(double *) * n_avg_samples);
|
||||
for (i = 0; i < n_avg_samples; i++)
|
||||
avg_samples[i] = 0.0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +181,7 @@ poll_add_dcb(DCB *dcb)
|
||||
|
||||
CHK_DCB(dcb);
|
||||
|
||||
ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
|
||||
ev.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLHUP | EPOLLET;
|
||||
ev.data.ptr = dcb;
|
||||
|
||||
/*<
|
||||
@ -245,20 +326,29 @@ 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 */
|
||||
/** Add this thread to the bitmask of running polling threads */
|
||||
bitmask_set(&poll_mask, thread_id);
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].state = THREAD_IDLE;
|
||||
}
|
||||
|
||||
/** Init mysql thread context for use with a mysql handle and a parser */
|
||||
mysql_thread_init();
|
||||
|
||||
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(
|
||||
@ -272,9 +362,14 @@ poll_waitevents(void *arg)
|
||||
#if 0
|
||||
simple_mutex_lock(&epoll_wait_mutex, TRUE);
|
||||
#endif
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].state = THREAD_POLLING;
|
||||
}
|
||||
|
||||
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 +383,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 +406,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
|
||||
@ -322,6 +425,20 @@ poll_waitevents(void *arg)
|
||||
pthread_self(),
|
||||
nfds)));
|
||||
atomic_add(&pollStats.n_polls, 1);
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].n_fds = nfds;
|
||||
thread_data[thread_id].cur_dcb = NULL;
|
||||
thread_data[thread_id].event = 0;
|
||||
thread_data[thread_id].state = THREAD_PROCESSING;
|
||||
}
|
||||
|
||||
pollStats.n_fds[(nfds < MAXNFDS ? (nfds - 1) : MAXNFDS - 1)]++;
|
||||
|
||||
load_average = (load_average * load_samples + nfds)
|
||||
/ (load_samples + 1);
|
||||
atomic_add(&load_samples, 1);
|
||||
atomic_add(&load_nfds, nfds);
|
||||
|
||||
for (i = 0; i < nfds; i++)
|
||||
{
|
||||
@ -329,6 +446,11 @@ poll_waitevents(void *arg)
|
||||
__uint32_t ev = events[i].events;
|
||||
|
||||
CHK_DCB(dcb);
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].cur_dcb = dcb;
|
||||
thread_data[thread_id].event = ev;
|
||||
}
|
||||
|
||||
#if defined(SS_DEBUG)
|
||||
if (dcb_fake_write_ev[dcb->fd] != 0) {
|
||||
@ -364,6 +486,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 +501,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, thread_id);
|
||||
#endif
|
||||
} else {
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
@ -393,11 +521,13 @@ poll_waitevents(void *arg)
|
||||
}
|
||||
if (ev & EPOLLIN)
|
||||
{
|
||||
#if MUTEX_BLOCK
|
||||
simple_mutex_lock(&dcb->dcb_read_lock,
|
||||
true);
|
||||
ss_info_dassert(!dcb->dcb_read_active,
|
||||
"Read already active");
|
||||
dcb->dcb_read_active = TRUE;
|
||||
#endif
|
||||
|
||||
if (dcb->state == DCB_STATE_LISTENING)
|
||||
{
|
||||
@ -421,11 +551,17 @@ poll_waitevents(void *arg)
|
||||
dcb,
|
||||
dcb->fd)));
|
||||
atomic_add(&pollStats.n_read, 1);
|
||||
#if MUTEX_BLOCK
|
||||
dcb->func.read(dcb);
|
||||
#else
|
||||
dcb_pollin(dcb, thread_id);
|
||||
#endif
|
||||
}
|
||||
#if MUTEX_BLOCK
|
||||
dcb->dcb_read_active = FALSE;
|
||||
simple_mutex_unlock(
|
||||
&dcb->dcb_read_lock);
|
||||
#endif
|
||||
}
|
||||
if (ev & EPOLLERR)
|
||||
{
|
||||
@ -475,10 +611,33 @@ poll_waitevents(void *arg)
|
||||
atomic_add(&pollStats.n_hup, 1);
|
||||
dcb->func.hangup(dcb);
|
||||
}
|
||||
|
||||
if (ev & EPOLLRDHUP)
|
||||
{
|
||||
int eno = 0;
|
||||
eno = gw_getsockerrno(dcb->fd);
|
||||
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [poll_waitevents] "
|
||||
"EPOLLRDHUP on dcb %p, fd %d. "
|
||||
"Errno %d, %s.",
|
||||
pthread_self(),
|
||||
dcb,
|
||||
dcb->fd,
|
||||
eno,
|
||||
strerror(eno))));
|
||||
atomic_add(&pollStats.n_hup, 1);
|
||||
dcb->func.hangup(dcb);
|
||||
}
|
||||
} /*< for */
|
||||
no_op = FALSE;
|
||||
}
|
||||
process_zombies:
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].state = THREAD_ZPROCESSING;
|
||||
}
|
||||
zombies = dcb_process_zombies(thread_id);
|
||||
|
||||
if (zombies == NULL) {
|
||||
@ -491,10 +650,20 @@ poll_waitevents(void *arg)
|
||||
* Remove the thread from the bitmask of running
|
||||
* polling threads.
|
||||
*/
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].state = THREAD_STOPPED;
|
||||
}
|
||||
bitmask_clear(&poll_mask, thread_id);
|
||||
return;
|
||||
}
|
||||
if (thread_data)
|
||||
{
|
||||
thread_data[thread_id].state = THREAD_IDLE;
|
||||
}
|
||||
} /*< while(1) */
|
||||
/** Release mysql thread context */
|
||||
mysql_thread_end();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -525,10 +694,194 @@ 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 descriptors\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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an EPOLL event mask into a printable string
|
||||
*
|
||||
* @param event The event mask
|
||||
* @return A string representation, the caller must free the string
|
||||
*/
|
||||
static char *
|
||||
event_to_string(uint32_t event)
|
||||
{
|
||||
char *str;
|
||||
|
||||
str = malloc(22); // 22 is max returned string length
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
*str = 0;
|
||||
if (event & EPOLLIN)
|
||||
{
|
||||
strcat(str, "IN");
|
||||
}
|
||||
if (event & EPOLLOUT)
|
||||
{
|
||||
if (*str)
|
||||
strcat(str, "|");
|
||||
strcat(str, "OUT");
|
||||
}
|
||||
if (event & EPOLLERR)
|
||||
{
|
||||
if (*str)
|
||||
strcat(str, "|");
|
||||
strcat(str, "ERR");
|
||||
}
|
||||
if (event & EPOLLHUP)
|
||||
{
|
||||
if (*str)
|
||||
strcat(str, "|");
|
||||
strcat(str, "HUP");
|
||||
}
|
||||
if (event & EPOLLRDHUP)
|
||||
{
|
||||
if (*str)
|
||||
strcat(str, "|");
|
||||
strcat(str, "RDHUP");
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the thread status for all the polling threads
|
||||
*
|
||||
* @param dcb The DCB to send the thread status data
|
||||
*/
|
||||
void
|
||||
dShowThreads(DCB *dcb)
|
||||
{
|
||||
int i, j, n;
|
||||
char *state;
|
||||
double avg1 = 0.0, avg5 = 0.0, avg15 = 0.0;
|
||||
|
||||
|
||||
dcb_printf(dcb, "Polling Threads.\n\n");
|
||||
dcb_printf(dcb, "Historic Thread Load Average: %.2f.\n", load_average);
|
||||
dcb_printf(dcb, "Current Thread Load Average: %.2f.\n", current_avg);
|
||||
|
||||
/* Average all the samples to get the 15 minute average */
|
||||
for (i = 0; i < n_avg_samples; i++)
|
||||
avg15 += avg_samples[i];
|
||||
avg15 = avg15 / n_avg_samples;
|
||||
|
||||
/* Average the last third of the samples to get the 5 minute average */
|
||||
n = 5 * 60 / POLL_LOAD_FREQ;
|
||||
i = next_sample - (n + 1);
|
||||
if (i < 0)
|
||||
i += n_avg_samples;
|
||||
for (j = i; j < i + n; j++)
|
||||
avg5 += avg_samples[j % n_avg_samples];
|
||||
avg5 = (3 * avg5) / (n_avg_samples);
|
||||
|
||||
/* Average the last 15th of the samples to get the 1 minute average */
|
||||
n = 60 / POLL_LOAD_FREQ;
|
||||
i = next_sample - (n + 1);
|
||||
if (i < 0)
|
||||
i += n_avg_samples;
|
||||
for (j = i; j < i + n; j++)
|
||||
avg1 += avg_samples[j % n_avg_samples];
|
||||
avg1 = (15 * avg1) / (n_avg_samples);
|
||||
|
||||
dcb_printf(dcb, "15 Minute Average: %.2f, 5 Minute Average: %.2f, "
|
||||
"1 Minute Average: %.2f\n\n", avg15, avg5, avg1);
|
||||
|
||||
if (thread_data == NULL)
|
||||
return;
|
||||
dcb_printf(dcb, " ID | State | # fds | Descriptor | Event\n");
|
||||
dcb_printf(dcb, "----+------------+--------+------------------+---------------\n");
|
||||
for (i = 0; i < n_threads; i++)
|
||||
{
|
||||
switch (thread_data[i].state)
|
||||
{
|
||||
case THREAD_STOPPED:
|
||||
state = "Stopped";
|
||||
break;
|
||||
case THREAD_IDLE:
|
||||
state = "Idle";
|
||||
break;
|
||||
case THREAD_POLLING:
|
||||
state = "Polling";
|
||||
break;
|
||||
case THREAD_PROCESSING:
|
||||
state = "Processing";
|
||||
break;
|
||||
case THREAD_ZPROCESSING:
|
||||
state = "Collecting";
|
||||
break;
|
||||
}
|
||||
if (thread_data[i].state != THREAD_PROCESSING)
|
||||
dcb_printf(dcb,
|
||||
" %2d | %-10s | | |\n",
|
||||
i, state);
|
||||
else if (thread_data[i].cur_dcb == NULL)
|
||||
dcb_printf(dcb,
|
||||
" %2d | %-10s | %6d | |\n",
|
||||
i, state, thread_data[i].n_fds);
|
||||
else
|
||||
{
|
||||
char *event_string
|
||||
= event_to_string(thread_data[i].event);
|
||||
if (event_string == NULL)
|
||||
event_string = "??";
|
||||
dcb_printf(dcb,
|
||||
" %2d | %-10s | %6d | %-16p | %s\n",
|
||||
i, state, thread_data[i].n_fds,
|
||||
thread_data[i].cur_dcb, event_string);
|
||||
free(event_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function used to calculate time based load data. This is called by the
|
||||
* housekeeper every POLL_LOAD_FREQ seconds.
|
||||
*
|
||||
* @param data Argument required by the housekeeper but not used here
|
||||
*/
|
||||
static void
|
||||
poll_loadav(void *data)
|
||||
{
|
||||
static int last_samples = 0, last_nfds = 0;
|
||||
int new_samples, new_nfds;
|
||||
|
||||
new_samples = load_samples - last_samples;
|
||||
new_nfds = load_nfds - last_nfds;
|
||||
last_samples = load_samples;
|
||||
last_nfds = load_nfds;
|
||||
|
||||
/* POLL_LOAD_FREQ average is... */
|
||||
if (new_samples)
|
||||
current_avg = new_nfds / new_samples;
|
||||
else
|
||||
current_avg = 0.0;
|
||||
avg_samples[next_sample] = current_avg;
|
||||
next_sample++;
|
||||
if (next_sample >= n_avg_samples)
|
||||
next_sample = 0;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
* 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
|
||||
* 30/08/14 Massimiliano Pinto Addition of new service status description
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -148,7 +149,8 @@ server_set_unique_name(SERVER *server, char *name)
|
||||
* Find an existing server using the unique section name in
|
||||
* configuration file
|
||||
*
|
||||
* @param name The Server name defined in the header file
|
||||
* @param servname The Server name or address
|
||||
* @param port The server port
|
||||
* @return The server or NULL if not found
|
||||
*/
|
||||
SERVER *
|
||||
@ -373,23 +375,23 @@ char *stat;
|
||||
if (ptr)
|
||||
{
|
||||
dcb_printf(dcb, "Servers.\n");
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n");
|
||||
dcb_printf(dcb, "%-18s | %-15s | Port | %-20s | Connections\n",
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n");
|
||||
dcb_printf(dcb, "%-18s | %-15s | Port | Connections | %-20s",
|
||||
"Server", "Address", "Status");
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n");
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n");
|
||||
}
|
||||
while (ptr)
|
||||
{
|
||||
stat = server_status(ptr);
|
||||
dcb_printf(dcb, "%-18s | %-15s | %5d | %-20s | %4d\n",
|
||||
dcb_printf(dcb, "%-18s | %-15s | %5d | %11d | %s\n",
|
||||
ptr->unique_name, ptr->name,
|
||||
ptr->port, stat,
|
||||
ptr->stats.n_current);
|
||||
ptr->port,
|
||||
ptr->stats.n_current, stat);
|
||||
free(stat);
|
||||
ptr = ptr->next;
|
||||
}
|
||||
if (allServers)
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+----------------------+------------\n\n");
|
||||
dcb_printf(dcb, "-------------------+-----------------+-------+-------------+--------------------\n");
|
||||
spinlock_release(&server_spin);
|
||||
}
|
||||
|
||||
@ -405,7 +407,7 @@ server_status(SERVER *server)
|
||||
{
|
||||
char *status = NULL;
|
||||
|
||||
if ((status = (char *)malloc(200)) == NULL)
|
||||
if ((status = (char *)malloc(256)) == NULL)
|
||||
return NULL;
|
||||
status[0] = 0;
|
||||
if (server->status & SERVER_MAINT)
|
||||
@ -416,6 +418,14 @@ char *status = NULL;
|
||||
strcat(status, "Slave, ");
|
||||
if (server->status & SERVER_JOINED)
|
||||
strcat(status, "Synced, ");
|
||||
if (server->status & SERVER_NDB)
|
||||
strcat(status, "NDB, ");
|
||||
if (server->status & SERVER_SLAVE_OF_EXTERNAL_MASTER)
|
||||
strcat(status, "Slave of External Server, ");
|
||||
if (server->status & SERVER_STALE_STATUS)
|
||||
strcat(status, "Stale Status, ");
|
||||
if (server->status & SERVER_AUTH_ERROR)
|
||||
strcat(status, "Auth Error, ");
|
||||
if (server->status & SERVER_RUNNING)
|
||||
strcat(status, "Running");
|
||||
else
|
||||
|
@ -56,9 +56,29 @@
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
/** To be used with configuration type checks */
|
||||
typedef struct typelib_st {
|
||||
int tl_nelems;
|
||||
const char* tl_name;
|
||||
const char** tl_p_elems;
|
||||
} typelib_t;
|
||||
/** Set of subsequent false,true pairs */
|
||||
static const char* bool_strings[11] = {"FALSE", "TRUE", "OFF", "ON", "N", "Y", "0", "1", "NO", "YES", 0};
|
||||
typelib_t bool_type = {array_nelems(bool_strings)-1, "bool_type", bool_strings};
|
||||
|
||||
/** List of valid values */
|
||||
static const char* sqlvar_target_strings[4] = {"MASTER", "ALL", 0};
|
||||
typelib_t sqlvar_target_type = {
|
||||
array_nelems(sqlvar_target_strings)-1,
|
||||
"sqlvar_target_type",
|
||||
sqlvar_target_strings
|
||||
};
|
||||
|
||||
static SPINLOCK service_spin = SPINLOCK_INIT;
|
||||
static SERVICE *allServices = NULL;
|
||||
|
||||
static int find_type(typelib_t* tl, const char* needle, int maxlen);
|
||||
|
||||
static void service_add_qualified_param(
|
||||
SERVICE* svc,
|
||||
CONFIG_PARAMETER* param);
|
||||
@ -677,6 +697,7 @@ int n = 0;
|
||||
"Unable to find filter '%s' for service '%s'\n",
|
||||
trim(ptr), service->name
|
||||
)));
|
||||
n--;
|
||||
}
|
||||
flist[n] = NULL;
|
||||
ptr = strtok_r(NULL, "|", &brkt);
|
||||
@ -1008,78 +1029,174 @@ bool service_set_param_value (
|
||||
count_spec_t count_spec,
|
||||
config_param_type_t type)
|
||||
{
|
||||
char* p;
|
||||
int valint;
|
||||
bool succp = true;
|
||||
|
||||
/**
|
||||
* Find out whether the value is numeric and ends with '%' or '\0'
|
||||
*/
|
||||
p = valstr;
|
||||
|
||||
while(isdigit(*p)) p++;
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (p == valstr || (*p != '%' && *p != '\0'))
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (*p == '%')
|
||||
{
|
||||
if (*(p+1) == '\0')
|
||||
{
|
||||
*p = '\0';
|
||||
valint = (int) strtol(valstr, (char **)NULL, 10);
|
||||
|
||||
if (valint == 0 && errno != 0)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (PARAM_IS_TYPE(type,PERCENT_TYPE))
|
||||
{
|
||||
succp = true;
|
||||
config_set_qualified_param(param, (void *)&valint, PERCENT_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Log error */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
}
|
||||
else if (*p == '\0')
|
||||
{
|
||||
valint = (int) strtol(valstr, (char **)NULL, 10);
|
||||
char* p;
|
||||
int valint;
|
||||
bool valbool;
|
||||
target_t valtarget;
|
||||
bool succp = true;
|
||||
|
||||
if (valint == 0 && errno != 0)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (PARAM_IS_TYPE(type,COUNT_TYPE))
|
||||
{
|
||||
succp = true;
|
||||
config_set_qualified_param(param, (void *)&valint, COUNT_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Log error */
|
||||
}
|
||||
}
|
||||
|
||||
if (PARAM_IS_TYPE(type,PERCENT_TYPE) ||PARAM_IS_TYPE(type,COUNT_TYPE))
|
||||
{
|
||||
/**
|
||||
* Find out whether the value is numeric and ends with '%' or '\0'
|
||||
*/
|
||||
p = valstr;
|
||||
|
||||
while(isdigit(*p)) p++;
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (p == valstr || (*p != '%' && *p != '\0'))
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (*p == '%')
|
||||
{
|
||||
if (*(p+1) == '\0')
|
||||
{
|
||||
*p = '\0';
|
||||
valint = (int) strtol(valstr, (char **)NULL, 10);
|
||||
|
||||
if (valint == 0 && errno != 0)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (PARAM_IS_TYPE(type,PERCENT_TYPE))
|
||||
{
|
||||
succp = true;
|
||||
config_set_qualified_param(param, (void *)&valint, PERCENT_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Log error */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
}
|
||||
else if (*p == '\0')
|
||||
{
|
||||
valint = (int) strtol(valstr, (char **)NULL, 10);
|
||||
|
||||
if (valint == 0 && errno != 0)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
else if (PARAM_IS_TYPE(type,COUNT_TYPE))
|
||||
{
|
||||
succp = true;
|
||||
config_set_qualified_param(param, (void *)&valint, COUNT_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Log error */
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == BOOL_TYPE)
|
||||
{
|
||||
unsigned int rc;
|
||||
|
||||
rc = find_type(&bool_type, valstr, strlen(valstr)+1);
|
||||
|
||||
if (rc > 0)
|
||||
{
|
||||
succp = true;
|
||||
if (rc%2 == 1)
|
||||
{
|
||||
valbool = false;
|
||||
}
|
||||
else if (rc%2 == 0)
|
||||
{
|
||||
valbool = true;
|
||||
}
|
||||
/** add param to config */
|
||||
config_set_qualified_param(param,
|
||||
(void *)&valbool,
|
||||
BOOL_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
}
|
||||
else if (type == SQLVAR_TARGET_TYPE)
|
||||
{
|
||||
unsigned int rc;
|
||||
|
||||
rc = find_type(&sqlvar_target_type, valstr, strlen(valstr)+1);
|
||||
|
||||
if (rc > 0 && rc < 3)
|
||||
{
|
||||
succp = true;
|
||||
if (rc == 1)
|
||||
{
|
||||
valtarget = TYPE_MASTER;
|
||||
}
|
||||
else if (rc == 2)
|
||||
{
|
||||
valtarget = TYPE_ALL;
|
||||
}
|
||||
/** add param to config */
|
||||
config_set_qualified_param(param,
|
||||
(void *)&valtarget,
|
||||
SQLVAR_TARGET_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (succp)
|
||||
{
|
||||
service_add_qualified_param(service, param); /*< add param to svc */
|
||||
service_add_qualified_param(service, param); /*< add param to svc */
|
||||
}
|
||||
return succp;
|
||||
}
|
||||
/*
|
||||
* Function to find a string in typelib_t
|
||||
* (similar to find_type() of mysys/typelib.c)
|
||||
*
|
||||
* SYNOPSIS
|
||||
* find_type()
|
||||
* lib typelib_t
|
||||
* find String to find
|
||||
* length Length of string to find
|
||||
* part_match Allow part matching of value
|
||||
*
|
||||
* RETURN
|
||||
* 0 error
|
||||
* > 0 position in TYPELIB->type_names +1
|
||||
*/
|
||||
|
||||
static int find_type(
|
||||
typelib_t* tl,
|
||||
const char* needle,
|
||||
int maxlen)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (tl == NULL || needle == NULL || maxlen <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i=0; i<tl->tl_nelems; i++)
|
||||
{
|
||||
if (strncasecmp(tl->tl_p_elems[i], needle, maxlen) == 0)
|
||||
{
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add qualified config parameter to SERVICE struct.
|
||||
*/
|
||||
*/
|
||||
static void service_add_qualified_param(
|
||||
SERVICE* svc,
|
||||
CONFIG_PARAMETER* param)
|
||||
|
@ -694,6 +694,7 @@ int i;
|
||||
return 0;
|
||||
}
|
||||
session->tail = *tail;
|
||||
free(tail);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -13,13 +13,13 @@ TESTLOG := $(shell pwd)/testcore.log
|
||||
LOGPATH := $(ROOT_PATH)/log_manager
|
||||
UTILSPATH := $(ROOT_PATH)/utils
|
||||
|
||||
LDFLAGS=-rdynamic -L$(LOGPATH) \
|
||||
LDFLAGS=-rdynamic -L$(LOGPATH) -L$(EMBEDDED_LIB) \
|
||||
-Wl,-rpath,$(DEST)/lib \
|
||||
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \
|
||||
-Wl,-rpath,$(EMBEDDED_LIB)
|
||||
|
||||
LIBS= -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \
|
||||
-L../../inih/extra -linih -lssl -lstdc++
|
||||
-L../../inih/extra -linih -lssl -lstdc++ -lmysqld
|
||||
|
||||
TESTS=testhash testspinlock testfilter testadminusers
|
||||
|
||||
|
@ -1,19 +1,73 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 18/08-2014 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "../../include/hashtable.h"
|
||||
|
||||
static void
|
||||
read_lock(HASHTABLE *table)
|
||||
{
|
||||
spinlock_acquire(&table->spin);
|
||||
while (table->writelock)
|
||||
{
|
||||
spinlock_release(&table->spin);
|
||||
while (table->writelock)
|
||||
;
|
||||
spinlock_acquire(&table->spin);
|
||||
}
|
||||
table->n_readers++;
|
||||
spinlock_release(&table->spin);
|
||||
}
|
||||
|
||||
static void
|
||||
read_unlock(HASHTABLE *table)
|
||||
{
|
||||
atomic_add(&table->n_readers, -1);
|
||||
}
|
||||
|
||||
static int hfun(void* key);
|
||||
static int cmpfun (void *, void *);
|
||||
|
||||
static int hfun(
|
||||
void* key)
|
||||
{
|
||||
return *(int *)key;
|
||||
int *i = (int *)key;
|
||||
int j = (*i * 23) + 41;
|
||||
return j;
|
||||
/* return *(int *)key; */
|
||||
}
|
||||
|
||||
|
||||
static int cmpfun(
|
||||
void* v1,
|
||||
void* v2)
|
||||
@ -27,7 +81,19 @@ static int cmpfun(
|
||||
return (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0));
|
||||
}
|
||||
|
||||
static double start;
|
||||
|
||||
/**
|
||||
* test1 spinlock_acquire_nowait tests
|
||||
*
|
||||
* Test that spinlock_acquire_nowait returns false if the spinlock
|
||||
* is already taken.
|
||||
*
|
||||
* Test that spinlock_acquire_nowait returns true if the spinlock
|
||||
* is not taken.
|
||||
*
|
||||
* Test that spinlock_acquire_nowait does hold the spinlock.
|
||||
*/
|
||||
static bool do_hashtest(
|
||||
int argelems,
|
||||
int argsize)
|
||||
@ -39,12 +105,14 @@ static bool do_hashtest(
|
||||
int* val_arr;
|
||||
int hsize;
|
||||
int longest;
|
||||
int* iter;
|
||||
|
||||
ss_dfprintf(stderr,
|
||||
"testhash : creating hash table of size %d, including %d "
|
||||
"elements in total.",
|
||||
"elements in total, at time %g.",
|
||||
argsize,
|
||||
argelems);
|
||||
argelems,
|
||||
(double)clock()-start);
|
||||
|
||||
val_arr = (int *)malloc(sizeof(void *)*argelems);
|
||||
|
||||
@ -56,17 +124,33 @@ static bool do_hashtest(
|
||||
val_arr[i] = i;
|
||||
hashtable_add(h, (void *)&val_arr[i], (void *)&val_arr[i]);
|
||||
}
|
||||
|
||||
if (argelems > 1000) ss_dfprintf(stderr, "\t..done\nOperation took %g", (double)clock()-start);
|
||||
|
||||
ss_dfprintf(stderr, "\t..done\nRead hash table statistics.");
|
||||
|
||||
hashtable_get_stats((void *)h, &hsize, &nelems, &longest);
|
||||
|
||||
ss_dfprintf(stderr, "\t..done\nValidate read values.");
|
||||
|
||||
ss_info_dassert(hsize == argsize, "Invalid hash size");
|
||||
ss_info_dassert(hsize == (argsize > 0 ? argsize: 1), "Invalid hash size");
|
||||
ss_info_dassert((nelems == argelems) || (nelems == 0 && argsize == 0),
|
||||
"Invalid element count");
|
||||
ss_info_dassert(longest <= nelems, "Too large longest list value");
|
||||
if (argelems > 1000) ss_dfprintf(stderr, "\t..done\nOperation took %g", (double)clock()-start);
|
||||
|
||||
ss_dfprintf(stderr, "\t..done\nValidate iterator.");
|
||||
|
||||
HASHITERATOR *iterator = hashtable_iterator(h);
|
||||
read_lock(h);
|
||||
for (i=0; i < (argelems+1); i++) {
|
||||
iter = (int *)hashtable_next(iterator);
|
||||
if (iter == NULL) break;
|
||||
if (argelems < 100) ss_dfprintf(stderr, "\nNext item, iter = %d, i = %d", *iter, i);
|
||||
}
|
||||
read_unlock(h);
|
||||
ss_info_dassert((i == argelems) || (i == 0 && argsize == 0), "\nIncorrect number of elements from iterator");
|
||||
hashtable_iterator_free(iterator);
|
||||
if (argelems > 1000) ss_dfprintf(stderr, "\t..done\nOperation took %g", (double)clock()-start);
|
||||
|
||||
ss_dfprintf(stderr, "\t\t..done\n\nTest completed successfully.\n\n");
|
||||
|
||||
@ -91,11 +175,13 @@ return_succp:
|
||||
int main(void)
|
||||
{
|
||||
int rc = 1;
|
||||
start = (double) clock();
|
||||
|
||||
if (!do_hashtest(0, 1)) goto return_rc;
|
||||
if (!do_hashtest(10, 1)) goto return_rc;
|
||||
if (!do_hashtest(1000, 10)) goto return_rc;
|
||||
if (!do_hashtest(10, 0)) goto return_rc;
|
||||
if (!do_hashtest(10, -5)) goto return_rc;
|
||||
if (!do_hashtest(1500, 17)) goto return_rc;
|
||||
if (!do_hashtest(1, 1)) goto return_rc;
|
||||
if (!do_hashtest(10000, 133)) goto return_rc;
|
||||
|
@ -55,18 +55,18 @@ SPINLOCK lck;
|
||||
spinlock_acquire(&lck);
|
||||
if (spinlock_acquire_nowait(&lck))
|
||||
{
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 1 failed.\n");
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 1.1 failed.\n");
|
||||
return 1;
|
||||
}
|
||||
spinlock_release(&lck);
|
||||
if (!spinlock_acquire_nowait(&lck))
|
||||
{
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 2 failed.\n");
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 1.2 failed.\n");
|
||||
return 1;
|
||||
}
|
||||
if (spinlock_acquire_nowait(&lck))
|
||||
{
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 3 failed.\n");
|
||||
fprintf(stderr, "spinlock_acquire_nowait: test 1.3 failed.\n");
|
||||
return 1;
|
||||
}
|
||||
spinlock_release(&lck);
|
||||
@ -89,6 +89,8 @@ unsigned long t1 = time(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* test2 spinlock_acquire tests
|
||||
*
|
||||
* Check that spinlock correctly blocks another thread whilst the spinlock
|
||||
* is held.
|
||||
*
|
||||
@ -114,7 +116,7 @@ void *handle;
|
||||
|
||||
if (acquire_time < 8)
|
||||
{
|
||||
fprintf(stderr, "spinlock: test 1 failed.\n");
|
||||
fprintf(stderr, "spinlock: test 2 failed.\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -38,12 +38,30 @@
|
||||
* 10/06/2013 Mark Riddoch Initial implementation
|
||||
* 11/07/2013 Mark Riddoch Addition of reference count in the gwbuf
|
||||
* 16/07/2013 Massimiliano Pinto Added command type for the queue
|
||||
* 10/07/2014 Mark Riddoch Addition of hints
|
||||
* 15/07/2014 Mark Riddoch Added buffer properties
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <skygw_debug.h>
|
||||
#include <hint.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
|
||||
EXTERN_C_BLOCK_BEGIN
|
||||
|
||||
/**
|
||||
* Buffer properties - used to store properties related to the buffer
|
||||
* contents. This may be added at any point during the processing of the
|
||||
* data, especially in the protocol stage of the processing.
|
||||
*/
|
||||
typedef struct buf_property {
|
||||
char *name;
|
||||
char *value;
|
||||
struct buf_property *next;
|
||||
} BUF_PROPERTY;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GWBUF_TYPE_UNDEFINED = 0x00,
|
||||
@ -52,7 +70,8 @@ typedef enum
|
||||
GWBUF_TYPE_SINGLE_STMT = 0x04,
|
||||
GWBUF_TYPE_SESCMD_RESPONSE = 0x08,
|
||||
GWBUF_TYPE_RESPONSE_END = 0x10,
|
||||
GWBUF_TYPE_SESCMD = 0x20
|
||||
GWBUF_TYPE_SESCMD = 0x20,
|
||||
GWBUF_TYPE_HTTP = 0x40
|
||||
} gwbuf_type_t;
|
||||
|
||||
#define GWBUF_IS_TYPE_UNDEFINED(b) (b->gwbuf_type == 0)
|
||||
@ -73,6 +92,35 @@ typedef struct {
|
||||
int refcount; /*< Reference count on the buffer */
|
||||
} SHARED_BUF;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GWBUF_INFO_NONE = 0x0,
|
||||
GWBUF_INFO_PARSED = 0x1
|
||||
} gwbuf_info_t;
|
||||
|
||||
#define GWBUF_IS_PARSED(b) (b->gwbuf_info & GWBUF_INFO_PARSED)
|
||||
|
||||
/**
|
||||
* A structure for cleaning up memory allocations of structures which are
|
||||
* referred to by GWBUF and deallocated in gwbuf_free but GWBUF doesn't
|
||||
* know what they are.
|
||||
* All functions on the list are executed before freeing memory of GWBUF struct.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GWBUF_PARSING_INFO
|
||||
} bufobj_id_t;
|
||||
|
||||
typedef struct buffer_object_st buffer_object_t;
|
||||
|
||||
struct buffer_object_st {
|
||||
bufobj_id_t bo_id;
|
||||
void* bo_data;
|
||||
void (*bo_donefun_fp)(void *);
|
||||
buffer_object_t* bo_next;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The buffer structure used by the descriptor control blocks.
|
||||
*
|
||||
@ -82,12 +130,17 @@ typedef struct {
|
||||
* be copied within the gateway.
|
||||
*/
|
||||
typedef struct gwbuf {
|
||||
SPINLOCK gwbuf_lock;
|
||||
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 */
|
||||
int command;/*< The command type for the queue */
|
||||
buffer_object_t *gwbuf_bufobj; /*< List of objects referred to by GWBUF */
|
||||
gwbuf_info_t gwbuf_info; /*< Info bits */
|
||||
gwbuf_type_t gwbuf_type; /*< buffer's data type information */
|
||||
HINT *hint; /*< Hint data for this buffer */
|
||||
BUF_PROPERTY *properties; /*< Buffer properties */
|
||||
} GWBUF;
|
||||
|
||||
/*<
|
||||
@ -121,4 +174,18 @@ extern unsigned int gwbuf_length(GWBUF *head);
|
||||
extern GWBUF *gwbuf_clone_portion(GWBUF *head, size_t offset, size_t len);
|
||||
extern GWBUF *gwbuf_clone_transform(GWBUF *head, gwbuf_type_t type);
|
||||
extern void gwbuf_set_type(GWBUF *head, gwbuf_type_t type);
|
||||
extern int gwbuf_add_property(GWBUF *buf, char *name, char *value);
|
||||
extern char *gwbuf_get_property(GWBUF *buf, char *name);
|
||||
extern GWBUF *gwbuf_make_contiguous(GWBUF *);
|
||||
extern int gwbuf_add_hint(GWBUF *, HINT *);
|
||||
|
||||
void gwbuf_add_buffer_object(GWBUF* buf,
|
||||
bufobj_id_t id,
|
||||
void* data,
|
||||
void (*donefun_fp)(void *));
|
||||
void* gwbuf_get_buffer_object_data(GWBUF* buf, bufobj_id_t id);
|
||||
|
||||
EXTERN_C_BLOCK_END
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -39,13 +39,22 @@
|
||||
enum {MAX_PARAM_LEN=256};
|
||||
|
||||
typedef enum {
|
||||
UNDEFINED_TYPE = 0x00,
|
||||
STRING_TYPE = 0x01,
|
||||
COUNT_TYPE = 0x02,
|
||||
PERCENT_TYPE = 0x04,
|
||||
BOOL_TYPE = 0x08
|
||||
UNDEFINED_TYPE = 0x00,
|
||||
STRING_TYPE = 0x01,
|
||||
COUNT_TYPE = 0x02,
|
||||
PERCENT_TYPE = 0x04,
|
||||
BOOL_TYPE = 0x08,
|
||||
SQLVAR_TARGET_TYPE = 0x10
|
||||
} config_param_type_t;
|
||||
|
||||
typedef enum {
|
||||
TYPE_UNDEFINED = 0,
|
||||
TYPE_MASTER,
|
||||
TYPE_ALL
|
||||
} target_t;
|
||||
|
||||
enum {MAX_RLAG_NOT_AVAILABLE=-1, MAX_RLAG_UNDEFINED=-2};
|
||||
|
||||
#define PARAM_IS_TYPE(p,t) ((p) & (t))
|
||||
|
||||
/**
|
||||
@ -59,6 +68,7 @@ typedef struct config_parameter {
|
||||
int valcount; /*< int */
|
||||
int valpercent; /*< int */
|
||||
bool valbool; /*< bool */
|
||||
target_t valtarget; /*< sql variable route target */
|
||||
} qfd;
|
||||
config_param_type_t qfd_param_type;
|
||||
struct config_parameter *next; /**< Next pointer in the linked list */
|
||||
@ -97,9 +107,21 @@ bool config_set_qualified_param(
|
||||
config_param_type_t type);
|
||||
|
||||
|
||||
int config_get_valint(
|
||||
bool config_get_valint(
|
||||
int* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name, /*< if NULL examine current param only */
|
||||
config_param_type_t ptype);
|
||||
|
||||
bool config_get_valbool(
|
||||
bool* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name, /*< if NULL examine current param only */
|
||||
config_param_type_t ptype);
|
||||
|
||||
bool config_get_valtarget(
|
||||
target_t* val,
|
||||
CONFIG_PARAMETER* param,
|
||||
const char* name, /*< if NULL examine current param only */
|
||||
config_param_type_t ptype);
|
||||
#endif
|
||||
|
@ -53,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
|
||||
*/
|
||||
@ -107,12 +108,16 @@ typedef struct gw_protocol {
|
||||
* The statitics gathered on a descriptor control block
|
||||
*/
|
||||
typedef struct dcbstats {
|
||||
int n_reads; /*< Number of reads on this descriptor */
|
||||
int n_writes; /*< Number of writes on this descriptor */
|
||||
int n_accepts; /*< Number of accepts on this descriptor */
|
||||
int n_buffered; /*< Number of buffered writes */
|
||||
int n_high_water; /*< Number of crosses of high water mark */
|
||||
int n_low_water; /*< Number of crosses of low water mark */
|
||||
int n_reads; /*< Number of reads on this descriptor */
|
||||
int n_writes; /*< Number of writes on this descriptor */
|
||||
int n_accepts; /*< Number of accepts on this descriptor */
|
||||
int n_buffered; /*< Number of buffered writes */
|
||||
int n_high_water; /*< Number of crosses of high water mark */
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -231,6 +236,13 @@ typedef struct dcb {
|
||||
DCBMM memdata; /**< The data related to DCB memory management */
|
||||
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 */
|
||||
@ -259,6 +271,8 @@ int fail_accept_errno;
|
||||
#define DCB_BELOW_LOW_WATER(x) ((x)->low_water && (x)->writeqlen < (x)->low_water)
|
||||
#define DCB_ABOVE_HIGH_WATER(x) ((x)->high_water && (x)->writeqlen > (x)->high_water)
|
||||
|
||||
void dcb_pollin(DCB *, int);
|
||||
void dcb_pollout(DCB *, int);
|
||||
DCB *dcb_get_zombies(void);
|
||||
int gw_write(
|
||||
#if defined(SS_DEBUG)
|
||||
@ -289,7 +303,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 */
|
||||
|
||||
|
@ -84,12 +84,17 @@ typedef struct hashtable {
|
||||
SPINLOCK spin; /**< Internal spinlock for the hashtable */
|
||||
int n_readers; /**< Number of clients reading the table */
|
||||
int writelock; /**< The table is locked by a writer */
|
||||
bool ht_isflat; /**< Indicates whether hashtable is in stack or heap */
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t ht_chk_tail;
|
||||
#endif
|
||||
} HASHTABLE;
|
||||
|
||||
extern HASHTABLE *hashtable_alloc(int, int (*hashfn)(), int (*cmpfn)());
|
||||
HASHTABLE *hashtable_alloc_flat(HASHTABLE* target,
|
||||
int size,
|
||||
int (*hashfn)(),
|
||||
int (*cmpfn)());
|
||||
/**< Allocate a hashtable */
|
||||
extern void hashtable_memory_fns(HASHTABLE *, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN);
|
||||
/**< Provide an interface to control key/value memory
|
||||
|
69
server/include/hint.h
Normal file
69
server/include/hint.h
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef _HINT_H
|
||||
#define _HINT_H
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file hint.h The generic hint data that may be attached to buffers
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 10/07/14 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <skygw_debug.h>
|
||||
|
||||
|
||||
/**
|
||||
* The types of hint that are supported by the generic hinting mechanism.
|
||||
*/
|
||||
typedef enum {
|
||||
HINT_ROUTE_TO_MASTER = 1,
|
||||
HINT_ROUTE_TO_SLAVE,
|
||||
HINT_ROUTE_TO_NAMED_SERVER,
|
||||
HINT_ROUTE_TO_UPTODATE_SERVER,
|
||||
HINT_ROUTE_TO_ALL, /*< not implemented yet */
|
||||
HINT_PARAMETER
|
||||
} HINT_TYPE;
|
||||
|
||||
/**
|
||||
* A generic hint.
|
||||
*
|
||||
* A hint has a type associated with it and may optionally have hint
|
||||
* specific data.
|
||||
* Multiple hints may be attached to a single buffer.
|
||||
*/
|
||||
typedef struct hint {
|
||||
HINT_TYPE type; /*< The Type of hint */
|
||||
void *data; /*< Type specific data */
|
||||
void *value; /*< Parameter value for hint */
|
||||
unsigned int dsize; /*< Size of the hint data */
|
||||
struct hint *next; /*< Another hint for this buffer */
|
||||
} HINT;
|
||||
|
||||
extern HINT *hint_alloc(HINT_TYPE, void *, unsigned int);
|
||||
extern HINT *hint_create_parameter(HINT *, char *, char *);
|
||||
extern HINT *hint_create_route(HINT *, HINT_TYPE, char *);
|
||||
extern void hint_free(HINT *);
|
||||
extern HINT *hint_dup(HINT *);
|
||||
bool hint_exists(HINT **, HINT_TYPE);
|
||||
#endif
|
50
server/include/housekeeper.h
Normal file
50
server/include/housekeeper.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef _HOUSEKEEPER_H
|
||||
#define _HOUSEKEEPER_H
|
||||
/*
|
||||
* This file is distributed as part of the SkySQL Gateway. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* @file housekeeper.h A mechanism to have task run periodically
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 29/08/14 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
/**
|
||||
* The housekeeper task list
|
||||
*/
|
||||
typedef struct hktask {
|
||||
char *name; /*< A simple task name */
|
||||
void (*task)(void *data); /*< The task to call */
|
||||
void *data; /*< Data to pass the task */
|
||||
int frequency; /*< How often to call the tasks (seconds) */
|
||||
time_t nextdue; /*< When the task should be next run */
|
||||
struct hktask
|
||||
*next; /*< Next task in the list */
|
||||
} HKTASK;
|
||||
|
||||
extern void hkinit();
|
||||
extern int hktask_add(char *name, void (*task)(void *), void *data, int frequency);
|
||||
extern int hktask_remove(char *name);
|
||||
#endif
|
@ -36,4 +36,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 *);
|
||||
char* modutil_get_query(GWBUF* buf);
|
||||
|
||||
#endif
|
||||
|
@ -31,6 +31,8 @@
|
||||
* 25/07/13 Mark Riddoch Addition of diagnotics
|
||||
* 23/05/14 Mark Riddoch Addition of routine to find monitors by name
|
||||
* 23/05/14 Massimiliano Pinto Addition of defaultId and setInterval
|
||||
* 23/06/14 Massimiliano Pinto Addition of replicationHeartbeat
|
||||
* 28/08/14 Massimiliano Pinto Addition of detectStaleMaster
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -70,6 +72,7 @@ typedef struct {
|
||||
void (*setInterval)(void *, unsigned long);
|
||||
void (*defaultId)(void *, unsigned long);
|
||||
void (*replicationHeartbeat)(void *, int);
|
||||
void (*detectStaleMaster)(void *, int);
|
||||
} MONITOR_OBJECT;
|
||||
|
||||
/**
|
||||
@ -110,4 +113,5 @@ extern void monitorList(DCB *);
|
||||
extern void monitorSetId(MONITOR *, unsigned long);
|
||||
extern void monitorSetInterval (MONITOR *, unsigned long);
|
||||
extern void monitorSetReplicationHeartbeat(MONITOR *, int);
|
||||
extern void monitorDetectStaleMaster(MONITOR *, int);
|
||||
#endif
|
||||
|
@ -41,4 +41,5 @@ extern void poll_waitevents(void *);
|
||||
extern void poll_shutdown();
|
||||
extern GWBITMASK *poll_bitmask();
|
||||
extern void dprintPollStats(DCB *);
|
||||
extern void dShowThreads(DCB *dcb);
|
||||
#endif
|
||||
|
@ -38,6 +38,8 @@
|
||||
* 03/06/14 Mark Riddoch Addition of maintainance mode
|
||||
* 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields
|
||||
* 26/06/14 Mark Riddoch Adidtion of server parameters
|
||||
* 30/07/14 Massimiliano Pinto Addition of NDB status for MySQL Cluster
|
||||
* 30/08/14 Massimiliano Pinto Addition of SERVER_STALE_STATUS
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -95,12 +97,15 @@ 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_SLAVE_OF_EXTERNAL_MASTER 0x0080 /**<< Server is slave of a Master outside the provided replication topology */
|
||||
#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_NDB 0x0010 /**<< The server is part of a MySQL cluster setup */
|
||||
#define SERVER_MAINT 0x0020 /**<< Server is in maintenance mode */
|
||||
#define SERVER_SLAVE_OF_EXTERNAL_MASTER 0x0040 /**<< Server is slave of a Master outside the provided replication topology */
|
||||
#define SERVER_STALE_STATUS 0x0080 /**<< Server stale status, monitor didn't update it */
|
||||
#define SERVER_AUTH_ERROR 0x1000 /**<< Authentication erorr from monitor */
|
||||
|
||||
/**
|
||||
* Is the server running - the macro returns true if the server is marked as running
|
||||
@ -131,15 +136,21 @@ typedef struct server {
|
||||
#define SERVER_IS_JOINED(server) \
|
||||
(((server)->status & (SERVER_RUNNING|SERVER_JOINED|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_JOINED))
|
||||
|
||||
/**
|
||||
* Is the server a SQL node in MySQL Cluster? The server must be running and with NDB status
|
||||
*/
|
||||
#define SERVER_IS_NDB(server) \
|
||||
(((server)->status & (SERVER_RUNNING|SERVER_NDB|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_NDB))
|
||||
|
||||
/**
|
||||
* Is the server in maintenance mode.
|
||||
*/
|
||||
#define SERVER_IN_MAINT(server) ((server)->status & SERVER_MAINT)
|
||||
|
||||
/** server is not master, slave or joined */
|
||||
#define SERVER_NOT_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) == 0)
|
||||
#define SERVER_NOT_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED|SERVER_NDB)) == 0)
|
||||
|
||||
#define SERVER_IS_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED)) != 0)
|
||||
#define SERVER_IS_IN_CLUSTER(s) (((s)->status & (SERVER_MASTER|SERVER_SLAVE|SERVER_JOINED|SERVER_NDB)) != 0)
|
||||
|
||||
#define SERVER_IS_RELAY_SERVER(server) \
|
||||
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE))
|
||||
|
@ -137,7 +137,7 @@ typedef struct service {
|
||||
struct service *next; /**< The next service in the linked list */
|
||||
} SERVICE;
|
||||
|
||||
typedef enum count_spec_t {COUNT_ATLEAST=0, COUNT_EXACT, COUNT_ATMOST} count_spec_t;
|
||||
typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOST} count_spec_t;
|
||||
|
||||
#define SERVICE_STATE_ALLOC 1 /**< The service has been allocated */
|
||||
#define SERVICE_STATE_STARTED 2 /**< The service has been started */
|
||||
|
@ -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
|
||||
|
@ -21,15 +21,16 @@
|
||||
include ../../../build_gateway.inc
|
||||
|
||||
LOGPATH := $(ROOT_PATH)/log_manager
|
||||
QCLASSPATH := $(ROOT_PATH)/query_classifier
|
||||
UTILSPATH := $(ROOT_PATH)/utils
|
||||
|
||||
CC=cc
|
||||
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
|
||||
-I$(UTILSPATH) -Wall -g
|
||||
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) -I$(QCLASSPATH) \
|
||||
-I$(UTILSPATH) -I$(MYSQL_ROOT) -Wall -g
|
||||
|
||||
include ../../../makefile.inc
|
||||
|
||||
LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \
|
||||
LDFLAGS=-shared -L$(LOGPATH) -L$(EMBEDDED_LIB) -L$(QCLASSPATH) -Wl,-rpath,$(DEST)/lib \
|
||||
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH)
|
||||
|
||||
TESTSRCS=testfilter.c
|
||||
@ -42,17 +43,31 @@ TOPNSRCS=topfilter.c
|
||||
TOPNOBJ=$(TOPNSRCS:.c=.o)
|
||||
TEESRCS=tee.c
|
||||
TEEOBJ=$(TEESRCS:.c=.o)
|
||||
MQSRCS=mqfilter.c
|
||||
MQOBJ=$(MQSRCS:.c=.o)
|
||||
SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS)
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
|
||||
MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so
|
||||
MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libhintfilter.so libtee.so
|
||||
|
||||
ifndef BUILD_RABBITMQ
|
||||
BUILD_RABBITMQ=N
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_RABBITMQ),Y)
|
||||
SRCS += $(MQSRCS)
|
||||
MODULES += libmqfilter.so
|
||||
LIBS += -lrabbitmq -lquery_classifier
|
||||
endif
|
||||
|
||||
all: $(MODULES)
|
||||
|
||||
libtestfilter.so: $(TESTOBJ)
|
||||
$(CC) $(LDFLAGS) $(TESTOBJ) $(LIBS) -o $@
|
||||
|
||||
libmqfilter.so: $(MQOBJ)
|
||||
$(CC) $(LDFLAGS) $(MQOBJ) $(LIBS) -o $@
|
||||
|
||||
libqlafilter.so: $(QLAOBJ)
|
||||
$(CC) $(LDFLAGS) $(QLAOBJ) $(LIBS) -o $@
|
||||
|
||||
@ -65,32 +80,38 @@ libtopfilter.so: $(TOPNOBJ)
|
||||
libtee.so: $(TEEOBJ)
|
||||
$(CC) $(LDFLAGS) $(TEEOBJ) $(LIBS) -o $@
|
||||
|
||||
libhintfilter.so:
|
||||
(cd hint; touch depend.mk ; make; cp $@ ..)
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
$(DEL) $(OBJ) $(MODULES)
|
||||
rm -f $(OBJ) $(MODULES)
|
||||
(cd hint; touch depend.mk; make clean)
|
||||
|
||||
tags:
|
||||
ctags $(SRCS) $(HDRS)
|
||||
(cd hint; touch depend.mk; make tags)
|
||||
|
||||
depend:
|
||||
@$(DEL) depend.mk
|
||||
@rm -f depend.mk
|
||||
cc -M $(CFLAGS) $(SRCS) > depend.mk
|
||||
(cd hint; touch depend.mk; make depend)
|
||||
|
||||
install: $(MODULES)
|
||||
install -D $(MODULES) $(DEST)/modules
|
||||
|
||||
cleantests:
|
||||
$(MAKE) -C test cleantests
|
||||
|
||||
|
||||
buildtests:
|
||||
$(MAKE) -C test DEBUG=Y buildtests
|
||||
|
||||
|
||||
runtests:
|
||||
$(MAKE) -C test runtests
|
||||
|
||||
|
||||
testall:
|
||||
$(MAKE) -C test testall
|
||||
|
||||
|
||||
include depend.mk
|
||||
|
70
server/modules/filter/hint/Makefile
Normal file
70
server/modules/filter/hint/Makefile
Normal file
@ -0,0 +1,70 @@
|
||||
# This file is distributed as part of MaxScale form SkySQL. It is free
|
||||
# software: you can redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License as published by the Free Software Foundation,
|
||||
# version 2.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Copyright SkySQL Ab 2014
|
||||
#
|
||||
# Revision History
|
||||
# Date Who Description
|
||||
# 21/07/14 Mark Riddoch Initial module development
|
||||
|
||||
include ../../../../build_gateway.inc
|
||||
|
||||
LOGPATH := $(ROOT_PATH)/log_manager
|
||||
UTILSPATH := $(ROOT_PATH)/utils
|
||||
|
||||
CC=cc
|
||||
CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include -I$(LOGPATH) \
|
||||
-I$(UTILSPATH) -Wall -g
|
||||
|
||||
include ../../../../makefile.inc
|
||||
|
||||
LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \
|
||||
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH)
|
||||
|
||||
SRCS= hintfilter.c hintparser.c
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
|
||||
|
||||
libhintfilter.so: $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) libhintfilter.so
|
||||
|
||||
tags:
|
||||
ctags $(SRCS) $(HDRS)
|
||||
|
||||
depend:
|
||||
@rm -f depend.mk
|
||||
cc -M $(CFLAGS) $(SRCS) > depend.mk
|
||||
|
||||
install: $(MODULES)
|
||||
install -D $(MODULES) $(DEST)/modules
|
||||
|
||||
cleantests:
|
||||
$(MAKE) -C test cleantests
|
||||
|
||||
buildtests:
|
||||
$(MAKE) -C test DEBUG=Y buildtests
|
||||
|
||||
runtests:
|
||||
$(MAKE) -C test runtests
|
||||
|
||||
testall:
|
||||
$(MAKE) -C test testall
|
||||
|
||||
include depend.mk
|
272
server/modules/filter/hint/hintfilter.c
Normal file
272
server/modules/filter/hint/hintfilter.c
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale by SkySQL. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <filter.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <mysqlhint.h>
|
||||
|
||||
/**
|
||||
* hintfilter.c - a filter to parse the MaxScale hint syntax and attach those
|
||||
* hints to the buffers that carry the requests.
|
||||
*
|
||||
*/
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_FILTER,
|
||||
MODULE_ALPHA_RELEASE,
|
||||
FILTER_VERSION,
|
||||
"A hint parsing filter"
|
||||
};
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
static FILTER *createInstance(char **options, FILTER_PARAMETER **params);
|
||||
static void *newSession(FILTER *instance, SESSION *session);
|
||||
static void closeSession(FILTER *instance, void *session);
|
||||
static void freeSession(FILTER *instance, void *session);
|
||||
static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream);
|
||||
static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue);
|
||||
static void diagnostic(FILTER *instance, void *fsession, DCB *dcb);
|
||||
|
||||
|
||||
static FILTER_OBJECT MyObject = {
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
setDownstream,
|
||||
NULL, // No upstream requirement
|
||||
routeQuery,
|
||||
NULL,
|
||||
diagnostic,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
FILTER_OBJECT *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the filter for a particular service
|
||||
* within MaxScale.
|
||||
*
|
||||
* @param options The options for this filter
|
||||
*
|
||||
* @return The instance data for this new instance
|
||||
*/
|
||||
static FILTER *
|
||||
createInstance(char **options, FILTER_PARAMETER **params)
|
||||
{
|
||||
HINT_INSTANCE *my_instance;
|
||||
|
||||
if ((my_instance = calloc(1, sizeof(HINT_INSTANCE))) != NULL)
|
||||
my_instance->sessions = 0;
|
||||
return (FILTER *)my_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new session with this instance of the filter.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
newSession(FILTER *instance, SESSION *session)
|
||||
{
|
||||
HINT_INSTANCE *my_instance = (HINT_INSTANCE *)instance;
|
||||
HINT_SESSION *my_session;
|
||||
|
||||
if ((my_session = calloc(1, sizeof(HINT_SESSION))) != NULL)
|
||||
{
|
||||
my_session->query_len = 0;
|
||||
my_session->request = NULL;
|
||||
my_session->stack = NULL;
|
||||
my_session->named_hints = NULL;
|
||||
}
|
||||
|
||||
return my_session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a session with the filter, this is the mechanism
|
||||
* by which a filter may cleanup data structure etc.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(FILTER *instance, void *session)
|
||||
{
|
||||
HINT_SESSION *my_session = (HINT_SESSION *)session;
|
||||
NAMEDHINTS* named_hints;
|
||||
HINTSTACK* hint_stack;
|
||||
|
||||
if (my_session->request)
|
||||
gwbuf_free(my_session->request);
|
||||
|
||||
|
||||
/** Free named hints */
|
||||
named_hints = my_session->named_hints;
|
||||
|
||||
while ((named_hints = free_named_hint(named_hints)) != NULL)
|
||||
;
|
||||
/** Free stacked hints */
|
||||
hint_stack = my_session->stack;
|
||||
|
||||
while ((hint_stack = free_hint_stack(hint_stack)) != NULL)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the memory associated with this filter session.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void
|
||||
freeSession(FILTER *instance, void *session)
|
||||
{
|
||||
free(session);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the downstream component for this filter.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The session being closed
|
||||
* @param downstream The downstream filter or router
|
||||
*/
|
||||
static void
|
||||
setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
|
||||
{
|
||||
HINT_SESSION *my_session = (HINT_SESSION *)session;
|
||||
|
||||
my_session->down = *downstream;
|
||||
}
|
||||
|
||||
/**
|
||||
* The routeQuery entry point. This is passed the query buffer
|
||||
* to which the filter should be applied. Once applied the
|
||||
* query shoudl normally be passed to the downstream component
|
||||
* (filter or router) in the filter chain.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param queue The query data
|
||||
*/
|
||||
static int
|
||||
routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
||||
{
|
||||
HINT_SESSION *my_session = (HINT_SESSION *)session;
|
||||
char *ptr;
|
||||
int rval, len, residual;
|
||||
HINT *hint;
|
||||
|
||||
if (my_session->request == NULL)
|
||||
{
|
||||
/*
|
||||
* No stored buffer, so this must be the first
|
||||
* buffer of a new request.
|
||||
*/
|
||||
if (modutil_MySQL_Query(queue, &ptr, &len, &residual) == 0)
|
||||
{
|
||||
return my_session->down.routeQuery(
|
||||
my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
}
|
||||
my_session->request = queue;
|
||||
my_session->query_len = len;
|
||||
}
|
||||
else
|
||||
{
|
||||
gwbuf_append(my_session->request, queue);
|
||||
}
|
||||
|
||||
if (gwbuf_length(my_session->request) < my_session->query_len)
|
||||
{
|
||||
/*
|
||||
* We have not got the entire SQL text, buffer and wait for
|
||||
* the remainder.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
/* We have the entire SQL text, parse for hints and attach to the
|
||||
* buffer at the head of the queue.
|
||||
*/
|
||||
queue = my_session->request;
|
||||
my_session->request = NULL;
|
||||
my_session->query_len = 0;
|
||||
hint = hint_parser(my_session, queue);
|
||||
queue->hint = hint;
|
||||
|
||||
/* Now process the request */
|
||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnostics routine
|
||||
*
|
||||
* If fsession is NULL then print diagnostics on the filter
|
||||
* instance as a whole, otherwise print diagnostics for the
|
||||
* particular session.
|
||||
*
|
||||
* @param instance The filter instance
|
||||
* @param fsession Filter session, may be NULL
|
||||
* @param dcb The DCB for diagnostic output
|
||||
*/
|
||||
static void
|
||||
diagnostic(FILTER *instance, void *fsession, DCB *dcb)
|
||||
{
|
||||
HINT_INSTANCE *my_instance = (HINT_INSTANCE *)instance;
|
||||
HINT_SESSION *my_session = (HINT_SESSION *)fsession;
|
||||
|
||||
}
|
770
server/modules/filter/hint/hintparser.c
Normal file
770
server/modules/filter/hint/hintparser.c
Normal file
@ -0,0 +1,770 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale by SkySQL. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <filter.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <mysqlhint.h>
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
/**
|
||||
* hintparser.c - Find any comment in the SQL packet and look for MAXSCALE
|
||||
* hints in that comment.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The keywords in the hint syntax
|
||||
*/
|
||||
struct {
|
||||
char *keyword;
|
||||
TOKEN_VALUE token;
|
||||
} keywords[] = {
|
||||
{ "maxscale", TOK_MAXSCALE },
|
||||
{ "prepare", TOK_PREPARE },
|
||||
{ "start", TOK_START },
|
||||
{ "begin", TOK_START },
|
||||
{ "stop", TOK_STOP },
|
||||
{ "end", TOK_STOP },
|
||||
{ "=", TOK_EQUAL },
|
||||
{ "route", TOK_ROUTE },
|
||||
{ "to", TOK_TO },
|
||||
{ "master", TOK_MASTER },
|
||||
{ "slave", TOK_SLAVE },
|
||||
{ "server", TOK_SERVER },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
/**
|
||||
HINT_TOKEN kwords[] = {
|
||||
{ TOK_MAXSCALE, "maxscale" },
|
||||
{ TOK_PREPARE, "prepare" },
|
||||
{ TOK_START, "start" },
|
||||
{ TOK_START, "begin" },
|
||||
{ TOK_STOP, "stop" },
|
||||
{ TOK_STOP, "end" },
|
||||
{ TOK_EQUAL, "=" },
|
||||
{ TOK_ROUTE, "route" },
|
||||
{ TOK_TO, "to" },
|
||||
{ TOK_MASTER, "master" },
|
||||
{ TOK_SLAVE, "slave" },
|
||||
{ TOK_SERVER, "server" },
|
||||
{ 0, NULL}
|
||||
};
|
||||
*/
|
||||
|
||||
static HINT_TOKEN *hint_next_token(GWBUF **buf, char **ptr);
|
||||
static void hint_pop(HINT_SESSION *);
|
||||
static HINT *lookup_named_hint(HINT_SESSION *, char *);
|
||||
static void create_named_hint(HINT_SESSION *, char *, HINT *);
|
||||
static void hint_push(HINT_SESSION *, HINT *);
|
||||
static const char* token_get_keyword (HINT_TOKEN* token);
|
||||
static void token_free(HINT_TOKEN* token);
|
||||
|
||||
typedef enum { HM_EXECUTE, HM_START, HM_PREPARE } HINT_MODE;
|
||||
|
||||
void token_free(HINT_TOKEN* token)
|
||||
{
|
||||
if (token->value != NULL)
|
||||
{
|
||||
free(token->value);
|
||||
}
|
||||
free(token);
|
||||
}
|
||||
|
||||
|
||||
static const char* token_get_keyword (
|
||||
HINT_TOKEN* token)
|
||||
{
|
||||
switch (token->token) {
|
||||
case TOK_EOL:
|
||||
return "End of line";
|
||||
break;
|
||||
|
||||
case TOK_STRING:
|
||||
return token->value;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
int i = 0;
|
||||
while (i < TOK_EOL && keywords[i].token != token->token)
|
||||
i++;
|
||||
|
||||
ss_dassert(i != TOK_EOL);
|
||||
|
||||
if (i == TOK_EOL)
|
||||
{
|
||||
return "Unknown token";
|
||||
}
|
||||
else
|
||||
{
|
||||
return keywords[i].keyword;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse the hint comments in the MySQL statement passed in request.
|
||||
* Add any hints to the buffer for later processing.
|
||||
*
|
||||
* @param session The filter session
|
||||
* @param request The MySQL request buffer
|
||||
* @return The hints parsed in this statement or active on the
|
||||
* stack
|
||||
*/
|
||||
HINT *
|
||||
hint_parser(HINT_SESSION *session, GWBUF *request)
|
||||
{
|
||||
char *ptr, lastch = ' ';
|
||||
int len, residual, state;
|
||||
int found, escape, quoted, squoted;
|
||||
HINT *rval = NULL;
|
||||
char *pname, *lvalue, *hintname = NULL;
|
||||
GWBUF *buf;
|
||||
HINT_TOKEN *tok;
|
||||
HINT_MODE mode = HM_EXECUTE;
|
||||
|
||||
/* First look for any comment in the SQL */
|
||||
modutil_MySQL_Query(request, &ptr, &len, &residual);
|
||||
buf = request;
|
||||
found = 0;
|
||||
escape = 0;
|
||||
quoted = 0;
|
||||
squoted = 0;
|
||||
do {
|
||||
while (len--)
|
||||
{
|
||||
if (*ptr == '\\')
|
||||
escape = 1;
|
||||
else if (*ptr == '\"' && quoted)
|
||||
quoted = 0;
|
||||
else if (*ptr == '\"' && quoted == 0)
|
||||
quoted = 0;
|
||||
else if (*ptr == '\'' && squoted)
|
||||
squoted = 0;
|
||||
else if (*ptr == '\"' && squoted == 0)
|
||||
squoted = 0;
|
||||
else if (quoted || squoted)
|
||||
;
|
||||
else if (escape)
|
||||
escape = 0;
|
||||
else if (*ptr == '#')
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (*ptr == '/')
|
||||
lastch = '/';
|
||||
else if (*ptr == '*' && lastch == '/')
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (*ptr == '-' && lastch == '-')
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (*ptr == '-')
|
||||
lastch = '-';
|
||||
else
|
||||
lastch = *ptr;
|
||||
ptr++;
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
|
||||
buf = buf->next;
|
||||
if (buf)
|
||||
{
|
||||
len = GWBUF_LENGTH(buf);
|
||||
ptr = GWBUF_DATA(buf);
|
||||
}
|
||||
} while (buf);
|
||||
|
||||
if (!found) /* No comment so we need do no more */
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have got here then we have a comment, ptr point to
|
||||
* the comment character if it is a '#' comment or the second
|
||||
* character of the comment if it is a -- or /* comment
|
||||
*
|
||||
* Move to the next character in the SQL.
|
||||
*/
|
||||
ptr++;
|
||||
if (ptr > (char *)(buf->end))
|
||||
{
|
||||
buf = buf->next;
|
||||
if (buf)
|
||||
ptr = GWBUF_DATA(buf);
|
||||
else
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
tok = hint_next_token(&buf, &ptr);
|
||||
|
||||
if (tok == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
/** This is not MaxScale hint because it doesn't start with 'maxscale' */
|
||||
if (tok->token != TOK_MAXSCALE)
|
||||
{
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
token_free(tok);
|
||||
|
||||
state = HS_INIT;
|
||||
|
||||
while ((tok = hint_next_token(&buf, &ptr)) != NULL
|
||||
&& tok->token != TOK_EOL)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case HS_INIT:
|
||||
switch (tok->token)
|
||||
{
|
||||
case TOK_ROUTE:
|
||||
state = HS_ROUTE;
|
||||
break;
|
||||
case TOK_STRING:
|
||||
state = HS_NAME;
|
||||
lvalue = strdup(tok->value);
|
||||
break;
|
||||
case TOK_STOP:
|
||||
/* Action: pop active hint */
|
||||
hint_pop(session);
|
||||
state = HS_INIT;
|
||||
break;
|
||||
case TOK_START:
|
||||
hintname = NULL;
|
||||
mode = HM_START;
|
||||
state = HS_INIT;
|
||||
break;
|
||||
default:
|
||||
/* Error: expected hint, name or STOP */
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'route', 'stop' or hint name instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'route', 'stop' or hint name instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
break;
|
||||
case HS_ROUTE:
|
||||
if (tok->token != TOK_TO)
|
||||
{
|
||||
/* Error, expect TO */;
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'to' instead of '%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'to' instead of '%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
state = HS_ROUTE1;
|
||||
break;
|
||||
case HS_ROUTE1:
|
||||
switch (tok->token)
|
||||
{
|
||||
case TOK_MASTER:
|
||||
rval = hint_create_route(rval,
|
||||
HINT_ROUTE_TO_MASTER, NULL);
|
||||
break;
|
||||
case TOK_SLAVE:
|
||||
rval = hint_create_route(rval,
|
||||
HINT_ROUTE_TO_SLAVE, NULL);
|
||||
break;
|
||||
case TOK_SERVER:
|
||||
state = HS_ROUTE_SERVER;
|
||||
break;
|
||||
default:
|
||||
/* Error expected MASTER, SLAVE or SERVER */
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'master', 'slave', or 'server' instead "
|
||||
"of '%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'master', 'slave', or 'server' instead "
|
||||
"of '%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
break;
|
||||
case HS_ROUTE_SERVER:
|
||||
if (tok->token == TOK_STRING)
|
||||
{
|
||||
rval = hint_create_route(rval,
|
||||
HINT_ROUTE_TO_NAMED_SERVER, tok->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Error: Expected server name */
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"server name instead of '%s'. Hint "
|
||||
"ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"server name instead of '%s'. Hint "
|
||||
"ignored.",
|
||||
token_get_keyword(tok))));
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
break;
|
||||
case HS_NAME:
|
||||
switch (tok->token)
|
||||
{
|
||||
case TOK_EQUAL:
|
||||
pname = lvalue;
|
||||
state = HS_PVALUE;
|
||||
break;
|
||||
case TOK_PREPARE:
|
||||
pname = lvalue;
|
||||
state = HS_PREPARE;
|
||||
break;
|
||||
case TOK_START:
|
||||
/* Action start(lvalue) */
|
||||
hintname = lvalue;
|
||||
mode = HM_START;
|
||||
state = HS_INIT;
|
||||
break;
|
||||
default:
|
||||
/* Error, token tok->value not expected */
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'=', 'prepare', or 'start' instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'=', 'prepare', or 'start' instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
break;
|
||||
case HS_PVALUE:
|
||||
/* Action: pname = tok->value */
|
||||
rval = hint_create_parameter(rval, pname, tok->value);
|
||||
state = HS_INIT;
|
||||
break;
|
||||
case HS_PREPARE:
|
||||
mode = HM_PREPARE;
|
||||
hintname = lvalue;
|
||||
switch (tok->token)
|
||||
{
|
||||
case TOK_ROUTE:
|
||||
state = HS_ROUTE;
|
||||
break;
|
||||
case TOK_STRING:
|
||||
state = HS_NAME;
|
||||
lvalue = tok->value;
|
||||
break;
|
||||
default:
|
||||
/* Error, token tok->value not expected */
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'route' or hint name instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Syntax error in hint. Expected "
|
||||
"'route' or hint name instead of "
|
||||
"'%s'. Hint ignored.",
|
||||
token_get_keyword(tok))));
|
||||
token_free(tok);
|
||||
goto retblock;
|
||||
}
|
||||
break;
|
||||
}
|
||||
token_free(tok);
|
||||
} /*< while */
|
||||
|
||||
if (tok->token == TOK_EOL)
|
||||
{
|
||||
token_free(tok);
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case HM_START:
|
||||
/*
|
||||
* We are starting either a predefined set of hints,
|
||||
* creating a new set of hints and starting in a single
|
||||
* operation or starting an anonymous block of hints.
|
||||
*/
|
||||
if (hintname == NULL && rval != NULL)
|
||||
{
|
||||
/* We are starting an anonymous block of hints */
|
||||
hint_push(session, rval);
|
||||
rval = NULL;
|
||||
} else if (hintname && rval)
|
||||
{
|
||||
/* We are creating and starting a block of hints */
|
||||
if (lookup_named_hint(session, hintname) != NULL)
|
||||
{
|
||||
/* Error hint with this name already exists */
|
||||
}
|
||||
else
|
||||
{
|
||||
create_named_hint(session, hintname, rval);
|
||||
hint_push(session, hint_dup(rval));
|
||||
}
|
||||
} else if (hintname && rval == NULL)
|
||||
{
|
||||
/* We starting an already define set of named hints */
|
||||
rval = lookup_named_hint(session, hintname);
|
||||
hint_push(session, hint_dup(rval));
|
||||
free(hintname);
|
||||
rval = NULL;
|
||||
} else if (hintname == NULL && rval == NULL)
|
||||
{
|
||||
/* Error case */
|
||||
}
|
||||
break;
|
||||
case HM_PREPARE:
|
||||
/*
|
||||
* We are preparing a named set of hints. Note this does
|
||||
* not trigger the usage of these hints currently.
|
||||
*/
|
||||
if (hintname == NULL || rval == NULL)
|
||||
{
|
||||
/* Error case, name and hints must be defined */
|
||||
}
|
||||
else
|
||||
{
|
||||
create_named_hint(session, hintname, rval);
|
||||
}
|
||||
/* We are not starting the hints now, so return an empty
|
||||
* hint set.
|
||||
*/
|
||||
rval = NULL;
|
||||
break;
|
||||
case HM_EXECUTE:
|
||||
/*
|
||||
* We have a one-off hint for the statement we are
|
||||
* currently forwarding.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
retblock:
|
||||
if (rval == NULL)
|
||||
{
|
||||
/* No new hint parsed in this statement, apply the current
|
||||
* top of stack if there is one.
|
||||
*/
|
||||
if (session->stack)
|
||||
rval = hint_dup(session->stack->hint);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next token in the inout stream
|
||||
*
|
||||
* @param buf A pointer to the buffer point, will be updated if a
|
||||
* new buffer is used.
|
||||
* @param ptr The pointer within the buffer we are processing
|
||||
* @return A HINT token
|
||||
*/
|
||||
static HINT_TOKEN *
|
||||
hint_next_token(GWBUF **buf, char **ptr)
|
||||
{
|
||||
char word[100], *dest;
|
||||
int inword = 0;
|
||||
int endtag = 0;
|
||||
char inquote = '\0';
|
||||
int i, found;
|
||||
HINT_TOKEN *tok;
|
||||
|
||||
if ((tok = (HINT_TOKEN *)malloc(sizeof(HINT_TOKEN))) == NULL)
|
||||
return NULL;
|
||||
tok->value = NULL;
|
||||
dest = word;
|
||||
while (*ptr < (char *)((*buf)->end) || (*buf)->next)
|
||||
{
|
||||
/** word ends, don't move ptr but return with read word */
|
||||
if (inword && inquote == '\0' &&
|
||||
(isspace(**ptr) || **ptr == '='))
|
||||
{
|
||||
inword = 0;
|
||||
break;
|
||||
}
|
||||
/** found '=', move ptr and return with '=' */
|
||||
else if (!inword && inquote == '\0' && **ptr == '=')
|
||||
{
|
||||
*dest = **ptr;
|
||||
*dest++;
|
||||
(*ptr)++;
|
||||
break;
|
||||
}
|
||||
else if (**ptr == '\'' && inquote == '\'')
|
||||
inquote = '\0';
|
||||
else if (**ptr == '\'')
|
||||
inquote = **ptr;
|
||||
/** Any other character which belongs to the word, move ahead */
|
||||
|
||||
else if(**ptr == '/' && endtag)
|
||||
{
|
||||
/** Comment end tag found, rewind the pointer back and return the token */
|
||||
inword = 0;
|
||||
(*ptr)--;
|
||||
break;
|
||||
}
|
||||
else if(**ptr == '*' && !endtag)
|
||||
{
|
||||
endtag = 1;
|
||||
}
|
||||
else if (inword || (isspace(**ptr) == 0))
|
||||
{
|
||||
*dest++ = **ptr;
|
||||
inword = 1;
|
||||
}
|
||||
(*ptr)++;
|
||||
|
||||
if (*ptr > (char *)((*buf)->end) && (*buf)->next)
|
||||
{
|
||||
*buf = (*buf)->next;
|
||||
*ptr = (*buf)->start;
|
||||
}
|
||||
|
||||
if (dest - word > 98)
|
||||
break;
|
||||
} /*< while */
|
||||
*dest = 0;
|
||||
|
||||
/* We now have a word in the local word, check to see if it is a
|
||||
* token we recognise.
|
||||
*/
|
||||
if (word[0] == '\0' || (word[0] == '*' && word[1] == '/'))
|
||||
{
|
||||
tok->token = TOK_EOL;
|
||||
return tok;
|
||||
}
|
||||
found = 0;
|
||||
for (i = 0; keywords[i].keyword; i++)
|
||||
{
|
||||
if (strcasecmp(word, keywords[i].keyword) == 0)
|
||||
{
|
||||
tok->token = keywords[i].token;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == 0)
|
||||
{
|
||||
tok->token = TOK_STRING;
|
||||
tok->value = strdup(word);
|
||||
}
|
||||
|
||||
return tok;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* hint_pop - pop the hint off the top of the stack if it is not empty
|
||||
*
|
||||
* @param session The filter session.
|
||||
*/
|
||||
void
|
||||
hint_pop(HINT_SESSION *session)
|
||||
{
|
||||
HINTSTACK *ptr;
|
||||
HINT *hint;
|
||||
|
||||
if ((ptr = session->stack) != NULL)
|
||||
{
|
||||
session->stack = ptr->next;
|
||||
while (ptr->hint)
|
||||
{
|
||||
hint = ptr->hint;
|
||||
ptr->hint = hint->next;
|
||||
hint_free(hint);
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a hint onto the stack of actie hints
|
||||
*
|
||||
* @param session The filter session
|
||||
* @param hint The hint to push, the hint ownership is retained
|
||||
* by the stack and should not be freed by the caller
|
||||
*/
|
||||
static void
|
||||
hint_push(HINT_SESSION *session, HINT *hint)
|
||||
{
|
||||
HINTSTACK *item;
|
||||
|
||||
if ((item = (HINTSTACK *)malloc(sizeof(HINTSTACK))) == NULL)
|
||||
return;
|
||||
item->hint = hint;
|
||||
item->next = session->stack;
|
||||
session->stack = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a hint block that already exists with this name
|
||||
*
|
||||
* @param session The filter session
|
||||
* @param name The name to lookup
|
||||
* @return the HINT or NULL if the name was not found.
|
||||
*/
|
||||
static HINT *
|
||||
lookup_named_hint(HINT_SESSION *session, char *name)
|
||||
{
|
||||
NAMEDHINTS *ptr = session->named_hints;
|
||||
|
||||
while (ptr)
|
||||
{
|
||||
if (strcmp(ptr->name, name) == 0)
|
||||
return ptr->hints;
|
||||
ptr = ptr->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a named hint block
|
||||
*
|
||||
* @param session The filter session
|
||||
* @param name The name of the block to ceate
|
||||
* @param hint The hints themselves
|
||||
*/
|
||||
static void
|
||||
create_named_hint(HINT_SESSION *session, char *name, HINT *hint)
|
||||
{
|
||||
NAMEDHINTS *block;
|
||||
|
||||
if ((block = (NAMEDHINTS *)malloc(sizeof(NAMEDHINTS))) == NULL)
|
||||
return;
|
||||
|
||||
block->name = name;
|
||||
block->hints = hint_dup(hint);
|
||||
block->next = session->named_hints;
|
||||
session->named_hints = block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the given NAMEDHINTS struct and all included hints.
|
||||
*
|
||||
* @param named_hint NAMEDHINTS struct to be released
|
||||
*
|
||||
* @return pointer to next NAMEDHINTS struct.
|
||||
*/
|
||||
NAMEDHINTS* free_named_hint(
|
||||
NAMEDHINTS* named_hint)
|
||||
{
|
||||
NAMEDHINTS* next;
|
||||
|
||||
if (named_hint != NULL)
|
||||
{
|
||||
HINT* hint;
|
||||
|
||||
next = named_hint->next;
|
||||
|
||||
while (named_hint->hints != NULL)
|
||||
{
|
||||
hint = named_hint->hints->next;
|
||||
hint_free(named_hint->hints);
|
||||
named_hint->hints = hint;
|
||||
}
|
||||
free(named_hint->name);
|
||||
free(named_hint);
|
||||
return next;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the given HINTSTACK struct and all included hints.
|
||||
*
|
||||
* @param hint_stack HINTSTACK struct to be released
|
||||
*
|
||||
* @return pointer to next HINTSTACK struct.
|
||||
*/
|
||||
HINTSTACK* free_hint_stack(
|
||||
HINTSTACK* hint_stack)
|
||||
{
|
||||
HINTSTACK* next;
|
||||
|
||||
if (hint_stack != NULL)
|
||||
{
|
||||
HINT* hint;
|
||||
|
||||
next = hint_stack->next;
|
||||
|
||||
while (hint_stack->hint != NULL)
|
||||
{
|
||||
hint = hint_stack->hint->next;
|
||||
hint_free(hint_stack->hint);
|
||||
hint_stack->hint = hint;
|
||||
}
|
||||
free(hint_stack);
|
||||
return next;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
1462
server/modules/filter/mqfilter.c
Normal file
1462
server/modules/filter/mqfilter.c
Normal file
File diff suppressed because it is too large
Load Diff
367
server/modules/include/blr.h
Normal file
367
server/modules/include/blr.h
Normal file
@ -0,0 +1,367 @@
|
||||
#ifndef _BLR_H
|
||||
#define _BLR_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 blr.h - The binlog router header file
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 02/04/14 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <dcb.h>
|
||||
#include <buffer.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define BINLOG_FNAMELEN 16
|
||||
#define BLR_PROTOCOL "MySQLBackend"
|
||||
#define BINLOG_MAGIC { 0xfe, 0x62, 0x69, 0x6e }
|
||||
#define BINLOG_NAMEFMT "%s.%06d"
|
||||
#define BINLOG_NAME_ROOT "mysql-bin"
|
||||
|
||||
/**
|
||||
* High and Low water marks for the slave dcb. These values can be overriden
|
||||
* by the router options highwater and lowwater.
|
||||
*/
|
||||
#define DEF_LOW_WATER 20000
|
||||
#define DEF_HIGH_WATER 300000
|
||||
|
||||
/**
|
||||
* Some useful macros for examining the MySQL Response packets
|
||||
*/
|
||||
#define MYSQL_RESPONSE_OK(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4) == 0x00)
|
||||
#define MYSQL_RESPONSE_EOF(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4) == 0xfe)
|
||||
#define MYSQL_RESPONSE_ERR(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4) == 0xff)
|
||||
#define MYSQL_ERROR_CODE(buf) (*((uint8_t *)GWBUF_DATA(buf) + 5))
|
||||
#define MYSQL_ERROR_MSG(buf) ((uint8_t *)GWBUF_DATA(buf) + 6)
|
||||
#define MYSQL_COMMAND(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4))
|
||||
|
||||
/**
|
||||
* Slave statistics
|
||||
*/
|
||||
typedef struct {
|
||||
int n_events; /*< Number of events sent */
|
||||
int n_bursts; /*< Number of bursts sent */
|
||||
int n_requests; /*< Number of requests received */
|
||||
int n_flows; /*< Number of flow control restarts */
|
||||
int n_catchupnr; /*< No. of times catchup resulted in not entering loop */
|
||||
int n_alreadyupd;
|
||||
int n_upd;
|
||||
int n_cb;
|
||||
int n_cbna;
|
||||
int n_dcb;
|
||||
int n_above;
|
||||
int n_failed_read;
|
||||
int n_overrun;
|
||||
int n_actions[3];
|
||||
} SLAVE_STATS;
|
||||
|
||||
/**
|
||||
* The client session structure used within this router. This represents
|
||||
* the slaves that are replicating binlogs from MaxScale.
|
||||
*/
|
||||
typedef struct router_slave {
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t rses_chk_top;
|
||||
#endif
|
||||
DCB *dcb; /*< The slave server DCB */
|
||||
int state; /*< The state of this slave */
|
||||
int binlog_pos; /*< Binlog position for this slave */
|
||||
char binlogfile[BINLOG_FNAMELEN+1];
|
||||
/*< Current binlog file for this slave */
|
||||
int serverid; /*< Server-id of the slave */
|
||||
char *hostname; /*< Hostname of the slave, if known */
|
||||
char *user; /*< Username if given */
|
||||
char *passwd; /*< Password if given */
|
||||
short port; /*< MySQL port */
|
||||
int nocrc; /*< Disable CRC */
|
||||
int overrun;
|
||||
uint32_t rank; /*< Replication rank */
|
||||
uint8_t seqno; /*< Replication dump sequence no */
|
||||
SPINLOCK catch_lock; /*< Event catchup lock */
|
||||
unsigned int cstate; /*< Catch up state */
|
||||
SPINLOCK rses_lock; /*< Protects rses_deleted */
|
||||
pthread_t pthread;
|
||||
struct router_instance
|
||||
*router; /*< Pointer to the owning router */
|
||||
struct router_slave *next;
|
||||
SLAVE_STATS stats; /*< Slave statistics */
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t rses_chk_tail;
|
||||
#endif
|
||||
} ROUTER_SLAVE;
|
||||
|
||||
|
||||
/**
|
||||
* The statistics for this router instance
|
||||
*/
|
||||
typedef struct {
|
||||
int n_slaves; /*< Number slave sessions created */
|
||||
int n_reads; /*< Number of record reads */
|
||||
uint64_t n_binlogs; /*< Number of binlog records from master */
|
||||
uint64_t n_binlog_errors;/*< Number of binlog records from master */
|
||||
uint64_t n_rotates; /*< Number of binlog rotate events */
|
||||
uint64_t n_cachehits; /*< Number of hits on the binlog cache */
|
||||
uint64_t n_cachemisses; /*< Number of misses on the binlog cache */
|
||||
int n_registered; /*< Number of registered slaves */
|
||||
int n_masterstarts; /*< Number of times connection restarted */
|
||||
int n_delayedreconnects;
|
||||
int n_residuals; /*< Number of times residual data was buffered */
|
||||
int n_heartbeats; /*< Number of heartbeat messages */
|
||||
time_t lastReply;
|
||||
uint64_t n_fakeevents; /*< Fake events not written to disk */
|
||||
uint64_t n_artificial; /*< Artificial events not written to disk */
|
||||
uint64_t events[0x24]; /*< Per event counters */
|
||||
} ROUTER_STATS;
|
||||
|
||||
/**
|
||||
* Saved responses from the master that will be forwarded to slaves
|
||||
*/
|
||||
typedef struct {
|
||||
GWBUF *server_id; /*< Master server id */
|
||||
GWBUF *heartbeat; /*< Heartbeat period */
|
||||
GWBUF *chksum1; /*< Binlog checksum 1st response */
|
||||
GWBUF *chksum2; /*< Binlog checksum 2nd response */
|
||||
GWBUF *gtid_mode; /*< GTID Mode response */
|
||||
GWBUF *uuid; /*< Master UUID */
|
||||
GWBUF *setslaveuuid; /*< Set Slave UUID */
|
||||
GWBUF *setnames; /*< Set NAMES latin1 */
|
||||
GWBUF *utf8; /*< Set NAMES utf8 */
|
||||
GWBUF *select1; /*< select 1 */
|
||||
GWBUF *selectver; /*< select version() */
|
||||
uint8_t *fde_event; /*< Format Description Event */
|
||||
int fde_len; /*< Length of fde_event */
|
||||
} MASTER_RESPONSES;
|
||||
|
||||
/**
|
||||
* The binlog record structure. This contains the actual packet received from the
|
||||
* master, the binlog position of the data in the packet, a point to the data and
|
||||
* the length of the binlog record.
|
||||
*
|
||||
* This allows requests for binlog records in the cache to be serviced by simply
|
||||
* sending the exact same packet as was received by MaxScale from the master.
|
||||
* Items are written to the backing file as soon as they are received. The binlog
|
||||
* cache is flushed of old records periodically, releasing the GWBUF's back to the
|
||||
* free memory pool.
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned long position; /*< binlog record position for this cache entry */
|
||||
GWBUF *pkt; /*< The packet received from the master */
|
||||
unsigned char *data; /*< Pointer to the data within the packet */
|
||||
unsigned int record_len; /*< Binlog record length */
|
||||
} BLCACHE_RECORD;
|
||||
|
||||
/**
|
||||
* The binlog cache. A cache exists for each file that hold cached bin log records.
|
||||
* Typically the router will hold two binlog caches, one for the current file and one
|
||||
* for the previous file.
|
||||
*/
|
||||
typedef struct {
|
||||
char filename[BINLOG_FNAMELEN+1];
|
||||
BLCACHE_RECORD *first;
|
||||
BLCACHE_RECORD *current;
|
||||
int cnt;
|
||||
} BLCACHE;
|
||||
|
||||
|
||||
/**
|
||||
* The per instance data for the router.
|
||||
*/
|
||||
typedef struct router_instance {
|
||||
SERVICE *service; /*< Pointer to the service using this router */
|
||||
ROUTER_SLAVE *slaves; /*< Link list of all the slave connections */
|
||||
SPINLOCK lock; /*< Spinlock for the instance data */
|
||||
char *uuid; /*< UUID for the router to use w/master */
|
||||
int masterid; /*< Server ID of the master */
|
||||
int serverid; /*< Server ID to use with master */
|
||||
char *user; /*< User name to use with master */
|
||||
char *password; /*< Password to use with master */
|
||||
char *fileroot; /*< Root of binlog filename */
|
||||
DCB *master; /*< DCB for master connection */
|
||||
DCB *client; /*< DCB for dummy client */
|
||||
SESSION *session; /*< Fake session for master connection */
|
||||
unsigned int master_state; /*< State of the master FSM */
|
||||
uint8_t lastEventReceived;
|
||||
GWBUF *residual; /*< Any residual binlog event */
|
||||
MASTER_RESPONSES saved_master; /*< Saved master responses */
|
||||
char binlog_name[BINLOG_FNAMELEN+1];
|
||||
/*< Name of the current binlog file */
|
||||
uint64_t binlog_position;
|
||||
/*< Current binlog position */
|
||||
int binlog_fd; /*< File descriptor of the binlog
|
||||
* file being written
|
||||
*/
|
||||
unsigned int low_water; /*< Low water mark for client DCB */
|
||||
unsigned int high_water; /*< High water mark for client DCB */
|
||||
BLCACHE *cache[2];
|
||||
ROUTER_STATS stats; /*< Statistics for this router */
|
||||
int active_logs;
|
||||
int reconnect_pending;
|
||||
int handling_threads;
|
||||
struct router_instance
|
||||
*next;
|
||||
} ROUTER_INSTANCE;
|
||||
|
||||
/**
|
||||
* Packet header for replication messages
|
||||
*/
|
||||
typedef struct rep_header {
|
||||
int payload_len; /*< Payload length (24 bits) */
|
||||
uint8_t seqno; /*< Response sequence number */
|
||||
uint8_t ok; /*< OK Byte from packet */
|
||||
uint32_t timestamp; /*< Timestamp - start of binlog record */
|
||||
uint8_t event_type; /*< Binlog event type */
|
||||
uint32_t serverid; /*< Server id of master */
|
||||
uint32_t event_size; /*< Size of header, post-header and body */
|
||||
uint32_t next_pos; /*< Position of next event */
|
||||
uint16_t flags; /*< Event flags */
|
||||
} REP_HEADER;
|
||||
|
||||
/**
|
||||
* State machine for the master to MaxScale replication
|
||||
*/
|
||||
#define BLRM_UNCONNECTED 0x0000
|
||||
#define BLRM_AUTHENTICATED 0x0001
|
||||
#define BLRM_TIMESTAMP 0x0002
|
||||
#define BLRM_SERVERID 0x0003
|
||||
#define BLRM_HBPERIOD 0x0004
|
||||
#define BLRM_CHKSUM1 0x0005
|
||||
#define BLRM_CHKSUM2 0x0006
|
||||
#define BLRM_GTIDMODE 0x0007
|
||||
#define BLRM_MUUID 0x0008
|
||||
#define BLRM_SUUID 0x0009
|
||||
#define BLRM_LATIN1 0x000A
|
||||
#define BLRM_UTF8 0x000B
|
||||
#define BLRM_SELECT1 0x000C
|
||||
#define BLRM_SELECTVER 0x000D
|
||||
#define BLRM_REGISTER 0x000E
|
||||
#define BLRM_BINLOGDUMP 0x000F
|
||||
|
||||
#define BLRM_MAXSTATE 0x000F
|
||||
|
||||
static char *blrm_states[] = { "Unconnected", "Authenticated", "Timestamp retrieval",
|
||||
"Server ID retrieval", "HeartBeat Period setup", "binlog checksum config",
|
||||
"binlog checksum rerieval", "GTID Mode retrieval", "Master UUID retrieval",
|
||||
"Set Slave UUID", "Set Names latin1", "Set Names utf8", "select 1",
|
||||
"select version()", "Register slave", "Binlog Dump" };
|
||||
|
||||
#define BLRS_CREATED 0x0000
|
||||
#define BLRS_UNREGISTERED 0x0001
|
||||
#define BLRS_REGISTERED 0x0002
|
||||
#define BLRS_DUMPING 0x0003
|
||||
|
||||
#define BLRS_MAXSTATE 0x0003
|
||||
|
||||
static char *blrs_states[] = { "Created", "Unregistered", "Registered",
|
||||
"Sending binlogs" };
|
||||
|
||||
/**
|
||||
* Slave catch-up status
|
||||
*/
|
||||
#define CS_READING 0x0001
|
||||
#define CS_INNERLOOP 0x0002
|
||||
#define CS_UPTODATE 0x0004
|
||||
#define CS_EXPECTCB 0x0008
|
||||
#define CS_DIST 0x0010
|
||||
#define CS_DISTLATCH 0x0020
|
||||
|
||||
/**
|
||||
* MySQL protocol OpCodes needed for replication
|
||||
*/
|
||||
#define COM_QUIT 0x01
|
||||
#define COM_QUERY 0x03
|
||||
#define COM_REGISTER_SLAVE 0x15
|
||||
#define COM_BINLOG_DUMP 0x12
|
||||
|
||||
/**
|
||||
* Binlog event types
|
||||
*/
|
||||
#define START_EVENT_V3 0x01
|
||||
#define QUERY_EVENT 0x02
|
||||
#define STOP_EVENT 0x03
|
||||
#define ROTATE_EVENT 0x04
|
||||
#define INTVAR_EVENT 0x05
|
||||
#define LOAD_EVENT 0x06
|
||||
#define SLAVE_EVENT 0x07
|
||||
#define CREATE_FILE_EVENT 0x08
|
||||
#define APPEND_BLOCK_EVENT 0x09
|
||||
#define EXEC_LOAD_EVENT 0x0A
|
||||
#define DELETE_FILE_EVENT 0x0B
|
||||
#define NEW_LOAD_EVENT 0x0C
|
||||
#define RAND_EVENT 0x0D
|
||||
#define USER_VAR_EVENT 0x0E
|
||||
#define FORMAT_DESCRIPTION_EVENT 0x0F
|
||||
#define XID_EVENT 0x10
|
||||
#define BEGIN_LOAD_QUERY_EVENT 0x11
|
||||
#define EXECUTE_LOAD_QUERY_EVENT 0x12
|
||||
#define TABLE_MAP_EVENT 0x13
|
||||
#define WRITE_ROWS_EVENTv0 0x14
|
||||
#define UPDATE_ROWS_EVENTv0 0x15
|
||||
#define DELETE_ROWS_EVENTv0 0x16
|
||||
#define WRITE_ROWS_EVENTv1 0x17
|
||||
#define UPDATE_ROWS_EVENTv1 0x18
|
||||
#define DELETE_ROWS_EVENTv1 0x19
|
||||
#define INCIDENT_EVENT 0x1A
|
||||
#define HEARTBEAT_EVENT 0x1B
|
||||
#define IGNORABLE_EVENT 0x1C
|
||||
#define ROWS_QUERY_EVENT 0x1D
|
||||
#define WRITE_ROWS_EVENTv2 0x1E
|
||||
#define UPDATE_ROWS_EVENTv2 0x1F
|
||||
#define DELETE_ROWS_EVENTv2 0x20
|
||||
#define GTID_EVENT 0x21
|
||||
#define ANONYMOUS_GTID_EVENT 0x22
|
||||
#define PREVIOUS_GTIDS_EVENT 0x23
|
||||
|
||||
/**
|
||||
* Binlog event flags
|
||||
*/
|
||||
#define LOG_EVENT_BINLOG_IN_USE_F 0x0001
|
||||
#define LOG_EVENT_FORCED_ROTATE_F 0x0002
|
||||
#define LOG_EVENT_THREAD_SPECIFIC_F 0x0004
|
||||
#define LOG_EVENT_SUPPRESS_USE_F 0x0008
|
||||
#define LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F 0x0010
|
||||
#define LOG_EVENT_ARTIFICIAL_F 0x0020
|
||||
#define LOG_EVENT_RELAY_LOG_F 0x0040
|
||||
#define LOG_EVENT_IGNORABLE_F 0x0080
|
||||
#define LOG_EVENT_NO_FILTER_F 0x0100
|
||||
#define LOG_EVENT_MTS_ISOLATE_F 0x0200
|
||||
|
||||
/*
|
||||
* Externals within the router
|
||||
*/
|
||||
extern void blr_start_master(ROUTER_INSTANCE *);
|
||||
extern void blr_master_response(ROUTER_INSTANCE *, GWBUF *);
|
||||
extern void blr_master_reconnect(ROUTER_INSTANCE *);
|
||||
|
||||
extern int blr_slave_request(ROUTER_INSTANCE *, ROUTER_SLAVE *, GWBUF *);
|
||||
extern void blr_slave_rotate(ROUTER_SLAVE *slave, uint8_t *ptr);
|
||||
extern int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
|
||||
extern void blr_init_cache(ROUTER_INSTANCE *);
|
||||
|
||||
extern void blr_file_init(ROUTER_INSTANCE *);
|
||||
extern int blr_open_binlog(ROUTER_INSTANCE *, char *);
|
||||
extern void blr_write_binlog_record(ROUTER_INSTANCE *, REP_HEADER *,uint8_t *);
|
||||
extern void blr_file_rotate(ROUTER_INSTANCE *, char *, uint64_t);
|
||||
extern void blr_file_flush(ROUTER_INSTANCE *);
|
||||
extern GWBUF *blr_read_binlog(int, unsigned int, REP_HEADER *);
|
||||
#endif
|
@ -219,7 +219,7 @@ typedef enum mysql_server_cmd {
|
||||
MYSQL_COM_QUERY,
|
||||
MYSQL_COM_FIELD_LIST,
|
||||
MYSQL_COM_CREATE_DB,
|
||||
MYSQL_COM_DROP_DB,
|
||||
MYSQL_COM_DROP_DB,
|
||||
MYSQL_COM_REFRESH,
|
||||
MYSQL_COM_SHUTDOWN,
|
||||
MYSQL_COM_STATISTICS,
|
||||
|
114
server/modules/include/mysqlhint.h
Normal file
114
server/modules/include/mysqlhint.h
Normal file
@ -0,0 +1,114 @@
|
||||
#ifndef _MYSQLHINT_H
|
||||
#define _MYSQLHINT_H
|
||||
/*
|
||||
* This file is distributed as part of the SkySQL Gateway. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2013
|
||||
*/
|
||||
|
||||
/*
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 17-07-2014 Mark Riddoch Initial implementation
|
||||
*/
|
||||
#include <hint.h>
|
||||
|
||||
/* Parser tokens for the hint parser */
|
||||
typedef enum {
|
||||
TOK_MAXSCALE = 1,
|
||||
TOK_PREPARE,
|
||||
TOK_START,
|
||||
TOK_STOP,
|
||||
TOK_EQUAL,
|
||||
TOK_STRING,
|
||||
TOK_ROUTE,
|
||||
TOK_TO,
|
||||
TOK_MASTER,
|
||||
TOK_SLAVE,
|
||||
TOK_SERVER,
|
||||
TOK_EOL
|
||||
} TOKEN_VALUE;
|
||||
|
||||
/* The tokenising return type */
|
||||
typedef struct {
|
||||
TOKEN_VALUE token; // The token itself
|
||||
char *value; // The string version of the token
|
||||
} HINT_TOKEN;
|
||||
|
||||
/**
|
||||
* A named hint set.
|
||||
*
|
||||
* The hint "MaxScale name PREPARE ..." can be used to defined a named set
|
||||
* of hints that can be later applied.
|
||||
*/
|
||||
typedef struct namedhints {
|
||||
char *name; /*< Hintsets name */
|
||||
HINT *hints;
|
||||
struct namedhints
|
||||
*next; /*< Next named hint */
|
||||
} NAMEDHINTS;
|
||||
|
||||
/**
|
||||
* A session meaintains a stack of hints, the hints BEGIN and STOP are used
|
||||
* push hints on and off the stack. The current top of the stack is added to
|
||||
* any statement that does not explicitly define a hint for that signle
|
||||
* statement.
|
||||
*/
|
||||
typedef struct hintstack {
|
||||
HINT *hint;
|
||||
struct hintstack
|
||||
*next;
|
||||
} HINTSTACK;
|
||||
|
||||
/**
|
||||
* The hint instance structure
|
||||
*/
|
||||
typedef struct {
|
||||
int sessions;
|
||||
} HINT_INSTANCE;
|
||||
|
||||
/**
|
||||
* A hint parser session structure
|
||||
*/
|
||||
typedef struct {
|
||||
DOWNSTREAM down;
|
||||
GWBUF *request;
|
||||
int query_len;
|
||||
HINTSTACK *stack;
|
||||
NAMEDHINTS *named_hints; /* The named hints defined in this session */
|
||||
} HINT_SESSION;
|
||||
|
||||
/* Some useful macros */
|
||||
#define CURRENT_HINT(session) ((session)->stack ? \
|
||||
(session)->stack->hints : NULL)
|
||||
|
||||
/* Hint Parser State Machine */
|
||||
#define HS_INIT 0
|
||||
#define HS_ROUTE 1
|
||||
#define HS_ROUTE1 2
|
||||
#define HS_ROUTE_SERVER 3
|
||||
#define HS_NAME 4
|
||||
#define HS_PVALUE 5
|
||||
#define HS_PREPARE 6
|
||||
|
||||
|
||||
extern HINT *hint_parser(HINT_SESSION *session, GWBUF *request);
|
||||
NAMEDHINTS* free_named_hint(NAMEDHINTS* named_hint);
|
||||
HINTSTACK* free_hint_stack(HINTSTACK* hint_stack);
|
||||
|
||||
|
||||
|
||||
#endif
|
@ -67,10 +67,26 @@ typedef enum backend_type_t {
|
||||
BE_UNDEFINED=-1,
|
||||
BE_MASTER,
|
||||
BE_JOINED = BE_MASTER,
|
||||
BE_SLAVE,
|
||||
BE_SLAVE,
|
||||
BE_COUNT
|
||||
} backend_type_t;
|
||||
|
||||
struct router_instance;
|
||||
|
||||
typedef enum {
|
||||
TARGET_MASTER = 0x01,
|
||||
TARGET_SLAVE = 0x02,
|
||||
TARGET_NAMED_SERVER = 0x04,
|
||||
TARGET_ALL = 0x08,
|
||||
TARGET_RLAG_MAX = 0x10
|
||||
} route_target_t;
|
||||
|
||||
#define TARGET_IS_MASTER(t) (t & TARGET_MASTER)
|
||||
#define TARGET_IS_SLAVE(t) (t & TARGET_SLAVE)
|
||||
#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER)
|
||||
#define TARGET_IS_ALL(t) (t & TARGET_ALL)
|
||||
#define TARGET_IS_RLAG_MAX(t) (t & TARGET_RLAG_MAX)
|
||||
|
||||
typedef struct rses_property_st rses_property_t;
|
||||
typedef struct router_client_session ROUTER_CLIENT_SES;
|
||||
|
||||
@ -78,7 +94,8 @@ typedef enum rses_property_type_t {
|
||||
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_TMPTABLES,
|
||||
RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_TMPTABLES,
|
||||
RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1
|
||||
} rses_property_type_t;
|
||||
|
||||
@ -103,6 +120,7 @@ typedef enum select_criteria {
|
||||
/** default values for rwsplit configuration parameters */
|
||||
#define CONFIG_MAX_SLAVE_CONN 1
|
||||
#define CONFIG_MAX_SLAVE_RLAG -1 /*< not used */
|
||||
#define CONFIG_SQL_VARIABLES_IN TYPE_ALL
|
||||
|
||||
#define GET_SELECT_CRITERIA(s) \
|
||||
(strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \
|
||||
@ -143,7 +161,7 @@ struct rses_property_st {
|
||||
rses_property_type_t rses_prop_type;
|
||||
union rses_prop_data {
|
||||
mysql_sescmd_t sescmd;
|
||||
void* placeholder; /*< to be removed due new type */
|
||||
HASHTABLE* temp_tables;
|
||||
} rses_prop_data;
|
||||
rses_property_t* rses_prop_next; /*< next property of same type */
|
||||
#if defined(SS_DEBUG)
|
||||
@ -217,6 +235,7 @@ typedef struct rwsplit_config_st {
|
||||
int rw_max_slave_conn_count;
|
||||
select_criteria_t rw_slave_select_criteria;
|
||||
int rw_max_slave_replication_lag;
|
||||
target_t rw_use_sql_variables_in;
|
||||
} rwsplit_config_t;
|
||||
|
||||
|
||||
@ -261,6 +280,7 @@ struct router_client_session {
|
||||
#if defined(PREP_STMT_CACHING)
|
||||
HASHTABLE* rses_prep_stmt[2];
|
||||
#endif
|
||||
struct router_instance *router; /*< The router instance */
|
||||
struct router_client_session* next;
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t rses_chk_tail;
|
||||
@ -294,6 +314,8 @@ typedef struct router_instance {
|
||||
unsigned int bitvalue; /*< Required value of server->status */
|
||||
ROUTER_STATS stats; /*< Statistics for this router */
|
||||
struct router_instance* next; /*< Next router on the list */
|
||||
bool available_slaves;
|
||||
/*< The router has some slaves avialable */
|
||||
} ROUTER_INSTANCE;
|
||||
|
||||
#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \
|
||||
|
@ -17,6 +17,7 @@
|
||||
# Revision History
|
||||
# Date Who Description
|
||||
# 08/07/13 Mark Riddoch Initial implementation
|
||||
# 28/07/14 Massimiliano Pinto new monitor ndbcluster added
|
||||
|
||||
include ../../../build_gateway.inc
|
||||
include ../../../makefile.inc
|
||||
@ -38,11 +39,13 @@ MYSQLSRCS=mysql_mon.c
|
||||
MYSQLOBJ=$(MYSQLSRCS:.c=.o)
|
||||
GALERASRCS=galera_mon.c
|
||||
GALERAOBJ=$(GALERASRCS:.c=.o)
|
||||
SRCS=$(MYSQLSRCS)
|
||||
NDBCLUSTERSRCS=ndbcluster_mon.c
|
||||
NDBCLUSTEROBJ=$(NDBCLUSTERSRCS:.c=.o)
|
||||
SRCS=$(MYSQLSRCS) $(GALERASRCS) $(NDBCLUSTERSRCS)
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
LIBS=$(UTILSPATH)/skygw_utils.o -llog_manager \
|
||||
-L$(EMBEDDED_LIB) -lmysqld
|
||||
MODULES=libmysqlmon.so libgaleramon.so
|
||||
MODULES=libmysqlmon.so libgaleramon.so libndbclustermon.so
|
||||
|
||||
|
||||
all: $(MODULES)
|
||||
@ -53,6 +56,9 @@ libmysqlmon.so: $(MYSQLOBJ)
|
||||
libgaleramon.so: $(GALERAOBJ)
|
||||
$(CC) $(LDFLAGS) $(GALERAOBJ) $(LIBS) -o $@
|
||||
|
||||
libndbclustermon.so: $(NDBCLUSTEROBJ)
|
||||
$(CC) $(LDFLAGS) $(NDBCLUSTEROBJ) $(LIBS) -o $@
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
|
@ -69,7 +69,7 @@ static void defaultUsers(void *, char *, char *);
|
||||
static void diagnostics(DCB *, void *);
|
||||
static void setInterval(void *, unsigned long);
|
||||
|
||||
static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics, setInterval, NULL, NULL };
|
||||
static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics, setInterval, NULL, NULL, NULL };
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
@ -335,10 +335,18 @@ char *server_string;
|
||||
database->server->port,
|
||||
mysql_error(database->con))));
|
||||
server_clear_status(database->server, SERVER_RUNNING);
|
||||
if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
|
||||
{
|
||||
server_set_status(database->server, SERVER_AUTH_ERROR);
|
||||
}
|
||||
database->server->node_id = -1;
|
||||
free(dpwd);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_clear_status(database->server, SERVER_AUTH_ERROR);
|
||||
}
|
||||
free(dpwd);
|
||||
}
|
||||
|
||||
@ -351,7 +359,9 @@ char *server_string;
|
||||
/* get server version string */
|
||||
server_string = (char *)mysql_get_server_info(database->con);
|
||||
if (server_string) {
|
||||
database->server->server_string = strdup(server_string);
|
||||
database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1);
|
||||
if (database->server->server_string)
|
||||
strcpy(database->server->server_string, server_string);
|
||||
}
|
||||
|
||||
/* Check if the the Galera FSM shows this node is joined to the cluster */
|
||||
|
@ -40,6 +40,9 @@
|
||||
* 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
|
||||
* 28/08/14 Massimiliano Pinto Added detectStaleMaster feature: previous detected master will be used again, even if the replication is stopped.
|
||||
* This means both IO and SQL threads are not working on slaves.
|
||||
* This option is not enabled by default.
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -62,7 +65,7 @@ extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
static void monitorMain(void *);
|
||||
|
||||
static char *version_str = "V1.2.0";
|
||||
static char *version_str = "V1.3.0";
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_MONITOR,
|
||||
@ -80,6 +83,7 @@ static void diagnostics(DCB *, void *);
|
||||
static void setInterval(void *, unsigned long);
|
||||
static void defaultId(void *, unsigned long);
|
||||
static void replicationHeartbeat(void *, int);
|
||||
static void detectStaleMaster(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);
|
||||
@ -91,7 +95,7 @@ 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 };
|
||||
static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUser, diagnostics, setInterval, defaultId, replicationHeartbeat, detectStaleMaster };
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
@ -160,6 +164,7 @@ MYSQL_MONITOR *handle;
|
||||
handle->id = MONITOR_DEFAULT_ID;
|
||||
handle->interval = MONITOR_INTERVAL;
|
||||
handle->replicationHeartbeat = 0;
|
||||
handle->detectStaleMaster = 0;
|
||||
handle->master = NULL;
|
||||
spinlock_init(&handle->lock);
|
||||
}
|
||||
@ -306,6 +311,7 @@ char *sep;
|
||||
dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval);
|
||||
dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id);
|
||||
dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled");
|
||||
dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
|
||||
dcb_printf(dcb, "\tMonitored servers: ");
|
||||
|
||||
db = handle->databases;
|
||||
@ -394,6 +400,11 @@ char *server_string;
|
||||
* Store server NOT running in server and monitor server pending struct
|
||||
*
|
||||
*/
|
||||
if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
|
||||
{
|
||||
server_set_status(database->server, SERVER_AUTH_ERROR);
|
||||
monitor_set_pending_status(database, SERVER_AUTH_ERROR);
|
||||
}
|
||||
server_clear_status(database->server, SERVER_RUNNING);
|
||||
monitor_clear_pending_status(database, SERVER_RUNNING);
|
||||
|
||||
@ -403,8 +414,19 @@ char *server_string;
|
||||
monitor_clear_pending_status(database, SERVER_SLAVE);
|
||||
monitor_clear_pending_status(database, SERVER_MASTER);
|
||||
|
||||
/* Clean addition status too */
|
||||
server_clear_status(database->server, SERVER_SLAVE_OF_EXTERNAL_MASTER);
|
||||
server_clear_status(database->server, SERVER_STALE_STATUS);
|
||||
monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER);
|
||||
monitor_clear_pending_status(database, SERVER_STALE_STATUS);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_clear_status(database->server, SERVER_AUTH_ERROR);
|
||||
monitor_clear_pending_status(database, SERVER_AUTH_ERROR);
|
||||
}
|
||||
free(dpwd);
|
||||
}
|
||||
/* Store current status in both server and monitor server pending struct */
|
||||
@ -417,7 +439,9 @@ char *server_string;
|
||||
/* get server version string */
|
||||
server_string = (char *)mysql_get_server_info(database->con);
|
||||
if (server_string) {
|
||||
database->server->server_string = strdup(server_string);
|
||||
database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1);
|
||||
if (database->server->server_string)
|
||||
strcpy(database->server->server_string, server_string);
|
||||
}
|
||||
|
||||
/* get server_id form current node */
|
||||
@ -458,12 +482,20 @@ char *server_string;
|
||||
if (strncmp(row[12], "Yes", 3) == 0
|
||||
&& strncmp(row[13], "Yes", 3) == 0) {
|
||||
isslave += 1;
|
||||
|
||||
}
|
||||
|
||||
/* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building
|
||||
* the replication tree, slaves ids will be added to master(s) and we will have at least the
|
||||
* root master server.
|
||||
* Please note, there could be no slaves at all if Slave_SQL_Running == 'No'
|
||||
*/
|
||||
if (strncmp(row[12], "Yes", 3) == 0) {
|
||||
/* get Master_Server_Id values */
|
||||
master_id = atol(row[41]);
|
||||
if (master_id == 0)
|
||||
master_id = -1;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
/* store master_id of current node */
|
||||
@ -489,7 +521,14 @@ char *server_string;
|
||||
if (strncmp(row[10], "Yes", 3) == 0
|
||||
&& strncmp(row[11], "Yes", 3) == 0) {
|
||||
isslave = 1;
|
||||
}
|
||||
|
||||
/* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building
|
||||
* the replication tree, slaves ids will be added to master(s) and we will have at least the
|
||||
* root master server.
|
||||
* Please note, there could be no slaves at all if Slave_SQL_Running == 'No'
|
||||
*/
|
||||
if (strncmp(row[10], "Yes", 3) == 0) {
|
||||
/* get Master_Server_Id values */
|
||||
master_id = atol(row[39]);
|
||||
if (master_id == 0)
|
||||
@ -505,6 +544,7 @@ char *server_string;
|
||||
|
||||
/* Remove addition info */
|
||||
monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER);
|
||||
monitor_clear_pending_status(database, SERVER_STALE_STATUS);
|
||||
|
||||
/* Please note, the MASTER status and SERVER_SLAVE_OF_EXTERNAL_MASTER
|
||||
* will be assigned in the monitorMain() via get_replication_tree() routine
|
||||
@ -534,6 +574,7 @@ monitorMain(void *arg)
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr;
|
||||
int replication_heartbeat = handle->replicationHeartbeat;
|
||||
int detect_stale_master = handle->detectStaleMaster;
|
||||
int num_servers=0;
|
||||
MONITOR_SERVERS *root_master;
|
||||
|
||||
@ -545,6 +586,7 @@ MONITOR_SERVERS *root_master;
|
||||
"module. Exiting.\n")));
|
||||
return;
|
||||
}
|
||||
|
||||
handle->status = MONITOR_RUNNING;
|
||||
while (1)
|
||||
{
|
||||
@ -616,10 +658,19 @@ MONITOR_SERVERS *root_master;
|
||||
while (ptr)
|
||||
{
|
||||
if (! SERVER_IN_MAINT(ptr->server)) {
|
||||
ptr->server->status = ptr->pending_status;
|
||||
/* If "detect_stale_master" option is On, let's use the previus master */
|
||||
if (detect_stale_master && root_master && (!strcmp(ptr->server->name, root_master->server->name) && ptr->server->port == root_master->server->port) && (ptr->server->status & SERVER_MASTER) && !(ptr->pending_status & SERVER_MASTER)) {
|
||||
/* in this case server->status will not be updated from pending_status */
|
||||
LOGIF(LM, (skygw_log_write_flush(
|
||||
LOGFILE_MESSAGE, "[mysql_mon]: root server [%s:%i] is no longer Master, let's use it again even if it could be a stale master, you have been warned!", ptr->server->name, ptr->server->port)));
|
||||
/* Set the STALE bit for this server in server struct */
|
||||
server_set_status(ptr->server, SERVER_STALE_STATUS);
|
||||
} else {
|
||||
ptr->server->status = ptr->pending_status;
|
||||
}
|
||||
}
|
||||
ptr = ptr->next;
|
||||
}
|
||||
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))) {
|
||||
@ -665,19 +716,34 @@ setInterval(void *arg, unsigned long interval)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
memcpy(&handle->interval, &interval, sizeof(unsigned long));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable the MySQL Replication hearbeat, detecting slave lag behind master.
|
||||
*
|
||||
* @param arg The handle allocated by startMonitor
|
||||
* @param replicationHeartbeat To enable it 1, disable it with 0
|
||||
* @param arg The handle allocated by startMonitor
|
||||
* @param enable To enable it 1, disable it with 0
|
||||
*/
|
||||
static void
|
||||
replicationHeartbeat(void *arg, int replicationHeartbeat)
|
||||
replicationHeartbeat(void *arg, int enable)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
memcpy(&handle->replicationHeartbeat, &replicationHeartbeat, sizeof(int));
|
||||
memcpy(&handle->replicationHeartbeat, &enable, sizeof(int));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable the MySQL Replication Stale Master dectection, allowing a previouvsly detected master to still act as a Master.
|
||||
* This option must be enabled in order to keep the Master when the replication is stopped or removed from slaves.
|
||||
* If the replication is still stopped when MaxSclale is restarted no Master will be available.
|
||||
*
|
||||
* @param arg The handle allocated by startMonitor
|
||||
* @param enable To enable it 1, disable it with 0
|
||||
*/
|
||||
static void
|
||||
detectStaleMaster(void *arg, int enable)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
memcpy(&handle->detectStaleMaster, &enable, sizeof(int));
|
||||
}
|
||||
|
||||
static bool mon_status_changed(
|
||||
@ -1038,6 +1104,10 @@ static MONITOR_SERVERS *get_replication_tree(MYSQL_MONITOR *handle, int num_serv
|
||||
monitor_set_pending_status(master, SERVER_MASTER);
|
||||
} else {
|
||||
if (current->master_id > 0) {
|
||||
/* this server is slave of another server not in MaxScale configuration
|
||||
* we cannot use it as a real slave.
|
||||
*/
|
||||
monitor_clear_pending_status(ptr, SERVER_SLAVE);
|
||||
monitor_set_pending_status(ptr, SERVER_SLAVE_OF_EXTERNAL_MASTER);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
* 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
|
||||
* 28/08/14 Massimiliano Pinto Addition of detectStaleMaster
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -43,9 +44,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 */
|
||||
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;
|
||||
@ -54,17 +55,18 @@ 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 */
|
||||
MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */
|
||||
MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */
|
||||
int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||
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
|
||||
|
471
server/modules/monitor/ndbcluster_mon.c
Normal file
471
server/modules/monitor/ndbcluster_mon.c
Normal file
@ -0,0 +1,471 @@
|
||||
/*
|
||||
* This file is distributed as part of the SkySQL Gateway. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2013
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file ndbcluster_mon.c - A MySQL cluster SQL node monitor
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 25/07/14 Massimiliano Pinto Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <monitor.h>
|
||||
#include <mysqlmon.h>
|
||||
#include <thread.h>
|
||||
#include <mysql.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <secrets.h>
|
||||
#include <dcb.h>
|
||||
#include <modinfo.h>
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
static void monitorMain(void *);
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_MONITOR,
|
||||
MODULE_BETA_RELEASE,
|
||||
MONITOR_VERSION,
|
||||
"A MySQL cluster SQL node monitor"
|
||||
};
|
||||
|
||||
static void *startMonitor(void *);
|
||||
static void stopMonitor(void *);
|
||||
static void registerServer(void *, SERVER *);
|
||||
static void unregisterServer(void *, SERVER *);
|
||||
static void defaultUsers(void *, char *, char *);
|
||||
static void diagnostics(DCB *, void *);
|
||||
static void setInterval(void *, unsigned long);
|
||||
|
||||
static MONITOR_OBJECT MyObject = { startMonitor, stopMonitor, registerServer, unregisterServer, defaultUsers, diagnostics, setInterval, NULL, NULL, NULL };
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"Initialise the MySQL Cluster Monitor module %s.\n",
|
||||
version_str)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
MONITOR_OBJECT *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the instance of the monitor, returning a handle on the monitor.
|
||||
*
|
||||
* This function creates a thread to execute the actual monitoring.
|
||||
*
|
||||
* @return A handle to use when interacting with the monitor
|
||||
*/
|
||||
static void *
|
||||
startMonitor(void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle;
|
||||
|
||||
if (arg != NULL)
|
||||
{
|
||||
handle = (MYSQL_MONITOR *)arg;
|
||||
handle->shutdown = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL)
|
||||
return NULL;
|
||||
handle->databases = NULL;
|
||||
handle->shutdown = 0;
|
||||
handle->defaultUser = NULL;
|
||||
handle->defaultPasswd = NULL;
|
||||
handle->id = MONITOR_DEFAULT_ID;
|
||||
handle->interval = MONITOR_INTERVAL;
|
||||
spinlock_init(&handle->lock);
|
||||
}
|
||||
handle->tid = (THREAD)thread_start(monitorMain, handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a running monitor
|
||||
*
|
||||
* @param arg Handle on thr running monior
|
||||
*/
|
||||
static void
|
||||
stopMonitor(void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
|
||||
handle->shutdown = 1;
|
||||
thread_wait((void *)handle->tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a server that must be added to the monitored servers for
|
||||
* a monitoring module.
|
||||
*
|
||||
* @param arg A handle on the running monitor module
|
||||
* @param server The server to add
|
||||
*/
|
||||
static void
|
||||
registerServer(void *arg, SERVER *server)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr, *db;
|
||||
|
||||
if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL)
|
||||
return;
|
||||
db->server = server;
|
||||
db->con = NULL;
|
||||
db->next = NULL;
|
||||
spinlock_acquire(&handle->lock);
|
||||
if (handle->databases == NULL)
|
||||
handle->databases = db;
|
||||
else
|
||||
{
|
||||
ptr = handle->databases;
|
||||
while (ptr->next != NULL)
|
||||
ptr = ptr->next;
|
||||
ptr->next = db;
|
||||
}
|
||||
spinlock_release(&handle->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a server from those being monitored by a monitoring module
|
||||
*
|
||||
* @param arg A handle on the running monitor module
|
||||
* @param server The server to remove
|
||||
*/
|
||||
static void
|
||||
unregisterServer(void *arg, SERVER *server)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr, *lptr;
|
||||
|
||||
spinlock_acquire(&handle->lock);
|
||||
if (handle->databases == NULL)
|
||||
{
|
||||
spinlock_release(&handle->lock);
|
||||
return;
|
||||
}
|
||||
if (handle->databases->server == server)
|
||||
{
|
||||
ptr = handle->databases;
|
||||
handle->databases = handle->databases->next;
|
||||
free(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = handle->databases;
|
||||
while (ptr->next != NULL && ptr->next->server != server)
|
||||
ptr = ptr->next;
|
||||
if (ptr->next)
|
||||
{
|
||||
lptr = ptr->next;
|
||||
ptr->next = ptr->next->next;
|
||||
free(lptr);
|
||||
}
|
||||
}
|
||||
spinlock_release(&handle->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnostic interface
|
||||
*
|
||||
* @param dcb DCB to send output
|
||||
* @param arg The monitor handle
|
||||
*/
|
||||
static void
|
||||
diagnostics(DCB *dcb, void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
MONITOR_SERVERS *db;
|
||||
char *sep;
|
||||
|
||||
switch (handle->status)
|
||||
{
|
||||
case MONITOR_RUNNING:
|
||||
dcb_printf(dcb, "\tMonitor running\n");
|
||||
break;
|
||||
case MONITOR_STOPPING:
|
||||
dcb_printf(dcb, "\tMonitor stopping\n");
|
||||
break;
|
||||
case MONITOR_STOPPED:
|
||||
dcb_printf(dcb, "\tMonitor stopped\n");
|
||||
break;
|
||||
}
|
||||
|
||||
dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval);
|
||||
dcb_printf(dcb, "\tMonitored servers: ");
|
||||
|
||||
db = handle->databases;
|
||||
sep = "";
|
||||
while (db)
|
||||
{
|
||||
dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port);
|
||||
sep = ", ";
|
||||
db = db->next;
|
||||
}
|
||||
dcb_printf(dcb, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default username and password to use to monitor if the server does not
|
||||
* override this.
|
||||
*
|
||||
* @param arg The handle allocated by startMonitor
|
||||
* @param uname The default user name
|
||||
* @param passwd The default password
|
||||
*/
|
||||
static void
|
||||
defaultUsers(void *arg, char *uname, char *passwd)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
|
||||
if (handle->defaultUser)
|
||||
free(handle->defaultUser);
|
||||
if (handle->defaultPasswd)
|
||||
free(handle->defaultPasswd);
|
||||
handle->defaultUser = strdup(uname);
|
||||
handle->defaultPasswd = strdup(passwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor an individual server
|
||||
*
|
||||
* @param database The database to probe
|
||||
*/
|
||||
static void
|
||||
monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
MYSQL_RES *result;
|
||||
int num_fields;
|
||||
int isjoined = 0;
|
||||
char *uname = defaultUser, *passwd = defaultPasswd;
|
||||
unsigned long int server_version = 0;
|
||||
char *server_string;
|
||||
|
||||
if (database->server->monuser != NULL)
|
||||
{
|
||||
uname = database->server->monuser;
|
||||
passwd = database->server->monpw;
|
||||
}
|
||||
if (uname == NULL)
|
||||
return;
|
||||
|
||||
/* Don't even probe server flagged as in maintenance */
|
||||
if (SERVER_IN_MAINT(database->server))
|
||||
return;
|
||||
|
||||
if (database->con == NULL || mysql_ping(database->con) != 0)
|
||||
{
|
||||
char *dpwd = decryptPassword(passwd);
|
||||
int rc;
|
||||
int read_timeout = 1;
|
||||
|
||||
database->con = mysql_init(NULL);
|
||||
rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout);
|
||||
|
||||
if (mysql_real_connect(database->con, database->server->name,
|
||||
uname, dpwd, NULL, database->server->port, NULL, 0) == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Monitor was unable to connect to "
|
||||
"server %s:%d : \"%s\"",
|
||||
database->server->name,
|
||||
database->server->port,
|
||||
mysql_error(database->con))));
|
||||
server_clear_status(database->server, SERVER_RUNNING);
|
||||
if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
|
||||
{
|
||||
server_set_status(database->server, SERVER_AUTH_ERROR);
|
||||
}
|
||||
database->server->node_id = -1;
|
||||
free(dpwd);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_clear_status(database->server, SERVER_AUTH_ERROR);
|
||||
}
|
||||
free(dpwd);
|
||||
}
|
||||
|
||||
/* If we get this far then we have a working connection */
|
||||
server_set_status(database->server, SERVER_RUNNING);
|
||||
|
||||
/* get server version from current server */
|
||||
server_version = mysql_get_server_version(database->con);
|
||||
|
||||
/* get server version string */
|
||||
server_string = (char *)mysql_get_server_info(database->con);
|
||||
if (server_string) {
|
||||
database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1);
|
||||
if (database->server->server_string)
|
||||
strcpy(database->server->server_string, server_string);
|
||||
}
|
||||
|
||||
/* Check if the the SQL node is able to contact one or more data nodes */
|
||||
if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0
|
||||
&& (result = mysql_store_result(database->con)) != NULL)
|
||||
{
|
||||
num_fields = mysql_num_fields(result);
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
if (atoi(row[1]) > 0)
|
||||
isjoined = 1;
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
|
||||
/* Check the the SQL node id in the MySQL cluster */
|
||||
if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_cluster_node_id'") == 0
|
||||
&& (result = mysql_store_result(database->con)) != NULL)
|
||||
{
|
||||
long cluster_node_id = -1;
|
||||
num_fields = mysql_num_fields(result);
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
cluster_node_id = strtol(row[1], NULL, 10);
|
||||
if ((errno == ERANGE && (cluster_node_id == LONG_MAX
|
||||
|| cluster_node_id == LONG_MIN)) || (errno != 0 && cluster_node_id == 0))
|
||||
{
|
||||
cluster_node_id = -1;
|
||||
}
|
||||
database->server->node_id = cluster_node_id;
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
|
||||
if (isjoined) {
|
||||
server_set_status(database->server, SERVER_NDB);
|
||||
database->server->depth = 0;
|
||||
} else {
|
||||
server_clear_status(database->server, SERVER_NDB);
|
||||
database->server->depth = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry point for the monitoring module thread
|
||||
*
|
||||
* @param arg The handle of the monitor
|
||||
*/
|
||||
static void
|
||||
monitorMain(void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr;
|
||||
long master_id;
|
||||
|
||||
if (mysql_thread_init())
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Fatal : mysql_thread_init failed in monitor "
|
||||
"module. Exiting.\n")));
|
||||
return;
|
||||
}
|
||||
handle->status = MONITOR_RUNNING;
|
||||
while (1)
|
||||
{
|
||||
master_id = -1;
|
||||
|
||||
if (handle->shutdown)
|
||||
{
|
||||
handle->status = MONITOR_STOPPING;
|
||||
mysql_thread_end();
|
||||
handle->status = MONITOR_STOPPED;
|
||||
return;
|
||||
}
|
||||
|
||||
ptr = handle->databases;
|
||||
|
||||
while (ptr)
|
||||
{
|
||||
unsigned int prev_status = ptr->server->status;
|
||||
monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd);
|
||||
|
||||
if (ptr->server->status != prev_status ||
|
||||
SERVER_IS_DOWN(ptr->server))
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write_flush(
|
||||
LOGFILE_MESSAGE,
|
||||
"Backend server %s:%d state : %s",
|
||||
ptr->server->name,
|
||||
ptr->server->port,
|
||||
STRSRVSTATUS(ptr->server))));
|
||||
}
|
||||
|
||||
ptr = ptr->next;
|
||||
}
|
||||
|
||||
thread_millisleep(handle->interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the monitor sampling interval.
|
||||
*
|
||||
* @param arg The handle allocated by startMonitor
|
||||
* @param interval The interval to set in monitor struct, in milliseconds
|
||||
*/
|
||||
static void
|
||||
setInterval(void *arg, unsigned long interval)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
memcpy(&handle->interval, &interval, sizeof(unsigned long));
|
||||
}
|
@ -235,6 +235,7 @@ maxscaled_error(DCB *dcb)
|
||||
static int
|
||||
maxscaled_hangup(DCB *dcb)
|
||||
{
|
||||
dcb_close(dcb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -313,9 +314,11 @@ maxscaled_close(DCB *dcb)
|
||||
MAXSCALED *maxscaled = dcb->protocol;
|
||||
|
||||
if (maxscaled && maxscaled->username)
|
||||
{
|
||||
free(maxscaled->username);
|
||||
maxscaled->username = NULL;
|
||||
}
|
||||
|
||||
dcb_close(dcb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -422,7 +422,6 @@ static int gw_read_backend_event(DCB *dcb) {
|
||||
GWBUF *read_buffer = NULL;
|
||||
ROUTER_OBJECT *router = NULL;
|
||||
ROUTER *router_instance = NULL;
|
||||
void *rsession = NULL;
|
||||
SESSION *session = dcb->session;
|
||||
int nbytes_read = 0;
|
||||
|
||||
@ -497,7 +496,7 @@ static int gw_read_backend_event(DCB *dcb) {
|
||||
{
|
||||
if (nbytes_read < 5)
|
||||
{
|
||||
gwbuf_append(dcb->dcb_readqueue, read_buffer);
|
||||
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
|
||||
rc = 0;
|
||||
goto return_rc;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
* 28/02/2014 Massimiliano Pinto Added: client IPv4 in dcb->ipv4 and inet_ntop for string representation
|
||||
* 11/03/2014 Massimiliano Pinto Added: Unix socket support
|
||||
* 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake
|
||||
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
|
||||
*
|
||||
*/
|
||||
#include <skygw_utils.h>
|
||||
@ -798,7 +799,7 @@ int gw_read_client_event(
|
||||
}
|
||||
|
||||
/** succeed */
|
||||
if (rc == 1) {
|
||||
if (rc) {
|
||||
rc = 0; /**< here '0' means success */
|
||||
} else {
|
||||
GWBUF* errbuf;
|
||||
@ -985,6 +986,16 @@ int gw_MySQLListener(
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set permission for all users */
|
||||
if (chmod(config_bind, 0777) < 0) {
|
||||
fprintf(stderr,
|
||||
"\n* chmod failed for %s due error %i, %s.\n\n",
|
||||
config_bind,
|
||||
errno,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AF_INET:
|
||||
@ -1420,7 +1431,6 @@ static int route_by_statement(
|
||||
int rc = -1;
|
||||
GWBUF* packetbuf;
|
||||
#if defined(SS_DEBUG)
|
||||
gwbuf_type_t prevtype;
|
||||
GWBUF* tmpbuf;
|
||||
|
||||
tmpbuf = *p_readbuf;
|
||||
|
@ -51,6 +51,7 @@ MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so
|
||||
|
||||
|
||||
all: $(MODULES)
|
||||
(cd readwritesplit; make)
|
||||
|
||||
libtestroute.so: $(TESTOBJ)
|
||||
$(CC) $(LDFLAGS) $(TESTOBJ) $(LIBS) -o $@
|
||||
@ -88,19 +89,15 @@ install: $(MODULES)
|
||||
(cd readwritesplit; make DEST=$(DEST) install)
|
||||
|
||||
cleantests:
|
||||
$(MAKE) -C readwritesplit/test cleantests
|
||||
$(MAKE) -C test cleantests
|
||||
|
||||
|
||||
buildtests:
|
||||
$(MAKE) -C readwritesplit/test DEBUG=Y buildtests
|
||||
$(MAKE) -C test DEBUG=Y buildtests
|
||||
|
||||
|
||||
runtests:
|
||||
$(MAKE) -C test runtests
|
||||
$(MAKE) -C readwritesplit runtests
|
||||
|
||||
|
||||
testall:
|
||||
$(MAKE) -C test testall
|
||||
$(MAKE) -C readwritesplit testall
|
||||
|
||||
|
||||
include depend.mk
|
||||
|
65
server/modules/routing/binlog/Makefile
Normal file
65
server/modules/routing/binlog/Makefile
Normal file
@ -0,0 +1,65 @@
|
||||
# This file is distributed as part of the SkySQL Gateway. It is free
|
||||
# software: you can redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License as published by the Free Software Foundation,
|
||||
# version 2.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Copyright SkySQL Ab 2013
|
||||
#
|
||||
# Revision History
|
||||
# Date Who Description
|
||||
# 2/04/14 Mark Riddoch Initial framework put in place
|
||||
|
||||
include ../../../../build_gateway.inc
|
||||
|
||||
LOGPATH := $(ROOT_PATH)/log_manager
|
||||
UTILSPATH := $(ROOT_PATH)/utils
|
||||
QCLASSPATH := $(ROOT_PATH)/query_classifier
|
||||
|
||||
CC=cc
|
||||
CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include \
|
||||
-I$(LOGPATH) -I$(UTILSPATH) -I$(QCLASSPATH) \
|
||||
$(MYSQL_HEADERS) -Wall -g
|
||||
|
||||
include ../../../../makefile.inc
|
||||
|
||||
LDFLAGS=-shared -L$(LOGPATH) -L$(QCLASSPATH) -L$(EMBEDDED_LIB) \
|
||||
-Wl,-rpath,$(DEST)/lib \
|
||||
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) -Wl,-rpath,$(QCLASSPATH) \
|
||||
-Wl,-rpath,$(EMBEDDED_LIB)
|
||||
|
||||
SRCS=blr.c blr_master.c blr_cache.c blr_slave.c blr_file.c
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
LIBS=-lssl -pthread -llog_manager -lmysqld
|
||||
MODULES=libbinlogrouter.so
|
||||
|
||||
all: $(MODULES)
|
||||
|
||||
$(MODULES): $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(OBJ) $(UTILSPATH)/skygw_utils.o $(LIBS) -o $@
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) $(MODULES)
|
||||
|
||||
tags:
|
||||
ctags $(SRCS) $(HDRS)
|
||||
|
||||
depend:
|
||||
@rm -f depend.mk
|
||||
cc -M $(CFLAGS) $(SRCS) > depend.mk
|
||||
|
||||
install: $(MODULES)
|
||||
install -D $(MODULES) $(DEST)/MaxScale/modules
|
||||
|
||||
include depend.mk
|
53
server/modules/routing/binlog/README
Normal file
53
server/modules/routing/binlog/README
Normal file
@ -0,0 +1,53 @@
|
||||
The binlog router is not a "normal" MaxScale router, it is not
|
||||
designed to be used to route client requests to a database in the
|
||||
usual proxy fashion. Rather it is designed to allow MaxScale to be
|
||||
used as a relay server in a MySQL replication environment.
|
||||
|
||||
In this environment MaxScale sits between a master MySQL server and
|
||||
a set of slave servers. The slaves servers execute a change master
|
||||
to the MaxScale server, otehrwise they are configured in exactly
|
||||
the same way as a normal MySQL slave server.
|
||||
|
||||
The master server configuration is unaltered, it simply sees a
|
||||
single slave server.
|
||||
|
||||
MaxScale is configured as usual, with a service definition that
|
||||
references the binlog router. The major configuration option to
|
||||
consider is the router_options paramter, in the binlog router this
|
||||
provides the binlog specific configuration parameters.
|
||||
|
||||
uuid=
|
||||
This is the UUID that MaxScale uses when it connects
|
||||
to the real master. It will report the master's
|
||||
UUID to slaves that connect to it.
|
||||
|
||||
server-id=
|
||||
The server-id that MaxScale uses when it connects
|
||||
to the real master server. Again it will reports
|
||||
the master's server-id to the slaves that connect
|
||||
to it.
|
||||
user=
|
||||
The user that MaxScale uses to login to the real
|
||||
master
|
||||
password=
|
||||
The password that MaxScale uses to login to the
|
||||
real master
|
||||
master-id=
|
||||
The server-id of the real master. MaxScale should
|
||||
get this by sending a query, but at the moment it
|
||||
is in the configuration file for ease of implementation
|
||||
|
||||
|
||||
An example binlog service configuration is shown below:
|
||||
|
||||
[Binlog Service]
|
||||
type=service
|
||||
router=binlogrouter
|
||||
servers=master
|
||||
router_options=uuid=f12fcb7f-b97b-11e3-bc5e-0401152c4c22,server-id=3,user=repl,password=slavepass,master-id=1
|
||||
user=maxscale
|
||||
passwd=Mhu87p2D
|
||||
|
||||
The servers list for a binlog router service should contain just
|
||||
the master server. In future a list will be given and the monitor
|
||||
used to determine which server is the current master server.
|
13
server/modules/routing/binlog/STATUS
Normal file
13
server/modules/routing/binlog/STATUS
Normal file
@ -0,0 +1,13 @@
|
||||
The binlog router contained here is a prototype implementation and
|
||||
should not be consider as production ready.
|
||||
|
||||
The router has been written and tested with MySQL 5.6 as a reference
|
||||
for the replication behaviour, more investigation and implementation
|
||||
is likely to be needed in order to use other versions of MySQL,
|
||||
MariaDB or Percona Server.
|
||||
|
||||
To Do List:
|
||||
|
||||
1. The router does not implement the replication heartbeat mechanism.
|
||||
|
||||
2. Performance measurements have yet to be made.
|
770
server/modules/routing/binlog/blr.c
Normal file
770
server/modules/routing/binlog/blr.c
Normal file
@ -0,0 +1,770 @@
|
||||
/*
|
||||
* 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 blr.c - binlog router, allows MaxScale to act as an intermediatory for replication
|
||||
*
|
||||
* The binlog router is designed to be used in replication environments to
|
||||
* increase the replication fanout of a master server. It provides a transparant
|
||||
* mechanism to read the binlog entries for multiple slaves while requiring
|
||||
* only a single connection to the actual master to support the slaves.
|
||||
*
|
||||
* The current prototype implement is designed to support MySQL 5.6 and has
|
||||
* a number of limitations. This prototype is merely a proof of concept and
|
||||
* should not be considered production ready.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 02/04/2014 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <service.h>
|
||||
#include <server.h>
|
||||
#include <router.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <blr.h>
|
||||
#include <dcb.h>
|
||||
#include <spinlock.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <skygw_types.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
#include <mysql_client_server_protocol.h>
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
static char *version_str = "V1.0.6";
|
||||
|
||||
/* 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 routeQuery(ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(ROUTER *instance, DCB *dcb);
|
||||
static void clientReply(
|
||||
ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *queue,
|
||||
DCB *backend_dcb);
|
||||
static void errorReply(
|
||||
ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *message,
|
||||
DCB *backend_dcb,
|
||||
error_action_t action,
|
||||
bool *succp);
|
||||
static uint8_t getCapabilities (ROUTER* inst, void* router_session);
|
||||
|
||||
|
||||
/** The module object definition */
|
||||
static ROUTER_OBJECT MyObject = {
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
routeQuery,
|
||||
diagnostics,
|
||||
clientReply,
|
||||
errorReply,
|
||||
getCapabilities
|
||||
};
|
||||
|
||||
static bool rses_begin_locked_router_action(ROUTER_SLAVE *);
|
||||
static void rses_end_locked_router_action(ROUTER_SLAVE *);
|
||||
|
||||
static SPINLOCK instlock;
|
||||
static ROUTER_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 binlog 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 MaxScale.
|
||||
*
|
||||
* The process of creating the instance causes the router to register
|
||||
* with the master server and begin replication of the binlogs from
|
||||
* the master server to MaxScale.
|
||||
*
|
||||
* @param service The service this router is being create for
|
||||
* @param options An array of options for this query router
|
||||
*
|
||||
* @return The instance data for this new instance
|
||||
*/
|
||||
static ROUTER *
|
||||
createInstance(SERVICE *service, char **options)
|
||||
{
|
||||
ROUTER_INSTANCE *inst;
|
||||
char *value;
|
||||
int i;
|
||||
|
||||
if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(&inst->stats, 0, sizeof(ROUTER_STATS));
|
||||
memset(&inst->saved_master, 0, sizeof(MASTER_RESPONSES));
|
||||
|
||||
inst->service = service;
|
||||
spinlock_init(&inst->lock);
|
||||
|
||||
inst->low_water = DEF_LOW_WATER;
|
||||
inst->high_water = DEF_HIGH_WATER;
|
||||
|
||||
/*
|
||||
* We only support one server behind this router, since the server is
|
||||
* the master from which we replicate binlog records. Therefore check
|
||||
* that only one server has been defined.
|
||||
*
|
||||
* A later improvement will be to define multiple servers and have the
|
||||
* router use the information that is supplied by the monitor to find
|
||||
* which of these servers is currently the master and replicate from
|
||||
* that server.
|
||||
*/
|
||||
if (service->databases == NULL || service->databases->nextdb != NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Exactly one database server may be "
|
||||
"for use with the binlog router.")));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process the options.
|
||||
* We have an array of attrbute values passed to us that we must
|
||||
* examine. Supported attributes are:
|
||||
* uuid=
|
||||
* server-id=
|
||||
* user=
|
||||
* password=
|
||||
* master-id=
|
||||
* filestem=
|
||||
* lowwater=
|
||||
* highwater=
|
||||
*/
|
||||
if (options)
|
||||
{
|
||||
for (i = 0; options[i]; i++)
|
||||
{
|
||||
if ((value = strchr(options[i], '=')) == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR, "Warning : Unsupported router "
|
||||
"option %s for binlog router.",
|
||||
options[i])));
|
||||
}
|
||||
else
|
||||
{
|
||||
*value = 0;
|
||||
value++;
|
||||
if (strcmp(options[i], "uuid") == 0)
|
||||
{
|
||||
inst->uuid = strdup(value);
|
||||
}
|
||||
else if (strcmp(options[i], "server-id") == 0)
|
||||
{
|
||||
inst->serverid = atoi(value);
|
||||
}
|
||||
else if (strcmp(options[i], "user") == 0)
|
||||
{
|
||||
inst->user = strdup(value);
|
||||
}
|
||||
else if (strcmp(options[i], "password") == 0)
|
||||
{
|
||||
inst->password = strdup(value);
|
||||
}
|
||||
else if (strcmp(options[i], "master-id") == 0)
|
||||
{
|
||||
inst->masterid = atoi(value);
|
||||
}
|
||||
else if (strcmp(options[i], "filestem") == 0)
|
||||
{
|
||||
inst->fileroot = strdup(value);
|
||||
}
|
||||
else if (strcmp(options[i], "lowwater") == 0)
|
||||
{
|
||||
inst->low_water = atoi(value);
|
||||
}
|
||||
else if (strcmp(options[i], "highwater") == 0)
|
||||
{
|
||||
inst->high_water = atoi(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Warning : Unsupported router "
|
||||
"option %s for binlog router.",
|
||||
options[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inst->fileroot == NULL)
|
||||
inst->fileroot = strdup(BINLOG_NAME_ROOT);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
inst->active_logs = 0;
|
||||
inst->reconnect_pending = 0;
|
||||
inst->handling_threads = 0;
|
||||
inst->residual = NULL;
|
||||
inst->slaves = NULL;
|
||||
inst->next = NULL;
|
||||
|
||||
/*
|
||||
* Initialise the binlog file and position
|
||||
*/
|
||||
blr_file_init(inst);
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Binlog router: current binlog file is: %s, current position %u\n",
|
||||
inst->binlog_name, inst->binlog_position)));
|
||||
|
||||
/*
|
||||
* Initialise the binlog cache for this router instance
|
||||
*/
|
||||
blr_init_cache(inst);
|
||||
|
||||
/*
|
||||
* Now start the replication from the master to MaxScale
|
||||
*/
|
||||
blr_start_master(inst);
|
||||
|
||||
return (ROUTER *)inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new session with this instance of the router.
|
||||
*
|
||||
* In the case of the binlog router a new session equates to a new slave
|
||||
* connecting to MaxScale and requesting binlog records. We need to go
|
||||
* through the slave registration process for this new slave.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_SLAVE *slave;
|
||||
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"binlog router: %lu [newSession] new router session with "
|
||||
"session %p, and inst %p.",
|
||||
pthread_self(),
|
||||
session,
|
||||
inst)));
|
||||
|
||||
|
||||
if ((slave = (ROUTER_SLAVE *)calloc(1, sizeof(ROUTER_SLAVE))) == NULL)
|
||||
{
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Insufficient memory to create new slave session for binlog router")));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if defined(SS_DEBUG)
|
||||
slave->rses_chk_top = CHK_NUM_ROUTER_SES;
|
||||
slave->rses_chk_tail = CHK_NUM_ROUTER_SES;
|
||||
#endif
|
||||
|
||||
memset(&slave->stats, 0, sizeof(SLAVE_STATS));
|
||||
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 = inst;
|
||||
|
||||
/**
|
||||
* Add this session to the list of active sessions.
|
||||
*/
|
||||
spinlock_acquire(&inst->lock);
|
||||
slave->next = inst->slaves;
|
||||
inst->slaves = slave;
|
||||
spinlock_release(&inst->lock);
|
||||
|
||||
CHK_CLIENT_RSES(slave);
|
||||
|
||||
return (void *)slave;
|
||||
}
|
||||
|
||||
/**
|
||||
* The session is no longer required. Shutdown all operation and free memory
|
||||
* associated with this session. In this case a single session is associated
|
||||
* to a slave of MaxScale. Therefore this is called when that slave is no
|
||||
* longer active and should remove of reference to that slave, free memory
|
||||
* and prevent any further forwarding of binlog records to that slave.
|
||||
*
|
||||
* Parameters:
|
||||
* @param router_instance The instance of the router
|
||||
* @param router_cli_ses The particular session to free
|
||||
*
|
||||
*/
|
||||
static void freeSession(
|
||||
ROUTER* router_instance,
|
||||
void* router_client_ses)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_client_ses;
|
||||
int prev_val;
|
||||
|
||||
prev_val = atomic_add(&router->stats.n_slaves, -1);
|
||||
ss_dassert(prev_val > 0);
|
||||
|
||||
/*
|
||||
* Remove the slave session form the list of slaves that are using the
|
||||
* router currently.
|
||||
*/
|
||||
spinlock_acquire(&router->lock);
|
||||
if (router->slaves == slave) {
|
||||
router->slaves = slave->next;
|
||||
} else {
|
||||
ROUTER_SLAVE *ptr = router->slaves;
|
||||
|
||||
while (ptr != NULL && ptr->next != slave) {
|
||||
ptr = ptr->next;
|
||||
}
|
||||
|
||||
if (ptr != NULL) {
|
||||
ptr->next = slave->next;
|
||||
}
|
||||
}
|
||||
spinlock_release(&router->lock);
|
||||
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [freeSession] Unlinked router_client_session %p from "
|
||||
"router %p. Connections : %d. ",
|
||||
pthread_self(),
|
||||
slave,
|
||||
router,
|
||||
prev_val-1)));
|
||||
|
||||
if (slave->hostname)
|
||||
free(slave->hostname);
|
||||
if (slave->user)
|
||||
free(slave->user);
|
||||
if (slave->passwd)
|
||||
free(slave->passwd);
|
||||
free(slave);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
|
||||
|
||||
if (slave == NULL)
|
||||
{
|
||||
/*
|
||||
* We must be closing the master session.
|
||||
*
|
||||
* TODO: Handle closure of master session
|
||||
*/
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR, "Binlog router close session with master")));
|
||||
blr_master_reconnect(router);
|
||||
return;
|
||||
}
|
||||
CHK_CLIENT_RSES(slave);
|
||||
/**
|
||||
* Lock router client session for secure read and update.
|
||||
*/
|
||||
if (rses_begin_locked_router_action(slave))
|
||||
{
|
||||
/* decrease server registered slaves counter */
|
||||
atomic_add(&router->stats.n_registered, -1);
|
||||
|
||||
/*
|
||||
* Mark the slave as unregistered to prevent the forwarding
|
||||
* of any more binlog records to this slave.
|
||||
*/
|
||||
slave->state = BLRS_UNREGISTERED;
|
||||
|
||||
/* Unlock */
|
||||
rses_end_locked_router_action(slave);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have data from the client, this is likely to be packets related to
|
||||
* the registration of the slave to receive binlog records. Unlike most
|
||||
* MaxScale routers there is no forwarding to the backend database, merely
|
||||
* the return of either predefined server responses that have been cached
|
||||
* or binlog records.
|
||||
*
|
||||
* @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
|
||||
routeQuery(ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
|
||||
|
||||
return blr_slave_request(router, slave, queue);
|
||||
}
|
||||
|
||||
static char *event_names[] = {
|
||||
"Invalid", "Start Event V3", "Query Event", "Stop Event", "Rotate Event",
|
||||
"Integer Session Variable", "Load Event", "Slave Event", "Create File Event",
|
||||
"Append Block Event", "Exec Load Event", "Delete File Event",
|
||||
"New Load Event", "Rand Event", "User Variable Event", "Format Description Event",
|
||||
"Transaction ID Event (2 Phase Commit)", "Begin Load Query Event",
|
||||
"Execute Load Query Event", "Table Map Event", "Write Rows Event (v0)",
|
||||
"Update Rows Event (v0)", "Delete Rows Event (v0)", "Write Rows Event (v1)",
|
||||
"Update Rows Event (v1)", "Delete Rows Event (v1)", "Incident Event",
|
||||
"Heartbeat Event", "Ignorable Event", "Rows Query Event", "Write Rows Event (v2)",
|
||||
"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
|
||||
*
|
||||
* @param instance Instance of the router
|
||||
* @param dcb DCB to send diagnostics to
|
||||
*/
|
||||
static void
|
||||
diagnostics(ROUTER *router, DCB *dcb)
|
||||
{
|
||||
ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)router;
|
||||
ROUTER_SLAVE *session;
|
||||
int i = 0;
|
||||
char buf[40];
|
||||
struct tm tm;
|
||||
|
||||
spinlock_acquire(&router_inst->lock);
|
||||
session = router_inst->slaves;
|
||||
while (session)
|
||||
{
|
||||
i++;
|
||||
session = session->next;
|
||||
}
|
||||
spinlock_release(&router_inst->lock);
|
||||
|
||||
dcb_printf(dcb, "\tMaster connection DCB: %p\n",
|
||||
router_inst->master);
|
||||
dcb_printf(dcb, "\tMaster connection state: %s\n",
|
||||
blrm_states[router_inst->master_state]);
|
||||
|
||||
localtime_r(&router_inst->stats.lastReply, &tm);
|
||||
asctime_r(&tm, buf);
|
||||
|
||||
dcb_printf(dcb, "\tNumber of master connects: %d\n",
|
||||
router_inst->stats.n_masterstarts);
|
||||
dcb_printf(dcb, "\tNumber of delayed reconnects: %d\n",
|
||||
router_inst->stats.n_delayedreconnects);
|
||||
dcb_printf(dcb, "\tCurrent binlog file: %s\n",
|
||||
router_inst->binlog_name);
|
||||
dcb_printf(dcb, "\tCurrent binlog position: %u\n",
|
||||
router_inst->binlog_position);
|
||||
dcb_printf(dcb, "\tNumber of slave servers: %u\n",
|
||||
router_inst->stats.n_slaves);
|
||||
dcb_printf(dcb, "\tNumber of binlog events received: %u\n",
|
||||
router_inst->stats.n_binlogs);
|
||||
dcb_printf(dcb, "\tNumber of fake binlog events: %u\n",
|
||||
router_inst->stats.n_fakeevents);
|
||||
dcb_printf(dcb, "\tNumber of artificial binlog events: %u\n",
|
||||
router_inst->stats.n_artificial);
|
||||
dcb_printf(dcb, "\tNumber of binlog events in error: %u\n",
|
||||
router_inst->stats.n_binlog_errors);
|
||||
dcb_printf(dcb, "\tNumber of binlog rotate events: %u\n",
|
||||
router_inst->stats.n_rotates);
|
||||
dcb_printf(dcb, "\tNumber of binlog cache hits: %u\n",
|
||||
router_inst->stats.n_cachehits);
|
||||
dcb_printf(dcb, "\tNumber of binlog cache misses: %u\n",
|
||||
router_inst->stats.n_cachemisses);
|
||||
dcb_printf(dcb, "\tNumber of heartbeat events: %u\n",
|
||||
router_inst->stats.n_heartbeats);
|
||||
dcb_printf(dcb, "\tNumber of packets received: %u\n",
|
||||
router_inst->stats.n_reads);
|
||||
dcb_printf(dcb, "\tNumber of residual data packets: %u\n",
|
||||
router_inst->stats.n_residuals);
|
||||
dcb_printf(dcb, "\tAverage events per packet %.1f\n",
|
||||
(double)router_inst->stats.n_binlogs / router_inst->stats.n_reads);
|
||||
dcb_printf(dcb, "\tLast event from master at: %s",
|
||||
buf);
|
||||
dcb_printf(dcb, "\t (%d seconds ago)\n",
|
||||
time(0) - router_inst->stats.lastReply);
|
||||
dcb_printf(dcb, "\tLast event from master: 0x%x\n",
|
||||
router_inst->lastEventReceived);
|
||||
if (router_inst->active_logs)
|
||||
dcb_printf(dcb, "\tRouter processing binlog records\n");
|
||||
if (router_inst->reconnect_pending)
|
||||
dcb_printf(dcb, "\tRouter pending reconnect to master\n");
|
||||
dcb_printf(dcb, "\tEvents received:\n");
|
||||
for (i = 0; i < 0x24; i++)
|
||||
{
|
||||
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");
|
||||
spinlock_acquire(&router_inst->lock);
|
||||
session = router_inst->slaves;
|
||||
while (session)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client Reply routine - in this case this is a message from the
|
||||
* master server, It should be sent to the state machine that manages
|
||||
* master packets as it may be binlog records or part of the registration
|
||||
* handshake that takes part during connection establishment.
|
||||
*
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session
|
||||
* @param master_dcb The DCB for the connection to the master
|
||||
* @param queue The GWBUF with reply data
|
||||
*/
|
||||
static void
|
||||
clientReply(ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb)
|
||||
{
|
||||
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
|
||||
|
||||
atomic_add(&router->stats.n_reads, 1);
|
||||
blr_master_response(router, queue);
|
||||
router->stats.lastReply = time(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Reply routine
|
||||
*
|
||||
* The routine will reply to client errors and/or closing the session
|
||||
* or try to open a new backend connection.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session
|
||||
* @param message The error message to reply
|
||||
* @param backend_dcb The backend DCB
|
||||
* @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION
|
||||
* @param succp Result of action
|
||||
*
|
||||
*/
|
||||
static void
|
||||
errorReply(ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb, error_action_t action, bool *succp)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR, "Erorr Reply '%s'", message)));
|
||||
*succp = false;
|
||||
}
|
||||
|
||||
/** to be inline'd */
|
||||
/**
|
||||
* @node Acquires lock to router client session if it is not closed.
|
||||
*
|
||||
* Parameters:
|
||||
* @param rses - in, use
|
||||
*
|
||||
*
|
||||
* @return true if router session was not closed. If return value is true
|
||||
* it means that router is locked, and must be unlocked later. False, if
|
||||
* router was closed before lock was acquired.
|
||||
*
|
||||
*
|
||||
* @details (write detailed description here)
|
||||
*
|
||||
*/
|
||||
static bool rses_begin_locked_router_action(ROUTER_SLAVE *rses)
|
||||
{
|
||||
bool succp = false;
|
||||
|
||||
CHK_CLIENT_RSES(rses);
|
||||
|
||||
spinlock_acquire(&rses->rses_lock);
|
||||
succp = true;
|
||||
|
||||
return succp;
|
||||
}
|
||||
|
||||
/** to be inline'd */
|
||||
/**
|
||||
* @node Releases router client session lock.
|
||||
*
|
||||
* Parameters:
|
||||
* @param rses - <usage>
|
||||
* <description>
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*
|
||||
* @details (write detailed description here)
|
||||
*
|
||||
*/
|
||||
static void rses_end_locked_router_action(ROUTER_SLAVE * rses)
|
||||
{
|
||||
CHK_CLIENT_RSES(rses);
|
||||
spinlock_release(&rses->rses_lock);
|
||||
}
|
||||
|
||||
|
||||
static uint8_t getCapabilities(ROUTER *inst, void *router_session)
|
||||
{
|
||||
return 0;
|
||||
}
|
69
server/modules/routing/binlog/blr_cache.c
Normal file
69
server/modules/routing/binlog/blr_cache.c
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 blr_cache.c - binlog router cache, manage the binlog cache
|
||||
*
|
||||
* The binlog router is designed to be used in replication environments to
|
||||
* increase the replication fanout of a master server. It provides a transparant
|
||||
* mechanism to read the binlog entries for multiple slaves while requiring
|
||||
* only a single connection to the actual master to support the slaves.
|
||||
*
|
||||
* The current prototype implement is designed to support MySQL 5.6 and has
|
||||
* a number of limitations. This prototype is merely a proof of concept and
|
||||
* should not be considered production ready.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 07/04/2014 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <service.h>
|
||||
#include <server.h>
|
||||
#include <router.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <blr.h>
|
||||
#include <dcb.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
#include <skygw_types.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the cache for this instanceof the binlog router. As a side
|
||||
* effect also determine the binlog file to read and the position to read
|
||||
* from.
|
||||
*
|
||||
* @param router The router instance
|
||||
*/
|
||||
void
|
||||
blr_init_cache(ROUTER_INSTANCE *router)
|
||||
{
|
||||
}
|
346
server/modules/routing/binlog/blr_file.c
Normal file
346
server/modules/routing/binlog/blr_file.c
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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 blr_file.c - contains code for the router binlog file management
|
||||
*
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 14/04/2014 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <service.h>
|
||||
#include <server.h>
|
||||
#include <router.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <blr.h>
|
||||
#include <dcb.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
#include <skygw_types.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
static void blr_file_create(ROUTER_INSTANCE *router, char *file);
|
||||
static void blr_file_append(ROUTER_INSTANCE *router, char *file);
|
||||
static uint32_t extract_field(uint8_t *src, int bits);
|
||||
|
||||
/**
|
||||
* Initialise the binlog file for this instance. MaxScale will look
|
||||
* for all the binlogs that it has on local disk, determien the next
|
||||
* binlog to use and initialise it for writing, determining the
|
||||
* next record to be fetched from the real master.
|
||||
*
|
||||
* @param router The router instance this defines the master for this replication chain
|
||||
*/
|
||||
void
|
||||
blr_file_init(ROUTER_INSTANCE *router)
|
||||
{
|
||||
char *ptr, path[1024], filename[1050];
|
||||
int file_found, n = 1;
|
||||
int root_len, i;
|
||||
DIR *dirp;
|
||||
struct dirent *dp;
|
||||
|
||||
strcpy(path, "/usr/local/skysql/MaxScale");
|
||||
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
|
||||
{
|
||||
strcpy(path, ptr);
|
||||
}
|
||||
strcat(path, "/");
|
||||
strcat(path, router->service->name);
|
||||
|
||||
if (access(path, R_OK) == -1)
|
||||
mkdir(path, 0777);
|
||||
|
||||
/* First try to find a binlog file number by reading the directory */
|
||||
root_len = strlen(router->fileroot);
|
||||
dirp = opendir(path);
|
||||
while ((dp = readdir(dirp)) != NULL)
|
||||
{
|
||||
if (strncmp(dp->d_name, router->fileroot, root_len) == 0)
|
||||
{
|
||||
i = atoi(dp->d_name + root_len + 1);
|
||||
if (i > n)
|
||||
n = i;
|
||||
}
|
||||
}
|
||||
closedir(dirp);
|
||||
|
||||
|
||||
file_found = 0;
|
||||
do {
|
||||
sprintf(filename, "%s/" BINLOG_NAMEFMT, path, router->fileroot, n);
|
||||
if (access(filename, R_OK) != -1)
|
||||
{
|
||||
file_found = 1;
|
||||
n++;
|
||||
}
|
||||
else
|
||||
file_found = 0;
|
||||
} while (file_found);
|
||||
n--;
|
||||
|
||||
if (n == 0) // No binlog files found
|
||||
{
|
||||
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, 1);
|
||||
blr_file_create(router, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, n);
|
||||
blr_file_append(router, filename);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
blr_file_rotate(ROUTER_INSTANCE *router, char *file, uint64_t pos)
|
||||
{
|
||||
blr_file_create(router, file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new binlog file for the router to use.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param file The binlog file name
|
||||
*/
|
||||
static void
|
||||
blr_file_create(ROUTER_INSTANCE *router, char *file)
|
||||
{
|
||||
char *ptr, path[1024];
|
||||
int fd;
|
||||
unsigned char magic[] = BINLOG_MAGIC;
|
||||
|
||||
strcpy(path, "/usr/local/skysql/MaxScale");
|
||||
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
|
||||
{
|
||||
strcpy(path, ptr);
|
||||
}
|
||||
strcat(path, "/");
|
||||
strcat(path, router->service->name);
|
||||
strcat(path, "/");
|
||||
strcat(path, file);
|
||||
|
||||
if ((fd = open(path, O_RDWR|O_CREAT, 0666)) != -1)
|
||||
{
|
||||
write(fd, magic, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to create binlog file %s\n", path)));
|
||||
}
|
||||
fsync(fd);
|
||||
close(router->binlog_fd);
|
||||
strcpy(router->binlog_name, file);
|
||||
router->binlog_position = 4; /* Initial position after the magic number */
|
||||
router->binlog_fd = fd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare an existing binlog file to be appened to.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param file The binlog file name
|
||||
*/
|
||||
static void
|
||||
blr_file_append(ROUTER_INSTANCE *router, char *file)
|
||||
{
|
||||
char *ptr, path[1024];
|
||||
int fd;
|
||||
|
||||
strcpy(path, "/usr/local/skysql/MaxScale");
|
||||
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
|
||||
{
|
||||
strcpy(path, ptr);
|
||||
}
|
||||
strcat(path, "/");
|
||||
strcat(path, router->service->name);
|
||||
strcat(path, "/");
|
||||
strcat(path, file);
|
||||
|
||||
if ((fd = open(path, O_RDWR|O_APPEND, 0666)) == -1)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to open binlog file %s for append.\n",
|
||||
path)));
|
||||
return;
|
||||
}
|
||||
fsync(fd);
|
||||
close(router->binlog_fd);
|
||||
strcpy(router->binlog_name, file);
|
||||
router->binlog_position = lseek(fd, 0L, SEEK_END);
|
||||
router->binlog_fd = fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a binlog entry to disk.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param buf The binlog record
|
||||
* @param len The length of the binlog record
|
||||
*/
|
||||
void
|
||||
blr_write_binlog_record(ROUTER_INSTANCE *router, REP_HEADER *hdr, uint8_t *buf)
|
||||
{
|
||||
pwrite(router->binlog_fd, buf, hdr->event_size, hdr->next_pos - hdr->event_size);
|
||||
router->binlog_position = hdr->next_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the content of the binlog file to disk.
|
||||
*
|
||||
* @param router The binlog router
|
||||
*/
|
||||
void
|
||||
blr_file_flush(ROUTER_INSTANCE *router)
|
||||
{
|
||||
fsync(router->binlog_fd);
|
||||
}
|
||||
|
||||
int
|
||||
blr_open_binlog(ROUTER_INSTANCE *router, char *binlog)
|
||||
{
|
||||
char *ptr, path[1024];
|
||||
int rval;
|
||||
|
||||
strcpy(path, "/usr/local/skysql/MaxScale");
|
||||
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
|
||||
{
|
||||
strcpy(path, ptr);
|
||||
}
|
||||
strcat(path, "/");
|
||||
strcat(path, router->service->name);
|
||||
strcat(path, "/");
|
||||
strcat(path, binlog);
|
||||
|
||||
if ((rval = open(path, O_RDONLY, 0666)) == -1)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to open binlog file %s\n", path)));
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a replication event into a GWBUF structure.
|
||||
*
|
||||
* @param fd File descriptor of the binlog file
|
||||
* @param pos Position of binlog record to read
|
||||
* @param hdr Binlog header to populate
|
||||
* @return The binlog record wrapped in a GWBUF structure
|
||||
*/
|
||||
GWBUF *
|
||||
blr_read_binlog(int fd, unsigned int pos, REP_HEADER *hdr)
|
||||
{
|
||||
uint8_t hdbuf[19];
|
||||
GWBUF *result;
|
||||
unsigned char *data;
|
||||
int n;
|
||||
|
||||
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 ((n = 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))));
|
||||
if (n> 0 && n < 19)
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Short read when reading the header. "
|
||||
"Expected 19 bytes got %d bytes.\n",
|
||||
n)));
|
||||
return NULL;
|
||||
}
|
||||
hdr->timestamp = extract_field(hdbuf, 32);
|
||||
hdr->event_type = hdbuf[4];
|
||||
hdr->serverid = extract_field(&hdbuf[5], 32);
|
||||
hdr->event_size = extract_field(&hdbuf[9], 32);
|
||||
hdr->next_pos = extract_field(&hdbuf[13], 32);
|
||||
hdr->flags = extract_field(&hdbuf[17], 16);
|
||||
if ((result = gwbuf_alloc(hdr->event_size)) == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to allocate memory for binlog entry, "
|
||||
"size %d at %d.\n",
|
||||
hdr->event_size, pos)));
|
||||
return NULL;
|
||||
}
|
||||
data = GWBUF_DATA(result);
|
||||
memcpy(data, hdbuf, 19); // Copy the header in
|
||||
if ((n = read(fd, &data[19], hdr->event_size - 19))
|
||||
!= hdr->event_size - 19) // Read the balance
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Short read when reading the event at %d. "
|
||||
"Expected %d bytes got %d bytes.\n",
|
||||
pos, n)));
|
||||
gwbuf_consume(result, hdr->event_size);
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a numeric field from a packet of the specified number of bits
|
||||
*
|
||||
* @param src The raw packet source
|
||||
* @param birs The number of bits to extract (multiple of 8)
|
||||
*/
|
||||
static uint32_t
|
||||
extract_field(uint8_t *src, int bits)
|
||||
{
|
||||
uint32_t rval = 0, shift = 0;
|
||||
|
||||
while (bits > 0)
|
||||
{
|
||||
rval |= (*src++) << shift;
|
||||
shift += 8;
|
||||
bits -= 8;
|
||||
}
|
||||
return rval;
|
||||
}
|
1024
server/modules/routing/binlog/blr_master.c
Normal file
1024
server/modules/routing/binlog/blr_master.c
Normal file
File diff suppressed because it is too large
Load Diff
944
server/modules/routing/binlog/blr_slave.c
Normal file
944
server/modules/routing/binlog/blr_slave.c
Normal file
@ -0,0 +1,944 @@
|
||||
/*
|
||||
* 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 blr_slave.c - contains code for the router to slave communication
|
||||
*
|
||||
* The binlog router is designed to be used in replication environments to
|
||||
* increase the replication fanout of a master server. It provides a transparant
|
||||
* mechanism to read the binlog entries for multiple slaves while requiring
|
||||
* only a single connection to the actual master to support the slaves.
|
||||
*
|
||||
* The current prototype implement is designed to support MySQL 5.6 and has
|
||||
* a number of limitations. This prototype is merely a proof of concept and
|
||||
* should not be considered production ready.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 14/04/2014 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <service.h>
|
||||
#include <server.h>
|
||||
#include <router.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <blr.h>
|
||||
#include <dcb.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
#include <skygw_types.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
|
||||
static uint32_t extract_field(uint8_t *src, int bits);
|
||||
static void encode_value(unsigned char *data, unsigned int value, int len);
|
||||
static int blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
|
||||
static int blr_slave_replay(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *master);
|
||||
static void blr_slave_send_error(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *msg);
|
||||
static int blr_slave_send_timestamp(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
|
||||
static int blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
|
||||
static int blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
|
||||
int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
|
||||
static uint8_t *blr_build_header(GWBUF *pkt, REP_HEADER *hdr);
|
||||
static int blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data);
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
|
||||
/**
|
||||
* Process a request packet from the slave server.
|
||||
*
|
||||
* The router can handle a limited subset of requests from the slave, these
|
||||
* include a subset of general SQL queries, a slave registeration command and
|
||||
* the binlog dump command.
|
||||
*
|
||||
* The strategy for responding to these commands is to use caches responses
|
||||
* for the the same commands that have previously been made to the real master
|
||||
* if this is possible, if it is not then the router itself will synthesize a
|
||||
* response.
|
||||
*
|
||||
* @param router The router instance this defines the master for this replication chain
|
||||
* @param slave The slave specific data
|
||||
* @param queue The incoming request packet
|
||||
*/
|
||||
int
|
||||
blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
if (slave->state < 0 || slave->state > BLRS_MAXSTATE)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR, "Invalid slave state machine state (%d) for binlog router.\n",
|
||||
slave->state)));
|
||||
gwbuf_consume(queue, gwbuf_length(queue));
|
||||
return 0;
|
||||
}
|
||||
|
||||
atomic_add(&slave->stats.n_requests, 1);
|
||||
switch (MYSQL_COMMAND(queue))
|
||||
{
|
||||
case COM_QUERY:
|
||||
return blr_slave_query(router, slave, queue);
|
||||
break;
|
||||
case COM_REGISTER_SLAVE:
|
||||
return blr_slave_register(router, slave, queue);
|
||||
break;
|
||||
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,
|
||||
"Unexpected MySQL Command (%d) received from slave\n",
|
||||
MYSQL_COMMAND(queue))));
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a query from the slave. This is expected to be one of the "standard"
|
||||
* queries we expect as part of the registraton process. Most of these can
|
||||
* be dealt with by replying the stored responses we got from the master
|
||||
* when MaxScale registered as a slave. The exception to the rule is the
|
||||
* request to obtain the current timestamp value of the server.
|
||||
*
|
||||
* 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'
|
||||
*
|
||||
* 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
|
||||
* @param queue The incoming request packet
|
||||
* @return Non-zero if data has been sent
|
||||
*/
|
||||
static int
|
||||
blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
char *qtext, *query_text;
|
||||
char *sep = " ,=";
|
||||
char *word, *brkb;
|
||||
int query_len;
|
||||
|
||||
qtext = GWBUF_DATA(queue);
|
||||
query_len = extract_field((uint8_t *)qtext, 24) - 1;
|
||||
qtext += 5; // Skip header and first byte of the payload
|
||||
query_text = strndup(qtext, query_len);
|
||||
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE, "Execute statement from the slave '%s'\n", query_text)));
|
||||
/*
|
||||
* Implement a very rudimental "parsing" of the query text by extarcting the
|
||||
* words from the statement and matchng them against the subset of queries we
|
||||
* are expecting from the slave. We already have responses to these commands,
|
||||
* except for the select of UNIX_TIMESTAMP(), that we have saved from MaxScale's
|
||||
* own interaction with the real master. We simply replay these saved responses
|
||||
* to the slave.
|
||||
*/
|
||||
word = strtok_r(query_text, sep, &brkb);
|
||||
if (strcasecmp(word, "SELECT") == 0)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "UNIX_TIMESTAMP()") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_send_timestamp(router, slave);
|
||||
}
|
||||
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.chksum2);
|
||||
}
|
||||
else if (strcasecmp(word, "@@GLOBAL.GTID_MODE") == 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "VARIABLES") == 0)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "LIKE") == 0)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "'SERVER_ID'") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.server_id);
|
||||
}
|
||||
else if (strcasecmp(word, "'SERVER_UUID'") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcasecmp(query_text, "SET") == 0)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "@master_heartbeat_period") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.heartbeat);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if (strcasecmp(word, "@slave_uuid") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
|
||||
}
|
||||
else if (strcasecmp(word, "NAMES") == 0)
|
||||
{
|
||||
word = strtok_r(NULL, sep, &brkb);
|
||||
if (strcasecmp(word, "latin1") == 0)
|
||||
{
|
||||
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);
|
||||
|
||||
query_text = strndup(qtext, query_len);
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR, "Unexpected query from slave server %s\n", query_text)));
|
||||
free(query_text);
|
||||
blr_slave_send_error(router, slave, "Unexpected SQL query received from slave.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a reply to a command we have received from the slave. The reply itself
|
||||
* is merely a copy of a previous message we received from the master when we
|
||||
* registered as a slave. Hence we just replay this saved reply.
|
||||
*
|
||||
* @param router The binlog router instance
|
||||
* @param slave The slave server to which we are sending the response
|
||||
* @param master The saved master response
|
||||
* @return Non-zero if data was sent
|
||||
*/
|
||||
static int
|
||||
blr_slave_replay(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *master)
|
||||
{
|
||||
GWBUF *clone;
|
||||
|
||||
if (!master)
|
||||
return 0;
|
||||
if ((clone = gwbuf_clone(master)) != NULL)
|
||||
{
|
||||
return slave->dcb->func.write(slave->dcb, clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to clone server response to send to slave.\n")));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an error response
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param slave The slave server instance
|
||||
* @param msg The error message to send
|
||||
*/
|
||||
static void
|
||||
blr_slave_send_error(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *msg)
|
||||
{
|
||||
GWBUF *pkt;
|
||||
unsigned char *data;
|
||||
int len;
|
||||
|
||||
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
|
||||
return;
|
||||
data = GWBUF_DATA(pkt);
|
||||
len = strlen(msg) + 1;
|
||||
encode_value(&data[0], len, 24); // Payload length
|
||||
data[3] = 0; // Sequence id
|
||||
// Payload
|
||||
data[4] = 0xff; // Error indicator
|
||||
data[5] = 0; // Error Code
|
||||
data[6] = 0; // Error Code
|
||||
strncpy((char *)&data[7], "#00000", 6);
|
||||
memcpy(&data[13], msg, strlen(msg)); // Error Message
|
||||
slave->dcb->func.write(slave->dcb, pkt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some standard packets that have been captured from a network trace of server
|
||||
* interactions. These packets are the schema definition sent in response to
|
||||
* a SELECT UNIX_TIMESTAMP() statement and the EOF packet that marks the end
|
||||
* of transmission of the result set.
|
||||
*/
|
||||
static uint8_t timestamp_def[] = {
|
||||
0x01, 0x00, 0x00, 0x01, 0x01, 0x26, 0x00, 0x00, 0x02, 0x03, 0x64, 0x65, 0x66, 0x00, 0x00, 0x00,
|
||||
0x10, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x28,
|
||||
0x29, 0x00, 0x0c, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x08, 0x81, 0x00, 0x00, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x02, 0x00
|
||||
};
|
||||
static uint8_t timestamp_eof[] = { 0x05, 0x00, 0x00, 0x05, 0xfe, 0x00, 0x00, 0x02, 0x00 };
|
||||
|
||||
/**
|
||||
* Send a response to a "SELECT UNIX_TIMESTAMP()" request. This differs from the other
|
||||
* requests since we do not save a copy of the original interaction with the master
|
||||
* and simply replay it. We want to always send the current time. We have stored a typcial
|
||||
* response, which gives us the schema information normally returned. This is sent to the
|
||||
* client and then we add a dynamic part that will insert the current timestamp data.
|
||||
* Finally we send a preprepaed EOF packet to end the response stream.
|
||||
*
|
||||
* @param router The binlog router instance
|
||||
* @param slave The slave server to which we are sending the response
|
||||
* @return Non-zero if data was sent
|
||||
*/
|
||||
static int
|
||||
blr_slave_send_timestamp(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
|
||||
{
|
||||
GWBUF *pkt;
|
||||
char timestamp[20];
|
||||
uint8_t *ptr;
|
||||
int len, ts_len;
|
||||
|
||||
sprintf(timestamp, "%ld", time(0));
|
||||
ts_len = strlen(timestamp);
|
||||
len = sizeof(timestamp_def) + sizeof(timestamp_eof) + 5 + ts_len;
|
||||
if ((pkt = gwbuf_alloc(len)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(pkt);
|
||||
memcpy(ptr, timestamp_def, sizeof(timestamp_def)); // Fixed preamble
|
||||
ptr += sizeof(timestamp_def);
|
||||
encode_value(ptr, ts_len + 1, 24); // Add length of data packet
|
||||
ptr += 3;
|
||||
*ptr++ = 0x04; // Sequence number in response
|
||||
*ptr++ = ts_len; // Length of result string
|
||||
strncpy((char *)ptr, timestamp, ts_len); // Result string
|
||||
ptr += ts_len;
|
||||
memcpy(ptr, timestamp_eof, sizeof(timestamp_eof)); // EOF packet to terminate result
|
||||
return slave->dcb->func.write(slave->dcb, pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a slave replication registration message.
|
||||
*
|
||||
* We store the various bits of information the slave gives us and generate
|
||||
* a reply message.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param slave The slave server
|
||||
* @param queue The BINLOG_DUMP packet
|
||||
* @return Non-zero if data was sent
|
||||
*/
|
||||
static int
|
||||
blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
GWBUF *resp;
|
||||
uint8_t *ptr;
|
||||
int len, slen;
|
||||
|
||||
ptr = GWBUF_DATA(queue);
|
||||
len = extract_field(ptr, 24);
|
||||
ptr += 4; // Skip length and sequence number
|
||||
if (*ptr++ != COM_REGISTER_SLAVE)
|
||||
return 0;
|
||||
slave->serverid = extract_field(ptr, 32);
|
||||
ptr += 4;
|
||||
slen = *ptr++;
|
||||
if (slen != 0)
|
||||
{
|
||||
slave->hostname = strndup((char *)ptr, slen);
|
||||
ptr += slen;
|
||||
}
|
||||
else
|
||||
slave->hostname = NULL;
|
||||
slen = *ptr++;
|
||||
if (slen != 0)
|
||||
{
|
||||
ptr += slen;
|
||||
slave->user = strndup((char *)ptr, slen);
|
||||
}
|
||||
else
|
||||
slave->user = NULL;
|
||||
slen = *ptr++;
|
||||
if (slen != 0)
|
||||
{
|
||||
slave->passwd = strndup((char *)ptr, slen);
|
||||
ptr += slen;
|
||||
}
|
||||
else
|
||||
slave->passwd = NULL;
|
||||
slave->port = extract_field(ptr, 16);
|
||||
ptr += 2;
|
||||
slave->rank = extract_field(ptr, 32);
|
||||
|
||||
/*
|
||||
* Now construct a response
|
||||
*/
|
||||
if ((resp = gwbuf_alloc(11)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(resp);
|
||||
encode_value(ptr, 7, 24); // Payload length
|
||||
ptr += 3;
|
||||
*ptr++ = 1; // Sequence number
|
||||
encode_value(ptr, 0, 24);
|
||||
ptr += 3;
|
||||
encode_value(ptr, slave->serverid, 32);
|
||||
slave->state = BLRS_REGISTERED;
|
||||
return slave->dcb->func.write(slave->dcb, resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a COM_BINLOG_DUMP message from the slave. This is the
|
||||
* final step in the process of registration. The new master, MaxScale
|
||||
* must send a response packet and generate a fake BINLOG_ROTATE event
|
||||
* with the binlog file requested by the slave. And then send a
|
||||
* FORMAT_DESCRIPTION_EVENT that has been saved from the real master.
|
||||
*
|
||||
* Once send MaxScale must continue to send binlog events to the slave.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param slave The slave server
|
||||
* @param queue The BINLOG_DUMP packet
|
||||
* @return The number of bytes written to the slave
|
||||
*/
|
||||
static int
|
||||
blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
GWBUF *resp;
|
||||
uint8_t *ptr;
|
||||
int len, flags, serverid, rval;
|
||||
REP_HEADER hdr;
|
||||
uint32_t chksum;
|
||||
|
||||
ptr = GWBUF_DATA(queue);
|
||||
len = extract_field(ptr, 24);
|
||||
ptr += 4; // Skip length and sequence number
|
||||
if (*ptr++ != COM_BINLOG_DUMP)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"blr_slave_binlog_dump expected a COM_BINLOG_DUMP but received %d\n",
|
||||
*(ptr-1))));
|
||||
return 0;
|
||||
}
|
||||
|
||||
slave->binlog_pos = extract_field(ptr, 32);
|
||||
ptr += 4;
|
||||
flags = extract_field(ptr, 16);
|
||||
ptr += 2;
|
||||
serverid = extract_field(ptr, 32);
|
||||
ptr += 4;
|
||||
strncpy(slave->binlogfile, (char *)ptr, BINLOG_FNAMELEN);
|
||||
|
||||
slave->state = BLRS_DUMPING;
|
||||
slave->seqno = 1;
|
||||
|
||||
if (slave->nocrc)
|
||||
len = 0x2b;
|
||||
else
|
||||
len = 0x2f;
|
||||
|
||||
// Build a fake rotate event
|
||||
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 = 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;
|
||||
|
||||
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);
|
||||
|
||||
/* Send the FORMAT_DESCRIPTION_EVENT */
|
||||
if (router->saved_master.fde_event)
|
||||
{
|
||||
resp = gwbuf_alloc(router->saved_master.fde_len + 5);
|
||||
ptr = GWBUF_DATA(resp);
|
||||
encode_value(ptr, router->saved_master.fde_len + 1, 24); // Payload length
|
||||
ptr += 3;
|
||||
*ptr++ = slave->seqno++;
|
||||
*ptr++ = 0; // OK
|
||||
memcpy(ptr, router->saved_master.fde_event, router->saved_master.fde_len);
|
||||
encode_value(ptr, time(0), 32); // Overwrite timestamp
|
||||
/*
|
||||
* Since we have changed the timestamp we must recalculate the CRC
|
||||
*
|
||||
* Position ptr to the start of the event header,
|
||||
* calculate a new checksum
|
||||
* and write it into the header
|
||||
*/
|
||||
ptr = GWBUF_DATA(resp) + 5 + router->saved_master.fde_len - 4;
|
||||
chksum = crc32(0L, NULL, 0);
|
||||
chksum = crc32(chksum, GWBUF_DATA(resp) + 5, router->saved_master.fde_len - 4);
|
||||
encode_value(ptr, chksum, 32);
|
||||
rval = slave->dcb->func.write(slave->dcb, resp);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a numeric field from a packet of the specified number of bits,
|
||||
* the number of bits must be a multiple of 8.
|
||||
*
|
||||
* @param src The raw packet source
|
||||
* @param bits The number of bits to extract (multiple of 8)
|
||||
* @return The extracted value
|
||||
*/
|
||||
static uint32_t
|
||||
extract_field(uint8_t *src, int bits)
|
||||
{
|
||||
uint32_t rval = 0, shift = 0;
|
||||
|
||||
while (bits > 0)
|
||||
{
|
||||
rval |= (*src++) << shift;
|
||||
shift += 8;
|
||||
bits -= 8;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a value into a number of bits in a MySQL packet
|
||||
*
|
||||
* @param data Pointer to location in target packet
|
||||
* @param value The value to encode into the buffer
|
||||
* @param len Number of bits to encode value into
|
||||
*/
|
||||
static void
|
||||
encode_value(unsigned char *data, unsigned int value, int len)
|
||||
{
|
||||
while (len > 0)
|
||||
{
|
||||
*data++ = value & 0xff;
|
||||
value >>= 8;
|
||||
len -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate a header structure for a replication message from a GWBUF structure.
|
||||
*
|
||||
* @param pkt The incoming packet in a GWBUF chain
|
||||
* @param hdr The packet header to populate
|
||||
* @return A pointer to the first byte following the event header
|
||||
*/
|
||||
static uint8_t *
|
||||
blr_build_header(GWBUF *pkt, REP_HEADER *hdr)
|
||||
{
|
||||
uint8_t *ptr;
|
||||
|
||||
ptr = GWBUF_DATA(pkt);
|
||||
|
||||
encode_value(ptr, hdr->payload_len, 24);
|
||||
ptr += 3;
|
||||
*ptr++ = hdr->seqno;
|
||||
*ptr++ = hdr->ok;
|
||||
encode_value(ptr, hdr->timestamp, 32);
|
||||
ptr += 4;
|
||||
*ptr++ = hdr->event_type;
|
||||
encode_value(ptr, hdr->serverid, 32);
|
||||
ptr += 4;
|
||||
encode_value(ptr, hdr->event_size, 32);
|
||||
ptr += 4;
|
||||
encode_value(ptr, hdr->next_pos, 32);
|
||||
ptr += 4;
|
||||
encode_value(ptr, hdr->flags, 16);
|
||||
ptr += 2;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* We have a registered slave that is behind the current leading edge of the
|
||||
* binlog. We must replay the log entries to bring this node up to speed.
|
||||
*
|
||||
* There may be a large numebr of records to send to the slave, the process
|
||||
* is triggered by the slave COM_BINLOG_DUMP message and all the events must
|
||||
* be sent without receiving any new event. This measn there is no trigger into
|
||||
* MaxScale other than this initial message. However, if we simply send all the
|
||||
* events we end up with an extremely long write queue on the DCB and risk running
|
||||
* the server out of resources.
|
||||
*
|
||||
* To resolve this the concept of high and low water marks within the DCB has been
|
||||
* added, with the ability for the DCB code to call user defined callbacks when the
|
||||
* write queue is completely drained, when it crosses above the high water mark and
|
||||
* when it crosses below the low water mark.
|
||||
*
|
||||
* The blr_slave_catchup routine will send binlog events to the slave until the high
|
||||
* water mark is reached, at which point it will return. Later, when a low water mark
|
||||
* callback is generated by the code that drains the DCB of data the blr_slave_catchup
|
||||
* routine will again be called to write more events. The process is repeated until
|
||||
* the slave has caught up with the master.
|
||||
*
|
||||
* Note: an additional check that the DCB is still above the low water mark is done
|
||||
* prior to the return from this function to allow for any delays due to the call to
|
||||
* the close system call, since this may cause thread rescheduling.
|
||||
*
|
||||
* @param router The binlog router
|
||||
* @param slave The slave that is behind
|
||||
* @return The number of bytes written
|
||||
*/
|
||||
int
|
||||
blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
|
||||
{
|
||||
GWBUF *head, *record;
|
||||
REP_HEADER hdr;
|
||||
int written, fd, rval = 1, burst = 0;
|
||||
uint8_t *ptr;
|
||||
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
|
||||
* running the catchup loop, but we need to be very careful
|
||||
* that we do not loose a call that is coming via a callback
|
||||
* call as this will stall the binlog catchup process.
|
||||
*
|
||||
* We don't want to simply use a traditional mutex here for
|
||||
* the loop, since this would block a MaxScale thread for
|
||||
* an unacceptable length of time.
|
||||
*
|
||||
* We have two status bits, the CS_READING that says we are
|
||||
* in the outer loop and the CS_INNERLOOP, to say we are in
|
||||
* the inner loop.
|
||||
*
|
||||
* 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
|
||||
* is cleared or CS_INNERLOOP is set.
|
||||
*
|
||||
* If CS_READING gets cleared then this thread should proceed
|
||||
* into the loop.
|
||||
*
|
||||
* If CS_INNERLOOP get's set then this thread does not need to
|
||||
* proceed.
|
||||
*
|
||||
* If CS_READING is not set then this thread simply enters the
|
||||
* loop.
|
||||
*/
|
||||
req.tv_sec = 0;
|
||||
req.tv_nsec = 1000;
|
||||
spinlock_acquire(&slave->catch_lock);
|
||||
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);
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
spinlock_acquire(&slave->catch_lock);
|
||||
slave->cstate &= ~CS_READING;
|
||||
spinlock_release(&slave->catch_lock);
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"blr_slave_catchup failed to open binlog file %s\n",
|
||||
slave->binlogfile)));
|
||||
return 0;
|
||||
}
|
||||
atomic_add(&slave->stats.n_bursts, 1);
|
||||
spinlock_acquire(&slave->catch_lock);
|
||||
slave->cstate |= CS_INNERLOOP;
|
||||
spinlock_release(&slave->catch_lock);
|
||||
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);
|
||||
ptr += 3;
|
||||
*ptr++ = slave->seqno++;
|
||||
*ptr++ = 0; // OK
|
||||
head = gwbuf_append(head, record);
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
{
|
||||
close(fd);
|
||||
blr_slave_rotate(slave, GWBUF_DATA(record));
|
||||
if ((fd = blr_open_binlog(router, slave->binlogfile)) == -1)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"blr_slave_catchup failed to open binlog file %s\n",
|
||||
slave->binlogfile)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
written = slave->dcb->func.write(slave->dcb, head);
|
||||
if (written && hdr.event_type != ROTATE_EVENT)
|
||||
{
|
||||
slave->binlog_pos = hdr.next_pos;
|
||||
}
|
||||
rval = written;
|
||||
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));
|
||||
if (record)
|
||||
{
|
||||
atomic_add(&slave->stats.n_flows, 1);
|
||||
spinlock_acquire(&slave->catch_lock);
|
||||
slave->cstate |= CS_EXPECTCB;
|
||||
spinlock_release(&slave->catch_lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
int state_change = 0;
|
||||
spinlock_acquire(&slave->catch_lock);
|
||||
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);
|
||||
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)));
|
||||
}
|
||||
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;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DCB callback used by the slave to obtain DCB_REASON_LOW_WATER callbacks
|
||||
* when the server sends all the the queue data for a DCB. This is the mechanism
|
||||
* that is used to implement the flow control mechanism for the sending of
|
||||
* large quantities of binlog records during the catchup process.
|
||||
*
|
||||
* @param dcb The DCB of the slave connection
|
||||
* @param reason The reason the callback was called
|
||||
* @param data The user data, in this case the server structure
|
||||
*/
|
||||
static int
|
||||
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_DRAINED)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the slave to the new binlog file
|
||||
*
|
||||
* @param slave The slave instance
|
||||
* @param ptr The rotate event (minux header and OK byte)
|
||||
*/
|
||||
void
|
||||
blr_slave_rotate(ROUTER_SLAVE *slave, uint8_t *ptr)
|
||||
{
|
||||
ptr += 19; // Skip header
|
||||
slave->binlog_pos = extract_field(ptr, 32);
|
||||
slave->binlog_pos += (extract_field(ptr+4, 32) << 32);
|
||||
memcpy(slave->binlogfile, ptr + 8, BINLOG_FNAMELEN);
|
||||
slave->binlogfile[BINLOG_FNAMELEN] = 0;
|
||||
}
|
@ -160,6 +160,10 @@ struct subcommand showoptions[] = {
|
||||
"Show all active sessions in MaxScale",
|
||||
"Show all active sessions in MaxScale",
|
||||
{0, 0, 0} },
|
||||
{ "threads", 0, dShowThreads,
|
||||
"Show the status of the polling threads in MaxScale",
|
||||
"Show the status of the polling threads in MaxScale",
|
||||
{0, 0, 0} },
|
||||
{ "users", 0, telnetdShowUsers,
|
||||
"Show statistics and user names for the debug interface",
|
||||
"Show statistics and user names for the debug interface",
|
||||
@ -208,6 +212,10 @@ struct subcommand listoptions[] = {
|
||||
"List all the active sessions within MaxScale",
|
||||
"List all the active sessions within MaxScale",
|
||||
{0, 0, 0} },
|
||||
{ "threads", 0, dShowThreads,
|
||||
"List the status of the polling threads in MaxScale",
|
||||
"List the status of the polling threads in MaxScale",
|
||||
{0, 0, 0} },
|
||||
{ NULL, 0, NULL, NULL, NULL,
|
||||
{0, 0, 0} }
|
||||
};
|
||||
@ -831,6 +839,7 @@ static struct {
|
||||
{ "master", SERVER_MASTER },
|
||||
{ "slave", SERVER_SLAVE },
|
||||
{ "synced", SERVER_JOINED },
|
||||
{ "ndb", SERVER_NDB },
|
||||
{ "maintenance", SERVER_MAINT },
|
||||
{ "maint", SERVER_MAINT },
|
||||
{ NULL, 0 }
|
||||
|
@ -311,6 +311,11 @@ char *weightby;
|
||||
inst->bitmask |= (SERVER_JOINED);
|
||||
inst->bitvalue |= SERVER_JOINED;
|
||||
}
|
||||
else if (!strcasecmp(options[i], "ndb"))
|
||||
{
|
||||
inst->bitmask |= (SERVER_NDB);
|
||||
inst->bitvalue |= SERVER_NDB;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
@ -318,7 +323,7 @@ char *weightby;
|
||||
"* Warning : Unsupported router "
|
||||
"option \'%s\' for readconnroute. "
|
||||
"Expected router options are "
|
||||
"[slave|master|synced]",
|
||||
"[slave|master|synced|ndb]",
|
||||
options[i])));
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@ testall:
|
||||
-$(MAKE) cleantests
|
||||
-$(MAKE) DEBUG=Y buildtests
|
||||
-$(MAKE) runtests
|
||||
-$(MAKE) -C test_hints testall
|
||||
|
||||
buildtests:
|
||||
|
||||
@ -32,4 +33,4 @@ runtests:
|
||||
@echo "-------------------------------" >> $(TESTLOG)
|
||||
./rwsplit.sh $(TESTLOG) $(THOST) $(TPORT_RW) $(TMASTER_ID) $(TUSER) $(TPWD)
|
||||
@echo "" >> $(TESTLOG)
|
||||
@cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG)
|
||||
@cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG)
|
||||
|
@ -230,6 +230,16 @@ if [ "$a" != "$TRETVAL" ]; then
|
||||
else
|
||||
echo "$TINPUT PASSED">>$TLOG ;
|
||||
fi
|
||||
|
||||
TINPUT=test_temporary_table.sql
|
||||
a=`$RUNCMD < ./$TINPUT`
|
||||
TRETVAL=1
|
||||
if [ "$a" != "$TRETVAL" ]; then
|
||||
echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG;
|
||||
else
|
||||
echo "$TINPUT PASSED">>$TLOG ;
|
||||
fi
|
||||
|
||||
echo "-----------------------------------" >> $TLOG
|
||||
echo "Session variables: Stress Test 1" >> $TLOG
|
||||
echo "-----------------------------------" >> $TLOG
|
||||
@ -238,6 +248,10 @@ RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --d
|
||||
TINPUT=test_sescmd2.sql
|
||||
for ((i = 0;i<1000;i++))
|
||||
do
|
||||
if [[ $(( i % 50 )) -eq 0 ]]
|
||||
then
|
||||
printf "."
|
||||
fi
|
||||
a=`$RUNCMD < $TINPUT 2>&1`
|
||||
if [[ "`echo "$a"|grep -i 'error'`" != "" ]]
|
||||
then
|
||||
@ -255,15 +269,19 @@ fi
|
||||
echo "-----------------------------------" >> $TLOG
|
||||
echo "Session variables: Stress Test 2" >> $TLOG
|
||||
echo "-----------------------------------" >> $TLOG
|
||||
|
||||
echo ""
|
||||
err=""
|
||||
TINPUT=test_sescmd3.sql
|
||||
for ((j = 0;j<1000;j++))
|
||||
do
|
||||
b=`$RUNCMD < $TINPUT 2>&1`
|
||||
if [[ "`echo "$b"|grep -i 'null'`" != "" ]]
|
||||
if [[ $(( j % 50 )) -eq 0 ]]
|
||||
then
|
||||
err=`echo "$b" | grep -i null`
|
||||
printf "."
|
||||
fi
|
||||
b=`$RUNCMD < $TINPUT 2>&1`
|
||||
if [[ "`echo "$b"|grep -i 'null\|error'`" != "" ]]
|
||||
then
|
||||
err=`echo "$b" | grep -i null\|error`
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
@ -0,0 +1,53 @@
|
||||
# cleantests - clean local and subdirectories' tests
|
||||
# buildtests - build all local and subdirectories' tests
|
||||
# runtests - run all local tests
|
||||
# testall - clean, build and run local and subdirectories' tests
|
||||
|
||||
include ../../../../../../build_gateway.inc
|
||||
include $(ROOT_PATH)/makefile.inc
|
||||
include $(ROOT_PATH)/test.inc
|
||||
|
||||
ARGS=6
|
||||
|
||||
CC=cc
|
||||
TESTLOG := $(shell pwd)/testrwsplit_hints.log
|
||||
RET := -1
|
||||
|
||||
cleantests:
|
||||
- $(DEL) *.o
|
||||
- $(DEL) *~
|
||||
- $(DEL) *.sql
|
||||
- $(DEL) *.output
|
||||
- $(DEL) *.log
|
||||
|
||||
testall:
|
||||
-$(MAKE) cleantests
|
||||
-$(MAKE) DEBUG=Y buildtests
|
||||
-$(MAKE) runtests
|
||||
|
||||
buildtests:
|
||||
|
||||
|
||||
runtests:
|
||||
@echo "" >> $(TESTLOG)
|
||||
@echo "-------------------------------" >> $(TESTLOG)
|
||||
@echo $(shell date) >> $(TESTLOG)
|
||||
@echo "Test Read/Write split router - hint routing" >> $(TESTLOG)
|
||||
@echo "-------------------------------" >> $(TESTLOG)
|
||||
@echo "Running simple tests" >> $(TESTLOG)
|
||||
@echo "" >> $(TESTLOG)
|
||||
./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) simple_tests
|
||||
@echo "" >> $(TESTLOG)
|
||||
@echo "Running syntax error tests" >> $(TESTLOG)
|
||||
@echo "" >> $(TESTLOG)
|
||||
./syntax_check.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) error_tests
|
||||
@echo "" >> $(TESTLOG)
|
||||
@echo "Running complex tests" >> $(TESTLOG)
|
||||
@echo "" >> $(TESTLOG)
|
||||
./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) complex_tests
|
||||
@echo "" >> $(TESTLOG)
|
||||
@echo "Running stack tests" >> $(TESTLOG)
|
||||
@echo "" >> $(TESTLOG)
|
||||
./rwsplit_hints.sh $(TESTLOG) $(THOST) $(TPORT_RW_HINT) $(TMASTER_ID) $(TUSER) $(TPWD) stack_tests
|
||||
@echo "" >> $(TESTLOG)
|
||||
@cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG)
|
@ -0,0 +1,48 @@
|
||||
select @@server_id; -- maxscale begin route to master:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale route to server server3:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale end:
|
||||
select @@server_id; -- maxscale named1 prepare route to master:
|
||||
select @@server_id; -- maxscale named1 begin:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale route to server server3:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale end:
|
||||
select @@server_id; -- maxscale shorthand1 begin route to server server2:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale route to server server3:3002
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale end:
|
||||
select @@server_id; # maxscale begin route to master:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; # maxscale route to server server3:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id; # maxscale end:
|
||||
select @@server_id; # maxscale named2 prepare route to master:
|
||||
select @@server_id; # maxscale named2 begin:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; # maxscale route to server server3:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id; # maxscale end:
|
||||
select @@server_id; # maxscale shorthand2 begin route to server server2:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; # maxscale route to server server3:3002
|
||||
select @@server_id;:3001
|
||||
select @@server_id; # maxscale end:
|
||||
select @@server_id/* maxscale begin route to master */;:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id/* maxscale route to server server3 */;:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id/* maxscale end */;:
|
||||
select @@server_id/* maxscale named3 prepare route to master */;:
|
||||
select @@server_id/* maxscale named3 begin */;:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id/* maxscale route to server server3 */;:3002
|
||||
select @@server_id;:3000
|
||||
select @@server_id/* maxscale end */;:
|
||||
select @@server_id/* maxscale shorthand3 begin route to server server2 */; :3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id/* maxscale route to server server3 */;:3002
|
||||
select @@server_id;:3001
|
||||
select @@server_id/* maxscale end */;:
|
@ -0,0 +1,39 @@
|
||||
select @@server_id; -- maxscalemaxscale route to master:
|
||||
select @@server_id; -- master to route maxscale:
|
||||
select @@server_id; -- route to master:
|
||||
select @@server_id; -- maxscale to master:
|
||||
select @@server_id; -- maxscale route master:
|
||||
select @@server_id; -- maxscale route to:
|
||||
select @@server_id; -- maxscale begin master:
|
||||
select @@server_id; -- maxscale master route to master:
|
||||
select @@server_id; -- maxscale route to maxscale route to master:
|
||||
select @@server_id; -- maxscale maxscale route to master:
|
||||
select @@server_id; -- maxscale route to to server =):
|
||||
select @@server_id; -- maxscale route to maxscale server server1:
|
||||
select @@server_id; -- maxscale route to server1:
|
||||
select @@server_id; # maxscalemaxscale route to master:
|
||||
select @@server_id; # master to route maxscale:
|
||||
select @@server_id; # route to master:
|
||||
select @@server_id; # maxscale to master:
|
||||
select @@server_id; # maxscale route master:
|
||||
select @@server_id; # maxscale route to:
|
||||
select @@server_id; # maxscale begin master:
|
||||
select @@server_id; # maxscale master route to master:
|
||||
select @@server_id; # maxscale route to maxscale route to master:
|
||||
select @@server_id; # maxscale maxscale route to master:
|
||||
select @@server_id; # maxscale route to to server =):
|
||||
select @@server_id; # maxscale route to maxscale server server1:
|
||||
select @@server_id; # maxscale route to server1:
|
||||
select @@server_id; /* maxscalemaxscale route to master */;:
|
||||
select @@server_id; /* master to route maxscale */;:
|
||||
select @@server_id; /* route to master */;:
|
||||
select @@server_id; /* maxscale to master */;:
|
||||
select @@server_id; /* maxscale route master */;:
|
||||
select @@server_id; /* maxscale route to */;:
|
||||
select @@server_id; /* maxscale begin master */;:
|
||||
select @@server_id; /* maxscale master route to master */;:
|
||||
select @@server_id; /* maxscale route to maxscale route to master */;:
|
||||
select @@server_id; /* maxscale maxscale route to master */;:
|
||||
select @@server_id; /* maxscale route to to server =) */;:
|
||||
select @@server_id; /* maxscale route to maxscale server server1 */;:
|
||||
select @@server_id; /* maxscale route to server1 */;:
|
@ -0,0 +1,9 @@
|
||||
-- maxscale route to master:3000
|
||||
-- maxscale route to server server1:3000
|
||||
-- maxscale route to server server2:3001
|
||||
-- maxscale route to server server3:3002
|
||||
-- maxscale route to server server4:3003
|
||||
-- maxscale max_slave_replication_lag = 100:
|
||||
-- maxscale max_slave_replication_lag= 100:
|
||||
-- maxscale max_slave_replication_lag =100:
|
||||
-- maxscale max_slave_replication_lag=100:
|
65
server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh
Executable file
65
server/modules/routing/readwritesplit/test/test_hints/rwsplit_hints.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
NARGS=7
|
||||
TLOG=$1
|
||||
THOST=$2
|
||||
TPORT=$3
|
||||
TMASTER_ID=$4
|
||||
TUSER=$5
|
||||
TPWD=$6
|
||||
TESTINPUT=$7
|
||||
|
||||
if [ $# != $NARGS ] ;
|
||||
then
|
||||
echo""
|
||||
echo "Wrong number of arguments, gave "$#" but "$NARGS" is required"
|
||||
echo ""
|
||||
echo "Usage :"
|
||||
echo " rwsplit_hints.sh <log filename> <host> <port> <master id> <user> <password> <test file>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --disable-reconnect\ --silent\ --comment
|
||||
i=0
|
||||
|
||||
while read -r LINE
|
||||
do
|
||||
TINPUT[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[1]}'`
|
||||
TRETVAL[$i]=`echo "$LINE"|awk '{split($0,a,":");print a[2]}'`
|
||||
echo "${TINPUT[i]}" >> $TESTINPUT.sql
|
||||
i=$((i+1))
|
||||
done < $TESTINPUT
|
||||
|
||||
`$RUNCMD < $TESTINPUT.sql > $TESTINPUT.output`
|
||||
|
||||
x=0
|
||||
crash=1
|
||||
all_passed=1
|
||||
|
||||
while read -r TOUTPUT
|
||||
do
|
||||
crash=0
|
||||
if [ "$TOUTPUT" != "${TRETVAL[x]}" -a "${TRETVAL[x]}" != "" ]
|
||||
then
|
||||
all_passed=0
|
||||
echo "$TESTINPUT:$((x + 1)): ${TINPUT[x]} FAILED, return value $TOUTPUT when ${TRETVAL[x]} was expected">>$TLOG;
|
||||
fi
|
||||
x=$((x+1))
|
||||
done < $TESTINPUT.output
|
||||
|
||||
if [ $crash -eq 1 ]
|
||||
then
|
||||
all_passed=0
|
||||
for ((v=0;v<$i;v++))
|
||||
do
|
||||
echo "${TINPUT[v]} FAILED, nothing was returned">>$TLOG;
|
||||
done
|
||||
fi
|
||||
|
||||
if [ $all_passed -eq 1 ]
|
||||
then
|
||||
echo "Test set: PASSED">>$TLOG;
|
||||
else
|
||||
echo "Test set: FAILED">>$TLOG;
|
||||
fi
|
@ -0,0 +1,18 @@
|
||||
select @@server_id; -- maxscale route to master:3000
|
||||
select @@server_id; -- maxscale route to slave:
|
||||
select @@server_id; -- maxscale route to server server1:3000
|
||||
select @@server_id; -- maxscale route to server server2:3001
|
||||
select @@server_id; -- maxscale route to server server3:3002
|
||||
select @@server_id; -- maxscale route to server server4:3003
|
||||
select @@server_id; # maxscale route to master:3000
|
||||
select @@server_id; # maxscale route to slave:
|
||||
select @@server_id; # maxscale route to server server1:3000
|
||||
select @@server_id; # maxscale route to server server2:3001
|
||||
select @@server_id; # maxscale route to server server3:3002
|
||||
select @@server_id; # maxscale route to server server4:3003
|
||||
select @@server_id/* maxscale route to master */;:3000
|
||||
select @@server_id/* maxscale route to slave */;:
|
||||
select @@server_id/* maxscale route to server server1 */;:3000
|
||||
select @@server_id/* maxscale route to server server2 */;:3001
|
||||
select @@server_id/* maxscale route to server server3 */;:3002
|
||||
select @@server_id/* maxscale route to server server4 */;:3003
|
@ -0,0 +1,50 @@
|
||||
select @@server_id; -- maxscale stack_named1 prepare route to server server1:
|
||||
select @@server_id; -- maxscale stack_named2 prepare route to server server2:
|
||||
select @@server_id; -- maxscale stack_named3 prepare route to server server3:
|
||||
select @@server_id; -- maxscale stack_named4 prepare route to server server4:
|
||||
select @@server_id; -- maxscale stack_named1 begin:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale stack_named2 begin:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale stack_named3 begin:3002
|
||||
select @@server_id;:3002
|
||||
select @@server_id; -- maxscale stack_named4 begin:3003
|
||||
select @@server_id;:3003
|
||||
select @@server_id; -- maxscale stack_shorthand1 begin route to server server1:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale stack_shorthand2 begin route to server server2:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale stack_shorthand3 begin route to server server3:3002
|
||||
select @@server_id;:3002
|
||||
select @@server_id; -- maxscale stack_shorthand4 begin route to server server4:3003
|
||||
select @@server_id;:3003
|
||||
select @@server_id; -- maxscale end:3002
|
||||
select @@server_id;:3002
|
||||
select @@server_id; -- maxscale end:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale end:3000
|
||||
select @@server_id;:3000
|
||||
select @@server_id; -- maxscale end:3003
|
||||
select @@server_id;:3003
|
||||
select @@server_id; -- maxscale end:3002
|
||||
select @@server_id;:3002
|
||||
select @@server_id; -- maxscale end:3001
|
||||
select @@server_id;:3001
|
||||
select @@server_id; -- maxscale end:3000
|
||||
select @@server_id; -- maxscale end:
|
||||
select @@server_id; -- maxscale stack_shorthand1 begin:3000
|
||||
select @@server_id; -- maxscale stack_shorthand2 begin:3001
|
||||
select @@server_id; -- maxscale stack_shorthand3 begin:3002
|
||||
select @@server_id; -- maxscale stack_shorthand4 begin:3003
|
||||
select @@server_id; -- maxscale stack_named1 begin:3000
|
||||
select @@server_id; -- maxscale stack_named2 begin:3001
|
||||
select @@server_id; -- maxscale stack_named3 begin:3002
|
||||
select @@server_id; -- maxscale stack_named4 begin:3003
|
||||
select @@server_id; -- maxscale end:3002
|
||||
select @@server_id; -- maxscale end:3001
|
||||
select @@server_id; -- maxscale end:3000
|
||||
select @@server_id; -- maxscale end:3003
|
||||
select @@server_id; -- maxscale end:3002
|
||||
select @@server_id; -- maxscale end:3001
|
||||
select @@server_id; -- maxscale end:3000
|
||||
select @@server_id; -- maxscale end:
|
33
server/modules/routing/readwritesplit/test/test_hints/syntax_check.sh
Executable file
33
server/modules/routing/readwritesplit/test/test_hints/syntax_check.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#! /bin/bash
|
||||
NARGS=7
|
||||
TLOG=$1
|
||||
THOST=$2
|
||||
TPORT=$3
|
||||
TMASTER_ID=$4
|
||||
TUSER=$5
|
||||
TPWD=$6
|
||||
TESTINPUT=$7
|
||||
|
||||
if [ $# != $NARGS ] ;
|
||||
then
|
||||
echo""
|
||||
echo "Wrong number of arguments, gave "$#" but "$NARGS" is required"
|
||||
echo ""
|
||||
echo "Usage :"
|
||||
echo " syntax_check.sh <log filename> <host> <port> <master id> <user> <password> <test file>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./rwsplit_hints.sh dummy.log $THOST $TPORT $TMASTER_ID $TUSER $TPWD $TESTINPUT
|
||||
|
||||
exp_count=`cat error_tests|wc -l`
|
||||
err_count=`tac ../../../../../test/log/skygw_err* | gawk '/enabled/{if(!bg){ bg = 1} else exit 0}{if(bg) print}'|grep -c 'Hint ignored'`
|
||||
|
||||
if [[ $err_count -ge $exp_count ]]
|
||||
then
|
||||
echo "Test set: PASSED">>$TLOG;
|
||||
else
|
||||
echo "Expected $exp_count ignored hints in the error log but found $err_count instead">>$TLOG
|
||||
echo "Test set: FAILED">>$TLOG;
|
||||
fi
|
@ -0,0 +1,5 @@
|
||||
use test;
|
||||
drop table if exists t1;
|
||||
create temporary table t1 (id integer);
|
||||
insert into t1 values(1);
|
||||
select id from t1;
|
616
server/modules/routing/webserver.c
Normal file
616
server/modules/routing/webserver.c
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2014
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <router.h>
|
||||
#include <modinfo.h>
|
||||
#include <server.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <monitor.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* The instance structure for this router.
|
||||
*/
|
||||
typedef struct {
|
||||
SERVICE *service;
|
||||
} WEB_INSTANCE;
|
||||
|
||||
/**
|
||||
* The session structure for this router.
|
||||
*/
|
||||
typedef struct {
|
||||
SESSION *session;
|
||||
} WEB_SESSION;
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_ROUTER,
|
||||
MODULE_IN_DEVELOPMENT,
|
||||
ROUTER_VERSION,
|
||||
"A test router - not for use in real systems"
|
||||
};
|
||||
|
||||
static ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(ROUTER *instance, SESSION *session);
|
||||
static void closeSession(ROUTER *instance, void *session);
|
||||
static void freeSession(ROUTER *instance, void *session);
|
||||
static int routeQuery(ROUTER *instance, void *session, GWBUF *queue);
|
||||
static void diagnostic(ROUTER *instance, DCB *dcb);
|
||||
static uint8_t getCapabilities (ROUTER* inst, void* router_session);
|
||||
|
||||
|
||||
static ROUTER_OBJECT MyObject = {
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
routeQuery,
|
||||
diagnostic,
|
||||
NULL,
|
||||
NULL,
|
||||
getCapabilities
|
||||
};
|
||||
|
||||
|
||||
static void send_index(WEB_SESSION *);
|
||||
static void send_css(WEB_SESSION *);
|
||||
static void send_menu(WEB_SESSION *);
|
||||
static void send_blank(WEB_SESSION *);
|
||||
static void send_title(WEB_SESSION *);
|
||||
static void send_frame1(WEB_SESSION *);
|
||||
static void send_services(WEB_SESSION *);
|
||||
static void send_sessions(WEB_SESSION *);
|
||||
static void send_servers(WEB_SESSION *);
|
||||
static void send_monitors(WEB_SESSION *);
|
||||
static void respond_error(WEB_SESSION *, int, char *);
|
||||
|
||||
/**
|
||||
* A map of URL to function that implements the URL
|
||||
*/
|
||||
static struct {
|
||||
char *page; /* URL */
|
||||
void (*fcn)(WEB_SESSION *); /* Function to call */
|
||||
} pages[] = {
|
||||
{ "index.html", send_index },
|
||||
{ "services.html", send_services },
|
||||
{ "menu.html", send_menu },
|
||||
{ "sessions.html", send_sessions },
|
||||
{ "blank.html", send_blank },
|
||||
{ "title.html", send_title },
|
||||
{ "frame1.html", send_frame1 },
|
||||
{ "servers.html", send_servers },
|
||||
{ "monitors.html", send_monitors },
|
||||
{ "styles.css", send_css },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
ROUTER_OBJECT *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the router for a particular service
|
||||
* within the gateway.
|
||||
*
|
||||
* @param service The service this router is being create for
|
||||
* @param options The options for this query router
|
||||
*
|
||||
* @return The instance data for this new instance
|
||||
*/
|
||||
static ROUTER *
|
||||
createInstance(SERVICE *service, char **options)
|
||||
{
|
||||
WEB_INSTANCE *inst;
|
||||
|
||||
if ((inst = (WEB_INSTANCE *)malloc(sizeof(WEB_INSTANCE))) == NULL)
|
||||
return NULL;
|
||||
|
||||
inst->service = service;
|
||||
return (ROUTER *)inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new session with this instance of the router.
|
||||
*
|
||||
* @param instance The router instance data
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
newSession(ROUTER *instance, SESSION *session)
|
||||
{
|
||||
WEB_SESSION *wsession;
|
||||
|
||||
if ((wsession = (WEB_SESSION *)malloc(sizeof(WEB_SESSION))) == NULL)
|
||||
return NULL;
|
||||
|
||||
wsession->session = session;
|
||||
return wsession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a session with the router, this is the mechanism
|
||||
* by which a router may cleanup data structure etc.
|
||||
*
|
||||
* @param instance The router instance data
|
||||
* @param session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(ROUTER *instance, void *session)
|
||||
{
|
||||
free(session);
|
||||
}
|
||||
|
||||
static void freeSession(
|
||||
ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
routeQuery(ROUTER *instance, void *session, GWBUF *queue)
|
||||
{
|
||||
WEB_SESSION *wsession = (WEB_SESSION *)session;
|
||||
char *ptr;
|
||||
int i, found = 0;
|
||||
char *url;
|
||||
|
||||
if ((url = gwbuf_get_property(queue, "URL")) == NULL)
|
||||
{
|
||||
respond_error(wsession, 404, "No URL available");
|
||||
}
|
||||
|
||||
ptr = strrchr(url, '/');
|
||||
if (ptr)
|
||||
ptr++;
|
||||
else
|
||||
ptr = url;
|
||||
for (i = 0; pages[i].page; i++)
|
||||
{
|
||||
if (!strcmp(ptr, pages[i].page))
|
||||
{
|
||||
(pages[i].fcn)(wsession);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
respond_error(wsession, 404, "Unrecognised URL received");
|
||||
gwbuf_free(queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnostics routine
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param dcb The DCB for diagnostic output
|
||||
*/
|
||||
static void
|
||||
diagnostic(ROUTER *instance, DCB *dcb)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the router capabilities bitmask
|
||||
*
|
||||
* @param inst The router instance
|
||||
* @param router_session The router session
|
||||
* @return Router capabilities bitmask
|
||||
*/
|
||||
static uint8_t
|
||||
getCapabilities(ROUTER *inst, void *router_session)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML of the index page.
|
||||
*/
|
||||
static char *index_page =
|
||||
"<HTML><HEAD>"
|
||||
"<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"
|
||||
"<TITLE>MaxScale</TITLE>"
|
||||
"</HEAD>"
|
||||
"<FRAMESET ROWS=\"60,*\">"
|
||||
"<FRAME SRC=\"title.html\">"
|
||||
"<FRAME SRC=\"frame1.html\">"
|
||||
"</FRAMESET>"
|
||||
"</HTML>";
|
||||
|
||||
/**
|
||||
* The HTML of the title page
|
||||
*/
|
||||
static char *title_page =
|
||||
"<HTML><HEAD>"
|
||||
"<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"
|
||||
"<TITLE>MaxScale</TITLE>"
|
||||
"</HEAD><BODY>"
|
||||
"<H1>MaxScale - Status View</H1>"
|
||||
"</BODY></HTML>";
|
||||
|
||||
/**
|
||||
* HTML of the main frames, those below the title frame
|
||||
*/
|
||||
static char *frame1_page =
|
||||
"<HTML>"
|
||||
"<FRAMESET COLS=\"20%,80%\">"
|
||||
"<FRAME SRC=\"menu.html\">"
|
||||
"<FRAME SRC=\"blank.html\" NAME=\"darea\">"
|
||||
"</FRAMESET>"
|
||||
"</HTML>";
|
||||
|
||||
/**
|
||||
* The menu page HTML
|
||||
*/
|
||||
static char *menu_page =
|
||||
"<HTML><HEAD>"
|
||||
"<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"
|
||||
"</HEAD><BODY>"
|
||||
"<H2>Options</H2><P>"
|
||||
"<UL><LI><A HREF=\"monitors.html\" target=\"darea\">Monitors</A>"
|
||||
"<LI><A HREF=\"services.html\" target=\"darea\">Services</A>"
|
||||
"<LI><A HREF=\"servers.html\" target=\"darea\">Servers</A>"
|
||||
"<LI><A HREF=\"sessions.html\" target=\"darea\">Sessions</A>"
|
||||
"</UL></BODY></HTML>";
|
||||
|
||||
/**
|
||||
* A blank page, contents of the display area when we first connect
|
||||
*/
|
||||
static char *blank_page = "<HTML><BODY> </BODY></HTML>";
|
||||
|
||||
/**
|
||||
* The CSS used for every "page"
|
||||
*/
|
||||
static char *css =
|
||||
"table, td, th { border: 1px solid blue; }\n"
|
||||
"th { background-color: blue; color: white; padding: 5px }\n"
|
||||
"td { padding: 5px; }\n"
|
||||
"table { border-collapse: collapse; }\n"
|
||||
"a:link { color: #0000FF; }\n"
|
||||
"a:visted { color: #0000FF; }\n"
|
||||
"a:hover { color: #FF0000; }\n"
|
||||
"a:active { color: #0000FF; }\n"
|
||||
"h1 { color: blue; font-family: serif }\n"
|
||||
"h2 { color: blue; font-family: serif }\n"
|
||||
"p { font-family: serif }\n"
|
||||
"li { font-family: serif }\n";
|
||||
|
||||
/**
|
||||
* Send the standard HTTP headers for an HTML file
|
||||
*/
|
||||
static void
|
||||
send_html_header(DCB *dcb)
|
||||
{
|
||||
char date[64] = "";
|
||||
const char *fmt = "%a, %d %b %Y %H:%M:%S GMT";
|
||||
|
||||
time_t httpd_current_time = time(NULL);
|
||||
|
||||
strftime(date, sizeof(date), fmt, localtime(&httpd_current_time));
|
||||
|
||||
dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: text/html\r\n", date, "MaxScale");
|
||||
|
||||
dcb_printf(dcb, "\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a static HTML page
|
||||
*
|
||||
* @param dcb The DCB of the connection to the browser
|
||||
* @param html The HTML to send
|
||||
*/
|
||||
static void
|
||||
send_static_html(DCB *dcb, char *html)
|
||||
{
|
||||
dcb_printf(dcb, html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the index page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_index(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, index_page);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the CSS
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_css(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, css);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the title page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_title(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, title_page);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the frame1 page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_frame1(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, frame1_page);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the menu page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_menu(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, menu_page);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a blank page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_blank(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
send_static_html(dcb, blank_page);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a table row for a service. This is called using the service
|
||||
* iterator function
|
||||
*
|
||||
* @param service The service to display
|
||||
* @param dcb The DCB to print the HTML to
|
||||
*/
|
||||
static void
|
||||
service_row(SERVICE *service, DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, "<TR><TD>%s</TD><TD>%s</TD><TD>%d</TD><TD>%d</TD></TR>\n",
|
||||
service->name, service->routerModule,
|
||||
service->stats.n_current, service->stats.n_sessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the services page. This produces a table by means of the
|
||||
* serviceIterate call.
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_services(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
dcb_printf(dcb, "<HTML><HEAD>");
|
||||
dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">");
|
||||
dcb_printf(dcb, "<BODY><H2>Services</H2><P>");
|
||||
dcb_printf(dcb, "<TABLE><TR><TH>Name</TH><TH>Router</TH><TH>");
|
||||
dcb_printf(dcb, "Current Sessions</TH><TH>Total Sessions</TH></TR>\n");
|
||||
serviceIterate(service_row, dcb);
|
||||
dcb_printf(dcb, "</TABLE></BODY></HTML>\n");
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a session row for a session. this is called using the session
|
||||
* iterator function
|
||||
*
|
||||
* @param session The session to display
|
||||
* @param dcb The DCB to send the HTML to
|
||||
*/
|
||||
static void
|
||||
session_row(SESSION *session, DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, "<TR><TD>%-16p</TD><TD>%s</TD><TD>%s</TD><TD>%s</TD></TR>\n",
|
||||
session, ((session->client && session->client->remote)
|
||||
? session->client->remote : ""),
|
||||
(session->service && session->service->name
|
||||
? session->service->name : ""),
|
||||
session_state(session->state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the sessions page. The produces a table of all the current sessions
|
||||
* display. It makes use of the sessionIterate call to call the function
|
||||
* session_row() with each session.
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_sessions(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
dcb_printf(dcb, "<HTML><HEAD>");
|
||||
dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">");
|
||||
dcb_printf(dcb, "<BODY><H2>Sessions</H2><P>");
|
||||
dcb_printf(dcb, "<TABLE><TR><TH>Session</TH><TH>Client</TH><TH>");
|
||||
dcb_printf(dcb, "Service</TH><TH>State</TH></TR>\n");
|
||||
sessionIterate(session_row, dcb);
|
||||
dcb_printf(dcb, "</TABLE></BODY></HTML>\n");
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a table row for a particular server. This is called via the
|
||||
* serverIterate call in send_servers.
|
||||
*
|
||||
* @param server The server to print
|
||||
* @param dcb The DCB to send the HTML to
|
||||
*/
|
||||
static void
|
||||
server_row(SERVER *server, DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, "<TR><TD>%s</TD><TD>%s</TD><TD>%d</TD><TD>%s</TD><TD>%d</TD></TR>\n",
|
||||
server->unique_name, server->name, server->port,
|
||||
server_status(server), server->stats.n_current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the servers page
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_servers(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
dcb_printf(dcb, "<HTML><HEAD>");
|
||||
dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">");
|
||||
dcb_printf(dcb, "<BODY><H2>Servers</H2><P>");
|
||||
dcb_printf(dcb, "<TABLE><TR><TH>Server</TH><TH>Address</TH><TH>");
|
||||
dcb_printf(dcb, "Port</TH><TH>State</TH><TH>Connections</TH></TR>\n");
|
||||
serverIterate(server_row, dcb);
|
||||
dcb_printf(dcb, "</TABLE></BODY></HTML>\n");
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a table row for the monitors table
|
||||
*
|
||||
* @param monitor The monitor to print
|
||||
* @param dcb The DCB to print to
|
||||
*/
|
||||
static void
|
||||
monitor_row(MONITOR *monitor, DCB *dcb)
|
||||
{
|
||||
dcb_printf(dcb, "<TR><TD>%s</TD><TD>%s</TD></TR>\n",
|
||||
monitor->name, monitor->state & MONITOR_STATE_RUNNING
|
||||
? "Running" : "Stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the monitors page. This iterates on all the monitors and send
|
||||
* the rows via the monitor_monitor.
|
||||
*
|
||||
* @param session The router session
|
||||
*/
|
||||
static void
|
||||
send_monitors(WEB_SESSION *session)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
send_html_header(dcb);
|
||||
dcb_printf(dcb, "<HTML><HEAD>");
|
||||
dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">");
|
||||
dcb_printf(dcb, "<BODY><H2>Monitors</H2><P>");
|
||||
dcb_printf(dcb, "<TABLE><TR><TH>Monitor</TH><TH>State</TH></TR>\n");
|
||||
monitorIterate(monitor_row, dcb);
|
||||
dcb_printf(dcb, "</TABLE></BODY></HTML>\n");
|
||||
dcb_close(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond with an HTTP error
|
||||
*
|
||||
* @param session The router session
|
||||
* @param err The HTTP error code to send
|
||||
* @param msg The message to print
|
||||
*/
|
||||
static void
|
||||
respond_error(WEB_SESSION *session, int err, char *msg)
|
||||
{
|
||||
DCB *dcb = session->session->client;
|
||||
|
||||
dcb_printf(dcb, "HTTP/1.1 %d %s\n", err, msg);
|
||||
dcb_printf(dcb, "Content-Type: text/html\n");
|
||||
dcb_printf(dcb, "\n");
|
||||
dcb_printf(dcb, "<HTML><BODY>\n");
|
||||
dcb_printf(dcb, "MaxScale webserver plugin unable to satisfy request.\n");
|
||||
dcb_printf(dcb, "<P>Code: %d, %s\n", err, msg);
|
||||
dcb_printf(dcb, "</BODY></HTML>");
|
||||
dcb_close(dcb);
|
||||
}
|
@ -20,11 +20,13 @@ threads=1
|
||||
# user =<user name - must have slave replication and
|
||||
# slave client privileges>
|
||||
# passwd=<password of the above user, plain text currently>
|
||||
# monitor_interval=<sampling interval in milliseconds,
|
||||
# default value is 10000>
|
||||
|
||||
[MySQL Monitor]
|
||||
type=monitor
|
||||
module=mysqlmon
|
||||
servers=server1,server2,server3
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
|
||||
@ -36,25 +38,46 @@ passwd=maxpwd
|
||||
# servers=<server name>,<server name>,...
|
||||
# user=<User to fetch password inforamtion with>
|
||||
# passwd=<Password of the user, plain text currently>
|
||||
# enable_root_user=<0 or 1, default is 0>
|
||||
# version_string=<specific string for server handshake,
|
||||
# default is the MariaDB embedded library version>
|
||||
#
|
||||
# Valid router modules currently are:
|
||||
# readwritesplit, readconnroute and debugcli
|
||||
|
||||
|
||||
[RW Split Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3
|
||||
servers=server1,server2,server3,server4
|
||||
max_slave_connections=90%
|
||||
write_ses_variables_to_all=Yes
|
||||
read_ses_variables_from_slaves=Yes
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=Hint
|
||||
|
||||
[RW Split Hint Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
max_slave_connections=90%
|
||||
write_ses_variables_to_all=Yes
|
||||
read_ses_variables_from_slaves=Yes
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=Hint
|
||||
|
||||
|
||||
[Read Connection Router]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=slave
|
||||
servers=server1,server2,server3
|
||||
router_options=master
|
||||
servers=server1
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
|
||||
|
||||
[HTTPD Router]
|
||||
type=service
|
||||
router=testroute
|
||||
@ -64,6 +87,12 @@ servers=server1,server2,server3
|
||||
type=service
|
||||
router=debugcli
|
||||
|
||||
|
||||
[Hint]
|
||||
type=filter
|
||||
module=hintfilter
|
||||
|
||||
|
||||
# Listener definitions for the services
|
||||
#
|
||||
# Valid options are:
|
||||
@ -71,6 +100,8 @@ router=debugcli
|
||||
# service=<name of service defined elsewhere>
|
||||
# protocol=<name of protocol module with which to listen>
|
||||
# port=<Listening port>
|
||||
# address=<Address to bind to>
|
||||
# socket=<Listening socket>
|
||||
|
||||
[RW Split Listener]
|
||||
type=listener
|
||||
@ -78,17 +109,25 @@ service=RW Split Router
|
||||
protocol=MySQLClient
|
||||
port=4006
|
||||
|
||||
[RW Split Hint Listener]
|
||||
type=listener
|
||||
service=RW Split Hint Router
|
||||
protocol=MySQLClient
|
||||
port=4009
|
||||
|
||||
[Read Connection Listener]
|
||||
type=listener
|
||||
service=Read Connection Router
|
||||
protocol=MySQLClient
|
||||
port=4008
|
||||
#socket=/tmp/readconn.sock
|
||||
|
||||
[Debug Listener]
|
||||
type=listener
|
||||
service=Debug Interface
|
||||
protocol=telnetd
|
||||
port=4442
|
||||
#address=127.0.0.1
|
||||
|
||||
[HTTPD Listener]
|
||||
type=listener
|
||||
@ -115,3 +154,9 @@ type=server
|
||||
address=127.0.0.1
|
||||
port=3002
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server4]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3003
|
||||
protocol=MySQLBackend
|
||||
|
Reference in New Issue
Block a user