Merge branch 'develop' into firewall
Conflicts: server/modules/filter/test/CMakeLists.txt
This commit is contained in:
@ -201,6 +201,36 @@ GWBUF *rval;
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone whole GWBUF list instead of single buffer.
|
||||
*
|
||||
* @param buf head of the list to be cloned till the tail of it
|
||||
*
|
||||
* @return head of the cloned list or NULL if the list was empty.
|
||||
*/
|
||||
GWBUF* gwbuf_clone_all(
|
||||
GWBUF* buf)
|
||||
{
|
||||
GWBUF* rval;
|
||||
GWBUF* clonebuf;
|
||||
|
||||
if (buf == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/** Store the head of the list to rval. */
|
||||
clonebuf = gwbuf_clone(buf);
|
||||
rval = clonebuf;
|
||||
|
||||
while (buf->next)
|
||||
{
|
||||
buf = buf->next;
|
||||
clonebuf->next = gwbuf_clone(buf);
|
||||
clonebuf = clonebuf->next;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
GWBUF *gwbuf_clone_portion(
|
||||
GWBUF *buf,
|
||||
|
@ -52,8 +52,32 @@
|
||||
#include <mysqld_error.h>
|
||||
|
||||
|
||||
#define DEFAULT_CONNECT_TIMEOUT 3
|
||||
#define DEFAULT_READ_TIMEOUT 1
|
||||
#define DEFAULT_WRITE_TIMEOUT 2
|
||||
|
||||
#define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')"
|
||||
#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
|
||||
|
||||
#if 0
|
||||
# define LOAD_MYSQL_USERS_QUERY \
|
||||
"SELECT DISTINCT \
|
||||
user.user AS user, \
|
||||
user.host AS host, \
|
||||
user.password AS password, \
|
||||
concat(user.user,user.host,user.password, \
|
||||
IF((user.Select_priv+0)||find_in_set('Select',Coalesce(tp.Table_priv,0)),'Y','N') , \
|
||||
COALESCE( db.db,tp.db, '')) AS userdata, \
|
||||
user.Select_priv AS anydb, \
|
||||
COALESCE( db.db,tp.db, NULL) AS db \
|
||||
FROM \
|
||||
mysql.user LEFT JOIN \
|
||||
mysql.db ON user.user=db.user AND user.host=db.host LEFT JOIN \
|
||||
mysql.tables_priv tp ON user.user=tp.user AND user.host=tp.host \
|
||||
WHERE user.user IS NOT NULL AND user.user <> ''"
|
||||
|
||||
#else
|
||||
# define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
|
||||
#endif
|
||||
#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user"
|
||||
|
||||
#define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC"
|
||||
@ -85,6 +109,11 @@ void *resource_fetch(HASHTABLE *, char *);
|
||||
int resource_add(HASHTABLE *, char *, char *);
|
||||
int resource_hash(char *);
|
||||
static int normalize_hostname(char *input_host, char *output_host);
|
||||
static int gw_mysql_set_timeouts(
|
||||
MYSQL* handle,
|
||||
int read_timeout,
|
||||
int write_timeout,
|
||||
int connect_timeout);
|
||||
|
||||
/**
|
||||
* Load the user/passwd form mysql.user table into the service users' hashtable
|
||||
@ -219,7 +248,7 @@ HASHTABLE *oldresources;
|
||||
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db) {
|
||||
struct sockaddr_in serv_addr;
|
||||
MYSQL_USER_HOST key;
|
||||
char ret_ip[INET_ADDRSTRLEN + 1]="";
|
||||
char ret_ip[400]="";
|
||||
int ret = 0;
|
||||
|
||||
if (users == NULL || user == NULL || host == NULL) {
|
||||
@ -409,7 +438,8 @@ getDatabases(SERVICE *service, MYSQL *con)
|
||||
*
|
||||
* @param service The current service
|
||||
* @param users The users table into which to load the users
|
||||
* @return -1 on any error or the number of users inserted (0 means no users at all)
|
||||
* @return -1 on any error or the number of users inserted
|
||||
* (0 means no users at all)
|
||||
*/
|
||||
static int
|
||||
getUsers(SERVICE *service, USERS *users)
|
||||
@ -421,20 +451,24 @@ getUsers(SERVICE *service, USERS *users)
|
||||
char *service_passwd = NULL;
|
||||
char *dpwd;
|
||||
int total_users = 0;
|
||||
SERVER *server;
|
||||
SERVER_REF *server;
|
||||
char *users_query;
|
||||
unsigned char hash[SHA_DIGEST_LENGTH]="";
|
||||
char *users_data = NULL;
|
||||
int nusers = 0;
|
||||
int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN;
|
||||
int users_data_row_len = MYSQL_USER_MAXLEN +
|
||||
MYSQL_HOST_MAXLEN +
|
||||
MYSQL_PASSWORD_LEN +
|
||||
sizeof(char) +
|
||||
MYSQL_DATABASE_MAXLEN;
|
||||
int dbnames = 0;
|
||||
int db_grants = 0;
|
||||
|
||||
serviceGetUser(service, &service_user, &service_passwd);
|
||||
|
||||
if (service_user == NULL || service_passwd == NULL)
|
||||
return -1;
|
||||
|
||||
if (serviceGetUser(service, &service_user, &service_passwd) == 0)
|
||||
{
|
||||
ss_dassert(service_passwd == NULL || service_user == NULL);
|
||||
return -1;
|
||||
}
|
||||
con = mysql_init(NULL);
|
||||
|
||||
if (con == NULL) {
|
||||
@ -444,13 +478,26 @@ getUsers(SERVICE *service, USERS *users)
|
||||
mysql_error(con))));
|
||||
return -1;
|
||||
}
|
||||
/** Set read, write and connect timeout values */
|
||||
if (gw_mysql_set_timeouts(con,
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
DEFAULT_WRITE_TIMEOUT,
|
||||
DEFAULT_CONNECT_TIMEOUT))
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : failed to set timeout values for backend "
|
||||
"connection.")));
|
||||
mysql_close(con);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : failed to set external connection. "
|
||||
"It is needed for backend server connections. "
|
||||
"Exiting.")));
|
||||
"It is needed for backend server connections.")));
|
||||
mysql_close(con);
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
@ -458,46 +505,69 @@ getUsers(SERVICE *service, USERS *users)
|
||||
* out of databases
|
||||
* to try
|
||||
*/
|
||||
server = service->databases;
|
||||
server = service->dbref;
|
||||
dpwd = decryptPassword(service_passwd);
|
||||
|
||||
/* Select a server with Master bit, if available */
|
||||
while (server != NULL && !(server->status & SERVER_MASTER)) {
|
||||
server = server->nextdb;
|
||||
while (server != NULL && !(server->server->status & SERVER_MASTER)) {
|
||||
server = server->next;
|
||||
}
|
||||
|
||||
if (service->svc_do_shutdown)
|
||||
{
|
||||
free(dpwd);
|
||||
mysql_close(con);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Try loading data from master server */
|
||||
if (server != NULL && (mysql_real_connect(con, server->name, service_user, dpwd, NULL, server->port, NULL, 0) != NULL)) {
|
||||
if (server != NULL &&
|
||||
(mysql_real_connect(con,
|
||||
server->server->name, service_user,
|
||||
dpwd,
|
||||
NULL,
|
||||
server->server->port,
|
||||
NULL, 0) != NULL))
|
||||
{
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"Dbusers : Loading data from backend database with Master role [%s:%i] "
|
||||
"for service [%s]",
|
||||
server->name,
|
||||
server->port,
|
||||
"Dbusers : Loading data from backend database with "
|
||||
"Master role [%s:%i] for service [%s]",
|
||||
server->server->name,
|
||||
server->server->port,
|
||||
service->name)));
|
||||
} else {
|
||||
/* load data from other servers via loop */
|
||||
server = service->databases;
|
||||
server = service->dbref;
|
||||
|
||||
while (server != NULL && (mysql_real_connect(con,
|
||||
server->name,
|
||||
service_user,
|
||||
dpwd,
|
||||
NULL,
|
||||
server->port,
|
||||
NULL,
|
||||
0) == NULL))
|
||||
while (!service->svc_do_shutdown &&
|
||||
server != NULL &&
|
||||
(mysql_real_connect(con,
|
||||
server->server->name,
|
||||
service_user,
|
||||
dpwd,
|
||||
NULL,
|
||||
server->server->port,
|
||||
NULL,
|
||||
0) == NULL))
|
||||
{
|
||||
server = server->nextdb;
|
||||
server = server->next;
|
||||
}
|
||||
|
||||
|
||||
if (service->svc_do_shutdown)
|
||||
{
|
||||
free(dpwd);
|
||||
mysql_close(con);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (server != NULL) {
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"Dbusers : Loading data from backend database [%s:%i] "
|
||||
"for service [%s]",
|
||||
server->name,
|
||||
server->port,
|
||||
"Dbusers : Loading data from backend database "
|
||||
"[%s:%i] for service [%s]",
|
||||
server->server->name,
|
||||
server->server->port,
|
||||
service->name)));
|
||||
}
|
||||
}
|
||||
@ -515,9 +585,7 @@ getUsers(SERVICE *service, USERS *users)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* count users */
|
||||
|
||||
/* start with users and db grants for users */
|
||||
/** Count users. Start with users and db grants for users */
|
||||
if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) {
|
||||
if (mysql_errno(con) != ER_TABLEACCESS_DENIED_ERROR) {
|
||||
/* This is an error we cannot handle, return */
|
||||
@ -1193,3 +1261,58 @@ int useorig = 0;
|
||||
|
||||
return netmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set read, write and connect timeout values for MySQL database connection.
|
||||
*
|
||||
* @param handle MySQL handle
|
||||
* @param read_timeout Read timeout value in seconds
|
||||
* @param write_timeout Write timeout value in seconds
|
||||
* @param connect_timeout Connect timeout value in seconds
|
||||
*
|
||||
* @return 0 if succeed, 1 if failed
|
||||
*/
|
||||
static int gw_mysql_set_timeouts(
|
||||
MYSQL* handle,
|
||||
int read_timeout,
|
||||
int write_timeout,
|
||||
int connect_timeout)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if ((rc = mysql_options(handle,
|
||||
MYSQL_OPT_READ_TIMEOUT,
|
||||
(void *)&read_timeout)))
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : failed to set read timeout for backend "
|
||||
"connection.")));
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
if ((rc = mysql_options(handle,
|
||||
MYSQL_OPT_CONNECT_TIMEOUT,
|
||||
(void *)&connect_timeout)))
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : failed to set connect timeout for backend "
|
||||
"connection.")));
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
if ((rc = mysql_options(handle,
|
||||
MYSQL_OPT_WRITE_TIMEOUT,
|
||||
(void *)&write_timeout)))
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : failed to set write timeout for backend "
|
||||
"connection.")));
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
retblock:
|
||||
return rc;
|
||||
}
|
@ -310,7 +310,7 @@ DCB *clone;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
clone->fd = DCBFD_CLONED;;
|
||||
clone->fd = DCBFD_CLOSED;
|
||||
clone->flags |= DCBF_CLONE;
|
||||
clone->state = orig->state;
|
||||
clone->data = orig->data;
|
||||
@ -321,7 +321,10 @@ DCB *clone;
|
||||
clone->protocol = orig->protocol;
|
||||
|
||||
clone->func.write = dcb_null_write;
|
||||
clone->func.close = dcb_null_close;
|
||||
/**
|
||||
* Close triggers closing of router session as well which is needed.
|
||||
*/
|
||||
clone->func.close = orig->func.close;
|
||||
clone->func.auth = dcb_null_auth;
|
||||
|
||||
return clone;
|
||||
@ -385,22 +388,25 @@ DCB_CALLBACK *cb;
|
||||
*/
|
||||
{
|
||||
SESSION *local_session = dcb->session;
|
||||
dcb->session = NULL;
|
||||
CHK_SESSION(local_session);
|
||||
/*<
|
||||
* Remove reference from session if dcb is client.
|
||||
*/
|
||||
if (local_session->client == dcb) {
|
||||
local_session->client = NULL;
|
||||
}
|
||||
dcb->session = NULL;
|
||||
/**
|
||||
* Set session's client pointer NULL so that other threads
|
||||
* won't try to call dcb_close for client DCB
|
||||
* after this call.
|
||||
*/
|
||||
if (local_session->client == dcb)
|
||||
{
|
||||
spinlock_acquire(&local_session->ses_lock);
|
||||
local_session->client = NULL;
|
||||
spinlock_release(&local_session->ses_lock);
|
||||
}
|
||||
session_free(local_session);
|
||||
}
|
||||
}
|
||||
|
||||
if (dcb->protocol && ((dcb->flags & DCBF_CLONE) ==0))
|
||||
free(dcb->protocol);
|
||||
if (dcb->data && ((dcb->flags & DCBF_CLONE) ==0))
|
||||
free(dcb->data);
|
||||
if (dcb->protocol && (!DCB_IS_CLONE(dcb)))
|
||||
free(dcb->protocol);
|
||||
if (dcb->remote)
|
||||
free(dcb->remote);
|
||||
if (dcb->user)
|
||||
@ -425,7 +431,6 @@ DCB_CALLBACK *cb;
|
||||
}
|
||||
spinlock_release(&dcb->cb_lock);
|
||||
|
||||
|
||||
bitmask_free(&dcb->memdata.bitmask);
|
||||
free(dcb);
|
||||
}
|
||||
@ -900,7 +905,8 @@ int below_water;
|
||||
dcb->state != DCB_STATE_POLLING &&
|
||||
dcb->state != DCB_STATE_LISTENING &&
|
||||
dcb->state != DCB_STATE_NOPOLLING &&
|
||||
dcb->session->state != SESSION_STATE_STOPPING))
|
||||
(dcb->session == NULL ||
|
||||
dcb->session->state != SESSION_STATE_STOPPING)))
|
||||
{
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
@ -911,7 +917,7 @@ int below_water;
|
||||
dcb,
|
||||
STRDCBSTATE(dcb->state),
|
||||
dcb->fd)));
|
||||
ss_dassert(false);
|
||||
//ss_dassert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1254,20 +1260,20 @@ dcb_close(DCB *dcb)
|
||||
if (rc == 0)
|
||||
{
|
||||
/**
|
||||
* close protocol and router session
|
||||
*/
|
||||
* close protocol and router session
|
||||
*/
|
||||
if (dcb->func.close != NULL)
|
||||
{
|
||||
dcb->func.close(dcb);
|
||||
}
|
||||
/** Call possible callback for this DCB in case of close */
|
||||
dcb_call_callback(dcb, DCB_REASON_CLOSE);
|
||||
|
||||
|
||||
if (dcb->state == DCB_STATE_NOPOLLING)
|
||||
{
|
||||
dcb_add_to_zombieslist(dcb);
|
||||
}
|
||||
}
|
||||
}
|
||||
ss_dassert(dcb->state == DCB_STATE_NOPOLLING ||
|
||||
dcb->state == DCB_STATE_ZOMBIE);
|
||||
}
|
||||
@ -1604,7 +1610,9 @@ void dcb_hashtable_stats(
|
||||
hashsize);
|
||||
|
||||
dcb_printf(dcb, "\tNo. of entries: %d\n", total);
|
||||
dcb_printf(dcb, "\tAverage chain length: %.1f\n", (float)total / hashsize);
|
||||
dcb_printf(dcb,
|
||||
"\tAverage chain length: %.1f\n",
|
||||
(hashsize == 0 ? (float)hashsize : (float)total / hashsize));
|
||||
dcb_printf(dcb, "\tLongest chain length: %d\n", longest);
|
||||
}
|
||||
|
||||
@ -1983,6 +1991,12 @@ DCB_CALLBACK *cb, *nextcb;
|
||||
{
|
||||
nextcb = cb->next;
|
||||
spinlock_release(&dcb->cb_lock);
|
||||
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"%lu [dcb_call_callback] %s",
|
||||
pthread_self(),
|
||||
STRDCBREASON(reason))));
|
||||
|
||||
cb->cb(dcb, reason, cb->userdata);
|
||||
spinlock_acquire(&dcb->cb_lock);
|
||||
cb = nextcb;
|
||||
@ -2068,6 +2082,10 @@ dcb_get_next (DCB* dcb)
|
||||
void
|
||||
dcb_call_foreach(DCB_REASON reason)
|
||||
{
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"%lu [dcb_call_foreach]",
|
||||
pthread_self())));
|
||||
|
||||
switch (reason) {
|
||||
case DCB_REASON_CLOSE:
|
||||
case DCB_REASON_DRAINED:
|
||||
@ -2100,7 +2118,7 @@ dcb_call_foreach(DCB_REASON reason)
|
||||
|
||||
/**
|
||||
* Null protocol write routine used for cloned dcb's. It merely consumes
|
||||
* buffers written on the cloned DCB.
|
||||
* buffers written on the cloned DCB and sets the DCB_REPLIED flag.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
* @param buf The buffer being written
|
||||
@ -2113,6 +2131,9 @@ dcb_null_write(DCB *dcb, GWBUF *buf)
|
||||
{
|
||||
buf = gwbuf_consume(buf, GWBUF_LENGTH(buf));
|
||||
}
|
||||
|
||||
dcb->flags |= DCBF_REPLIED;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -91,28 +91,31 @@ filter_free(FILTER_DEF *filter)
|
||||
{
|
||||
FILTER_DEF *ptr;
|
||||
|
||||
/* First of all remove from the linked list */
|
||||
spinlock_acquire(&filter_spin);
|
||||
if (allFilters == filter)
|
||||
if (filter)
|
||||
{
|
||||
allFilters = filter->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = allFilters;
|
||||
while (ptr && ptr->next != filter)
|
||||
/* First of all remove from the linked list */
|
||||
spinlock_acquire(&filter_spin);
|
||||
if (allFilters == filter)
|
||||
{
|
||||
ptr = ptr->next;
|
||||
allFilters = filter->next;
|
||||
}
|
||||
if (ptr)
|
||||
ptr->next = filter->next;
|
||||
}
|
||||
spinlock_release(&filter_spin);
|
||||
else
|
||||
{
|
||||
ptr = allFilters;
|
||||
while (ptr && ptr->next != filter)
|
||||
{
|
||||
ptr = ptr->next;
|
||||
}
|
||||
if (ptr)
|
||||
ptr->next = filter->next;
|
||||
}
|
||||
spinlock_release(&filter_spin);
|
||||
|
||||
/* Clean up session and free the memory */
|
||||
free(filter->name);
|
||||
free(filter->module);
|
||||
free(filter);
|
||||
/* Clean up session and free the memory */
|
||||
free(filter->name);
|
||||
free(filter->module);
|
||||
free(filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include <config.h>
|
||||
#include <poll.h>
|
||||
#include <housekeeper.h>
|
||||
#include <service.h>
|
||||
#include <memlog.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
@ -216,6 +217,7 @@ static void sigterm_handler (int i) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"MaxScale received signal SIGTERM. Exiting.")));
|
||||
skygw_log_sync_all();
|
||||
shutdown_server();
|
||||
}
|
||||
|
||||
@ -227,6 +229,7 @@ sigint_handler (int i)
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"MaxScale received signal SIGINT. Shutting down.")));
|
||||
skygw_log_sync_all();
|
||||
shutdown_server();
|
||||
fprintf(stderr, "\n\nShutting down MaxScale\n\n");
|
||||
}
|
||||
@ -269,6 +272,8 @@ sigfatal_handler (int i)
|
||||
}
|
||||
}
|
||||
|
||||
skygw_log_sync_all();
|
||||
|
||||
/* re-raise signal to enforce core dump */
|
||||
fprintf(stderr, "\n\nWriting core dump\n");
|
||||
signal_set(i, SIG_DFL);
|
||||
@ -1526,7 +1531,7 @@ int main(int argc, char **argv)
|
||||
free(log_context);
|
||||
}
|
||||
|
||||
/*<
|
||||
/**
|
||||
* Init Log Manager for MaxScale.
|
||||
* If $MAXSCALE_HOME is set then write the logs into $MAXSCALE_HOME/log.
|
||||
* The skygw_logmanager_init expects to take arguments as passed to main
|
||||
@ -1536,23 +1541,28 @@ int main(int argc, char **argv)
|
||||
{
|
||||
char buf[1024];
|
||||
char *argv[8];
|
||||
bool succp;
|
||||
|
||||
bool succp;
|
||||
/** Set log directory under $MAXSCALE_HOME/log */
|
||||
sprintf(buf, "%s/log", home_dir);
|
||||
if(mkdir(buf, 0777) != 0){
|
||||
|
||||
if(errno != EEXIST){
|
||||
fprintf(stderr,
|
||||
"Error: Cannot create log directory: %s\n",buf);
|
||||
goto return_main;
|
||||
}
|
||||
}
|
||||
|
||||
if(mkdir(buf, 0777) != 0)
|
||||
{
|
||||
if(errno != EEXIST)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Error: Cannot create log directory: %s\n",
|
||||
buf);
|
||||
goto return_main;
|
||||
}
|
||||
}
|
||||
argv[0] = "MaxScale";
|
||||
argv[1] = "-j";
|
||||
argv[2] = buf;
|
||||
|
||||
if (logtofile)
|
||||
{
|
||||
argv[3] = "-l"; /*< write to syslog */
|
||||
/** Logs that should be syslogged */
|
||||
argv[4] = "LOGFILE_MESSAGE,LOGFILE_ERROR"
|
||||
"LOGFILE_DEBUG,LOGFILE_TRACE";
|
||||
argv[5] = NULL;
|
||||
@ -1561,9 +1571,9 @@ int main(int argc, char **argv)
|
||||
else
|
||||
{
|
||||
argv[3] = "-s"; /*< store to shared memory */
|
||||
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< ..these logs to shm */
|
||||
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< to shm */
|
||||
argv[5] = "-l"; /*< write to syslog */
|
||||
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< ..these logs to syslog */
|
||||
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< to syslog */
|
||||
argv[7] = NULL;
|
||||
succp = skygw_logmanager_init(7, argv);
|
||||
}
|
||||
@ -1574,8 +1584,7 @@ int main(int argc, char **argv)
|
||||
goto return_main;
|
||||
}
|
||||
}
|
||||
|
||||
/*<
|
||||
/**
|
||||
* Resolve the full pathname for configuration file and check for
|
||||
* read accessibility.
|
||||
*/
|
||||
@ -1837,7 +1846,8 @@ return_main:
|
||||
void
|
||||
shutdown_server()
|
||||
{
|
||||
poll_shutdown();
|
||||
service_shutdown();
|
||||
poll_shutdown();
|
||||
hkshutdown();
|
||||
memlog_flush_all();
|
||||
log_flush_shutdown();
|
||||
|
@ -81,7 +81,7 @@ setipaddress(struct in_addr *a, char *p) {
|
||||
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : getaddrinfo failed for [%s] due [%s]",
|
||||
"Error: Failed to obtain address for host %s, %s",
|
||||
p,
|
||||
gai_strerror(rc))));
|
||||
|
||||
@ -94,7 +94,7 @@ setipaddress(struct in_addr *a, char *p) {
|
||||
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : getaddrinfo failed for [%s] due [%s]",
|
||||
"Error: Failed to obtain address for host %s, %s",
|
||||
p,
|
||||
gai_strerror(rc))));
|
||||
|
||||
|
@ -449,28 +449,33 @@ void hashtable_get_stats(
|
||||
int i;
|
||||
int j;
|
||||
|
||||
ht = (HASHTABLE *)table;
|
||||
CHK_HASHTABLE(ht);
|
||||
*nelems = 0;
|
||||
*longest = 0;
|
||||
hashtable_read_lock(ht);
|
||||
|
||||
for (i = 0; i < ht->hashsize; i++)
|
||||
*nelems = 0;
|
||||
*longest = 0;
|
||||
*hashsize = 0;
|
||||
|
||||
if (table != NULL)
|
||||
{
|
||||
j = 0;
|
||||
entries = ht->entries[i];
|
||||
while (entries)
|
||||
ht = (HASHTABLE *)table;
|
||||
CHK_HASHTABLE(ht);
|
||||
hashtable_read_lock(ht);
|
||||
|
||||
for (i = 0; i < ht->hashsize; i++)
|
||||
{
|
||||
j++;
|
||||
entries = entries->next;
|
||||
j = 0;
|
||||
entries = ht->entries[i];
|
||||
while (entries)
|
||||
{
|
||||
j++;
|
||||
entries = entries->next;
|
||||
}
|
||||
*nelems += j;
|
||||
if (j > *longest) {
|
||||
*longest = j;
|
||||
}
|
||||
}
|
||||
*nelems += j;
|
||||
if (j > *longest) {
|
||||
*longest = j;
|
||||
}
|
||||
*hashsize = ht->hashsize;
|
||||
hashtable_read_unlock(ht);
|
||||
}
|
||||
*hashsize = ht->hashsize;
|
||||
hashtable_read_unlock(ht);
|
||||
}
|
||||
|
||||
|
||||
@ -503,7 +508,7 @@ hashtable_read_lock(HASHTABLE *table)
|
||||
;
|
||||
spinlock_acquire(&table->spin);
|
||||
}
|
||||
table->n_readers++;
|
||||
atomic_add(&table->n_readers, 1);
|
||||
spinlock_release(&table->spin);
|
||||
}
|
||||
|
||||
|
@ -121,6 +121,40 @@ unsigned char *ptr;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the length of MySQL packet and how much is missing from the GWBUF
|
||||
* passed as parameter.
|
||||
*
|
||||
* This routine assumes that there is only one MySQL packet in the buffer.
|
||||
*
|
||||
* @param buf buffer list including the query, may consist of
|
||||
* multiple buffers
|
||||
* @param nbytes_missing pointer to missing bytecount
|
||||
*
|
||||
* @return the length of MySQL packet and writes missing bytecount to
|
||||
* nbytes_missing.
|
||||
*/
|
||||
int modutil_MySQL_query_len(
|
||||
GWBUF* buf,
|
||||
int* nbytes_missing)
|
||||
{
|
||||
int len;
|
||||
int buflen;
|
||||
|
||||
if (!modutil_is_SQL(buf))
|
||||
{
|
||||
len = 0;
|
||||
goto retblock;
|
||||
}
|
||||
len = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf));
|
||||
*nbytes_missing = len-1;
|
||||
buflen = gwbuf_length(buf);
|
||||
|
||||
*nbytes_missing -= buflen-5;
|
||||
|
||||
retblock:
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -178,7 +212,7 @@ GWBUF *addition;
|
||||
|
||||
/**
|
||||
* Extract the SQL from a COM_QUERY packet and return in a NULL terminated buffer.
|
||||
* The buffer shoudl be freed by the caller when it is no longer required.
|
||||
* The buffer should be freed by the caller when it is no longer required.
|
||||
*
|
||||
* If the packet is not a COM_QUERY packet then the function will return NULL
|
||||
*
|
||||
@ -252,7 +286,7 @@ modutil_get_query(GWBUF *buf)
|
||||
|
||||
case MYSQL_COM_QUERY:
|
||||
len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */
|
||||
if (len < 1 || (query_str = (char *)malloc(len+1)) == NULL)
|
||||
if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
@ -262,7 +296,7 @@ modutil_get_query(GWBUF *buf)
|
||||
|
||||
default:
|
||||
len = strlen(STRPACKETTYPE(packet_type))+1;
|
||||
if (len < 1 || (query_str = (char *)malloc(len+1)) == NULL)
|
||||
if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
|
||||
{
|
||||
goto retblock;
|
||||
}
|
||||
|
@ -674,6 +674,14 @@ poll_set_maxwait(unsigned int maxwait)
|
||||
* to process the DCB. If there are pending events the DCB will be moved to the
|
||||
* back of the queue so that other DCB's will have a share of the threads to
|
||||
* execute events for them.
|
||||
*
|
||||
* Including session id to log entries depends on this function. Assumption is
|
||||
* that when maxscale thread starts processing of an event it processes one
|
||||
* and only one session until it returns from this function. Session id is
|
||||
* read to thread's local storage in macro LOGIF_MAYBE(...) and reset back
|
||||
* to zero just before returning in LOGIF(...) macro.
|
||||
* Thread local storage (tls_log_info_t) follows thread and is accessed every
|
||||
* time log is written to particular log.
|
||||
*
|
||||
* @param thread_id The thread ID of the calling thread
|
||||
* @return 0 if no DCB's have been processed
|
||||
@ -797,7 +805,7 @@ unsigned long qtime;
|
||||
simple_mutex_unlock(&dcb->dcb_write_lock);
|
||||
#else
|
||||
atomic_add(&pollStats.n_write, 1);
|
||||
|
||||
/** Read session id to thread's local storage */
|
||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||
dcb,
|
||||
&tls_log_info.li_sesid,
|
||||
@ -851,6 +859,7 @@ unsigned long qtime;
|
||||
dcb,
|
||||
dcb->fd)));
|
||||
atomic_add(&pollStats.n_read, 1);
|
||||
/** Read session id to thread's local storage */
|
||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||
dcb,
|
||||
&tls_log_info.li_sesid,
|
||||
@ -890,6 +899,7 @@ unsigned long qtime;
|
||||
strerror(eno))));
|
||||
}
|
||||
atomic_add(&pollStats.n_error, 1);
|
||||
/** Read session id to thread's local storage */
|
||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||
dcb,
|
||||
&tls_log_info.li_sesid,
|
||||
@ -918,6 +928,7 @@ unsigned long qtime;
|
||||
{
|
||||
dcb->flags |= DCBF_HUNG;
|
||||
spinlock_release(&dcb->dcb_initlock);
|
||||
/** Read session id to thread's local storage */
|
||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||
dcb,
|
||||
&tls_log_info.li_sesid,
|
||||
@ -950,6 +961,7 @@ unsigned long qtime;
|
||||
{
|
||||
dcb->flags |= DCBF_HUNG;
|
||||
spinlock_release(&dcb->dcb_initlock);
|
||||
/** Read session id to thread's local storage */
|
||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||
dcb,
|
||||
&tls_log_info.li_sesid,
|
||||
@ -1015,6 +1027,7 @@ unsigned long qtime;
|
||||
}
|
||||
}
|
||||
dcb->evq.processing = 0;
|
||||
/** Reset session id from thread's local storage */
|
||||
LOGIF(LT, tls_log_info.li_sesid = 0);
|
||||
spinlock_release(&pollqlock);
|
||||
|
||||
@ -1329,7 +1342,6 @@ void poll_add_epollin_event_to_dcb(
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void poll_add_event_to_dcb(
|
||||
DCB* dcb,
|
||||
GWBUF* buf,
|
||||
@ -1345,6 +1357,10 @@ static void poll_add_event_to_dcb(
|
||||
/** Set event to DCB */
|
||||
if (DCB_POLL_BUSY(dcb))
|
||||
{
|
||||
if (dcb->evq.pending_events == 0)
|
||||
{
|
||||
pollStats.evq_pending++;
|
||||
}
|
||||
dcb->evq.pending_events |= ev;
|
||||
}
|
||||
else
|
||||
@ -1365,6 +1381,8 @@ static void poll_add_event_to_dcb(
|
||||
dcb->evq.next = dcb;
|
||||
}
|
||||
pollStats.evq_length++;
|
||||
pollStats.evq_pending++;
|
||||
|
||||
if (pollStats.evq_length > pollStats.evq_max)
|
||||
{
|
||||
pollStats.evq_max = pollStats.evq_length;
|
||||
|
@ -68,25 +68,16 @@ server_alloc(char *servname, char *protocol, unsigned short port)
|
||||
{
|
||||
SERVER *server;
|
||||
|
||||
if ((server = (SERVER *)malloc(sizeof(SERVER))) == NULL)
|
||||
if ((server = (SERVER *)calloc(1, sizeof(SERVER))) == NULL)
|
||||
return NULL;
|
||||
server->name = strdup(servname);
|
||||
server->protocol = strdup(protocol);
|
||||
server->port = port;
|
||||
memset(&server->stats, 0, sizeof(SERVER_STATS));
|
||||
server->status = SERVER_RUNNING;
|
||||
server->nextdb = NULL;
|
||||
server->monuser = NULL;
|
||||
server->monpw = NULL;
|
||||
server->unique_name = NULL;
|
||||
server->server_string = NULL;
|
||||
server->node_id = -1;
|
||||
server->rlag = -2;
|
||||
server->node_ts = 0;
|
||||
server->parameters = NULL;
|
||||
server->master_id = -1;
|
||||
server->depth = -1;
|
||||
server->slaves = NULL;
|
||||
|
||||
spinlock_acquire(&server_spin);
|
||||
server->next = allServers;
|
||||
@ -451,6 +442,12 @@ void
|
||||
server_set_status(SERVER *server, int bit)
|
||||
{
|
||||
server->status |= bit;
|
||||
|
||||
/** clear error logged flag before the next failure */
|
||||
if (SERVER_IS_MASTER(server))
|
||||
{
|
||||
server->master_err_is_logged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,7 +87,6 @@ static void service_add_qualified_param(
|
||||
SERVICE* svc,
|
||||
CONFIG_PARAMETER* param);
|
||||
|
||||
|
||||
/**
|
||||
* Allocate a new service for the gateway to support
|
||||
*
|
||||
@ -102,7 +101,7 @@ service_alloc(const char *servname, const char *router)
|
||||
{
|
||||
SERVICE *service;
|
||||
|
||||
if ((service = (SERVICE *)malloc(sizeof(SERVICE))) == NULL)
|
||||
if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL)
|
||||
return NULL;
|
||||
if ((service->router = load_module(router, MODULE_ROUTER)) == NULL)
|
||||
{
|
||||
@ -132,27 +131,10 @@ SERVICE *service;
|
||||
free(service);
|
||||
return NULL;
|
||||
}
|
||||
service->version_string = NULL;
|
||||
memset(&service->stats, 0, sizeof(SERVICE_STATS));
|
||||
service->ports = NULL;
|
||||
service->stats.started = time(0);
|
||||
service->state = SERVICE_STATE_ALLOC;
|
||||
service->credentials.name = NULL;
|
||||
service->credentials.authdata = NULL;
|
||||
service->enable_root = 0;
|
||||
service->localhost_match_wildcard_host = 0;
|
||||
service->routerOptions = NULL;
|
||||
service->databases = NULL;
|
||||
service->svc_config_param = NULL;
|
||||
service->svc_config_version = 0;
|
||||
service->filters = NULL;
|
||||
service->n_filters = 0;
|
||||
service->weightby = 0;
|
||||
service->users = NULL;
|
||||
service->resources = NULL;
|
||||
spinlock_init(&service->spin);
|
||||
spinlock_init(&service->users_table_spin);
|
||||
memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE));
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
service->next = allServices;
|
||||
@ -233,11 +215,6 @@ GWPROTOCOL *funcs;
|
||||
(port->address == NULL ? "0.0.0.0" : port->address),
|
||||
port->port,
|
||||
service->name)));
|
||||
hashtable_free(service->users->data);
|
||||
free(service->users);
|
||||
dcb_free(port->listener);
|
||||
port->listener = NULL;
|
||||
goto retblock;
|
||||
}
|
||||
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
|
||||
* This way MaxScale could try reloading users' just after startup
|
||||
@ -349,24 +326,27 @@ serviceStart(SERVICE *service)
|
||||
SERV_PROTOCOL *port;
|
||||
int listeners = 0;
|
||||
|
||||
if((service->router_instance = service->router->createInstance(service,
|
||||
service->routerOptions)) == NULL)
|
||||
if ((service->router_instance = service->router->createInstance(service,
|
||||
service->routerOptions)) == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Failed to start router for service '%s'.",
|
||||
service->name)));
|
||||
return listeners;
|
||||
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
|
||||
"%s: Failed to create router instance for service. Service not started.",
|
||||
service->name)));
|
||||
service->state = SERVICE_STATE_FAILED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
port = service->ports;
|
||||
while (port)
|
||||
while (!service->svc_do_shutdown && port)
|
||||
{
|
||||
listeners += serviceStartPort(service, port);
|
||||
port = port->next;
|
||||
}
|
||||
if (listeners)
|
||||
{
|
||||
service->state = SERVICE_STATE_STARTED;
|
||||
service->stats.started = time(0);
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
@ -405,16 +385,16 @@ SERVICE *ptr;
|
||||
int n = 0,i;
|
||||
|
||||
ptr = allServices;
|
||||
while (ptr)
|
||||
while (ptr && !ptr->svc_do_shutdown)
|
||||
{
|
||||
n += (i = serviceStart(ptr));
|
||||
|
||||
if(i == 0)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Failed to start service '%s'.",
|
||||
ptr->name)));
|
||||
LOGFILE_ERROR,
|
||||
"Error : Failed to start service '%s'.",
|
||||
ptr->name)));
|
||||
}
|
||||
|
||||
ptr = ptr->next;
|
||||
@ -445,6 +425,7 @@ int listeners = 0;
|
||||
|
||||
port = port->next;
|
||||
}
|
||||
service->state = SERVICE_STATE_STOPPED;
|
||||
|
||||
return listeners;
|
||||
}
|
||||
@ -487,7 +468,7 @@ int
|
||||
service_free(SERVICE *service)
|
||||
{
|
||||
SERVICE *ptr;
|
||||
|
||||
SERVER_REF *srv;
|
||||
if (service->stats.n_current)
|
||||
return 0;
|
||||
/* First of all remove from the linked list */
|
||||
@ -509,6 +490,13 @@ SERVICE *ptr;
|
||||
spinlock_release(&service_spin);
|
||||
|
||||
/* Clean up session and free the memory */
|
||||
|
||||
while(service->dbref){
|
||||
srv = service->dbref;
|
||||
service->dbref = service->dbref->next;
|
||||
free(srv);
|
||||
}
|
||||
|
||||
free(service->name);
|
||||
free(service->routerModule);
|
||||
if (service->credentials.name)
|
||||
@ -587,8 +575,13 @@ void
|
||||
serviceAddBackend(SERVICE *service, SERVER *server)
|
||||
{
|
||||
spinlock_acquire(&service->spin);
|
||||
server->nextdb = service->databases;
|
||||
service->databases = server;
|
||||
SERVER_REF *sref;
|
||||
if((sref = calloc(1,sizeof(SERVER_REF))) != NULL)
|
||||
{
|
||||
sref->next = service->dbref;
|
||||
sref->server = server;
|
||||
service->dbref = sref;
|
||||
}
|
||||
spinlock_release(&service->spin);
|
||||
}
|
||||
|
||||
@ -602,12 +595,12 @@ serviceAddBackend(SERVICE *service, SERVER *server)
|
||||
int
|
||||
serviceHasBackend(SERVICE *service, SERVER *server)
|
||||
{
|
||||
SERVER *ptr;
|
||||
SERVER_REF *ptr;
|
||||
|
||||
spinlock_acquire(&service->spin);
|
||||
ptr = service->databases;
|
||||
while (ptr && ptr != server)
|
||||
ptr = ptr->nextdb;
|
||||
ptr = service->dbref;
|
||||
while (ptr && ptr->server != server)
|
||||
ptr = ptr->next;
|
||||
spinlock_release(&service->spin);
|
||||
|
||||
return ptr != NULL;
|
||||
@ -825,7 +818,7 @@ SERVICE *service;
|
||||
void
|
||||
printService(SERVICE *service)
|
||||
{
|
||||
SERVER *ptr = service->databases;
|
||||
SERVER_REF *ptr = service->dbref;
|
||||
struct tm result;
|
||||
char time_buf[30];
|
||||
int i;
|
||||
@ -838,8 +831,8 @@ int i;
|
||||
printf("\tBackend databases\n");
|
||||
while (ptr)
|
||||
{
|
||||
printf("\t\t%s:%d Protocol: %s\n", ptr->name, ptr->port, ptr->protocol);
|
||||
ptr = ptr->nextdb;
|
||||
printf("\t\t%s:%d Protocol: %s\n", ptr->server->name, ptr->server->port, ptr->server->protocol);
|
||||
ptr = ptr->next;
|
||||
}
|
||||
if (service->n_filters)
|
||||
{
|
||||
@ -906,7 +899,7 @@ SERVICE *ptr;
|
||||
*/
|
||||
void dprintService(DCB *dcb, SERVICE *service)
|
||||
{
|
||||
SERVER *server = service->databases;
|
||||
SERVER_REF *server = service->dbref;
|
||||
struct tm result;
|
||||
char timebuf[30];
|
||||
int i;
|
||||
@ -916,7 +909,22 @@ int i;
|
||||
service->name);
|
||||
dcb_printf(dcb, "\tRouter: %s (%p)\n",
|
||||
service->routerModule, service->router);
|
||||
if (service->router)
|
||||
switch (service->state)
|
||||
{
|
||||
case SERVICE_STATE_STARTED:
|
||||
dcb_printf(dcb, "\tState: Started\n");
|
||||
break;
|
||||
case SERVICE_STATE_STOPPED:
|
||||
dcb_printf(dcb, "\tState: Stopped\n");
|
||||
break;
|
||||
case SERVICE_STATE_FAILED:
|
||||
dcb_printf(dcb, "\tState: Failed\n");
|
||||
break;
|
||||
case SERVICE_STATE_ALLOC:
|
||||
dcb_printf(dcb, "\tState: Allocated\n");
|
||||
break;
|
||||
}
|
||||
if (service->router && service->router_instance)
|
||||
service->router->diagnostics(service->router_instance, dcb);
|
||||
dcb_printf(dcb, "\tStarted: %s",
|
||||
asctime_r(localtime_r(&service->stats.started, &result), timebuf));
|
||||
@ -935,9 +943,9 @@ int i;
|
||||
dcb_printf(dcb, "\tBackend databases\n");
|
||||
while (server)
|
||||
{
|
||||
dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->name, server->port,
|
||||
server->protocol);
|
||||
server = server->nextdb;
|
||||
dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->server->name, server->server->port,
|
||||
server->server->protocol);
|
||||
server = server->next;
|
||||
}
|
||||
if (service->weightby)
|
||||
dcb_printf(dcb, "\tRouting weight parameter: %s\n",
|
||||
@ -1403,3 +1411,16 @@ serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action)
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void service_shutdown()
|
||||
{
|
||||
SERVICE* svc;
|
||||
spinlock_acquire(&service_spin);
|
||||
svc = allServices;
|
||||
while (svc != NULL)
|
||||
{
|
||||
svc->svc_do_shutdown = true;
|
||||
svc = svc->next;
|
||||
}
|
||||
spinlock_release(&service_spin);
|
||||
}
|
@ -85,12 +85,21 @@ session_alloc(SERVICE *service, DCB *client_dcb)
|
||||
"session object due error %d, %s.",
|
||||
errno,
|
||||
strerror(errno))));
|
||||
if (client_dcb->data && !DCB_IS_CLONE(client_dcb))
|
||||
{
|
||||
free(client_dcb->data);
|
||||
client_dcb->data = NULL;
|
||||
}
|
||||
goto return_session;
|
||||
}
|
||||
#if defined(SS_DEBUG)
|
||||
session->ses_chk_top = CHK_NUM_SESSION;
|
||||
session->ses_chk_tail = CHK_NUM_SESSION;
|
||||
#endif
|
||||
if (DCB_IS_CLONE(client_dcb))
|
||||
{
|
||||
session->ses_is_child = true;
|
||||
}
|
||||
spinlock_init(&session->ses_lock);
|
||||
/*<
|
||||
* Prevent backend threads from accessing before session is completely
|
||||
@ -149,6 +158,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
|
||||
* Decrease refcount, set dcb's session pointer NULL
|
||||
* and set session pointer to NULL.
|
||||
*/
|
||||
session->client = NULL;
|
||||
session_free(session);
|
||||
client_dcb->session = NULL;
|
||||
session = NULL;
|
||||
@ -189,6 +199,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
|
||||
* Decrease refcount, set dcb's session pointer NULL
|
||||
* and set session pointer to NULL.
|
||||
*/
|
||||
session->client = NULL;
|
||||
session_free(session);
|
||||
client_dcb->session = NULL;
|
||||
session = NULL;
|
||||
@ -207,6 +218,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
|
||||
if (session->state != SESSION_STATE_READY)
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
session->client = NULL;
|
||||
session_free(session);
|
||||
client_dcb->session = NULL;
|
||||
session = NULL;
|
||||
@ -331,12 +343,16 @@ int session_unlink_dcb(
|
||||
|
||||
if (nlink == 0)
|
||||
{
|
||||
session->state = SESSION_STATE_FREE;
|
||||
session->state = SESSION_STATE_TO_BE_FREED;
|
||||
}
|
||||
|
||||
if (dcb != NULL)
|
||||
{
|
||||
dcb->session = NULL;
|
||||
if (session->client == dcb)
|
||||
{
|
||||
session->client = NULL;
|
||||
}
|
||||
dcb->session = NULL;
|
||||
}
|
||||
spinlock_release(&session->ses_lock);
|
||||
|
||||
@ -357,7 +373,6 @@ bool session_free(
|
||||
int i;
|
||||
|
||||
CHK_SESSION(session);
|
||||
|
||||
/*<
|
||||
* Remove one reference. If there are no references left,
|
||||
* free session.
|
||||
@ -388,8 +403,12 @@ bool session_free(
|
||||
spinlock_release(&session_spin);
|
||||
atomic_add(&session->service->stats.n_current, -1);
|
||||
|
||||
/* Free router_session and session */
|
||||
if (session->router_session) {
|
||||
/**
|
||||
* If session is not child of some other session, free router_session.
|
||||
* Otherwise let the parent free it.
|
||||
*/
|
||||
if (!session->ses_is_child && session->router_session)
|
||||
{
|
||||
session->service->router->freeSession(
|
||||
session->service->router_instance,
|
||||
session->router_session);
|
||||
@ -422,7 +441,17 @@ bool session_free(
|
||||
/** Disable trace and decrease trace logger counter */
|
||||
session_disable_log(session, LT);
|
||||
|
||||
free(session);
|
||||
/** If session doesn't have parent referencing to it, it can be freed */
|
||||
if (!session->ses_is_child)
|
||||
{
|
||||
session->state = SESSION_STATE_FREE;
|
||||
|
||||
if (session->data)
|
||||
{
|
||||
free(session->data);
|
||||
}
|
||||
free(session);
|
||||
}
|
||||
succp = true;
|
||||
|
||||
return_succp :
|
||||
@ -687,6 +716,15 @@ session_state(int state)
|
||||
return "Listener Session";
|
||||
case SESSION_STATE_LISTENER_STOPPED:
|
||||
return "Stopped Listener Session";
|
||||
#ifdef SS_DEBUG
|
||||
case SESSION_STATE_STOPPING:
|
||||
return "Stopping session";
|
||||
case SESSION_STATE_TO_BE_FREED:
|
||||
return "Session to be freed";
|
||||
case SESSION_STATE_FREE:
|
||||
return "Freed session";
|
||||
|
||||
#endif
|
||||
default:
|
||||
return "Invalid State";
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ target_link_libraries(test_service fullcore)
|
||||
target_link_libraries(test_server fullcore)
|
||||
target_link_libraries(test_users fullcore)
|
||||
target_link_libraries(test_adminusers fullcore)
|
||||
add_test(testMySQLUsers test_mysql_users)
|
||||
target_link_libraries(testmemlog fullcore)
|
||||
add_test(testMySQLUsers test_mysql_users)
|
||||
add_test(TestHash test_hash)
|
||||
add_test(TestHint test_hint)
|
||||
add_test(TestSpinlock test_spinlock)
|
||||
@ -40,3 +40,17 @@ add_test(TestServer test_server)
|
||||
add_test(TestUsers test_users)
|
||||
add_test(TestAdminUsers test_adminusers)
|
||||
add_test(TestMemlog testmemlog)
|
||||
set_tests_properties(testMySQLUsers
|
||||
TestHash
|
||||
TestHint
|
||||
TestSpinlock
|
||||
TestFilter
|
||||
TestBuffer
|
||||
TestDCB
|
||||
TestModutil
|
||||
TestPoll
|
||||
TestService
|
||||
TestServer
|
||||
TestUsers
|
||||
TestAdminUsers
|
||||
TestMemlog PROPERTIES ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/)
|
||||
|
@ -272,6 +272,8 @@ char *home, buf[1024];
|
||||
if ((home = getenv("MAXSCALE_HOME")) == NULL || strlen(home) >= 1024)
|
||||
home = "/usr/local/skysql";
|
||||
sprintf(buf, "%s/etc/passwd", home);
|
||||
if(!is_valid_posix_path(buf))
|
||||
exit(1);
|
||||
if (strcmp(buf, "/etc/passwd") != 0)
|
||||
unlink(buf);
|
||||
|
||||
@ -281,6 +283,9 @@ char *home, buf[1024];
|
||||
result += test4();
|
||||
result += test5();
|
||||
|
||||
/* Add the default user back so other tests can use it */
|
||||
admin_add_user("admin", "skysql");
|
||||
|
||||
exit(result);
|
||||
}
|
||||
|
||||
|
@ -49,11 +49,13 @@ HINT *hint;
|
||||
char* name = strdup("name");
|
||||
hint = hint_create_parameter(NULL, name, "value");
|
||||
free(name);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL != hint, "New hint list should not be null");
|
||||
ss_info_dassert(0 == strcmp("value", hint->value), "Hint value should be correct");
|
||||
ss_info_dassert(0 != hint_exists(&hint, HINT_PARAMETER), "Hint of parameter type should exist");
|
||||
ss_dfprintf(stderr, "\t..done\nFree hints.");
|
||||
if (NULL != hint) hint_free(hint);
|
||||
skygw_log_sync_all();
|
||||
ss_dfprintf(stderr, "\t..done\n");
|
||||
|
||||
return 0;
|
||||
|
@ -51,7 +51,7 @@ int result;
|
||||
"testpoll : Initialise the polling system.");
|
||||
poll_init();
|
||||
ss_dfprintf(stderr, "\t..done\nAdd a DCB");
|
||||
dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER);
|
||||
dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER);
|
||||
|
||||
if(dcb == NULL){
|
||||
ss_dfprintf(stderr, "\nError on function call: dcb_alloc() returned NULL.\n");
|
||||
|
@ -48,7 +48,7 @@ char *status;
|
||||
ss_dfprintf(stderr,
|
||||
"testserver : creating server called MyServer");
|
||||
server = server_alloc("MyServer", "HTTPD", 9876);
|
||||
|
||||
skygw_log_sync_all();
|
||||
|
||||
//ss_info_dassert(NULL != service, "New server with valid protocol and port must not be null");
|
||||
//ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation");
|
||||
@ -56,26 +56,32 @@ char *status;
|
||||
ss_dfprintf(stderr, "\t..done\nTest Parameter for Server.");
|
||||
ss_info_dassert(NULL == serverGetParameter(server, "name"), "Parameter should be null when not set");
|
||||
serverAddParameter(server, "name", "value");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 == strcmp("value", serverGetParameter(server, "name")), "Parameter should be returned correctly");
|
||||
ss_dfprintf(stderr, "\t..done\nTesting Unique Name for Server.");
|
||||
ss_info_dassert(NULL == server_find_by_unique_name("uniquename"), "Should not find non-existent unique name.");
|
||||
server_set_unique_name(server, "uniquename");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(server == server_find_by_unique_name("uniquename"), "Should find by unique name.");
|
||||
ss_dfprintf(stderr, "\t..done\nTesting Status Setting for Server.");
|
||||
status = server_status(server);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 == strcmp("Running", status), "Status of Server should be Running by default.");
|
||||
if (NULL != status) free(status);
|
||||
server_set_status(server, SERVER_MASTER);
|
||||
status = server_status(server);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 == strcmp("Master, Running", status), "Should find correct status.");
|
||||
server_clear_status(server, SERVER_MASTER);
|
||||
free(status);
|
||||
status = server_status(server);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 == strcmp("Running", status), "Status of Server should be Running after master status cleared.");
|
||||
if (NULL != status) free(status);
|
||||
ss_dfprintf(stderr, "\t..done\nRun Prints for Server and all Servers.");
|
||||
printServer(server);
|
||||
printAllServers();
|
||||
skygw_log_sync_all();
|
||||
ss_dfprintf(stderr, "\t..done\nFreeing Server.");
|
||||
ss_info_dassert(0 != server_free(server), "Free should succeed");
|
||||
ss_dfprintf(stderr, "\t..done\n");
|
||||
|
@ -30,9 +30,9 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <maxscale_test.h>
|
||||
#include <service.h>
|
||||
|
||||
#include <poll.h>
|
||||
/**
|
||||
* test1 Allocate a service and do lots of other things
|
||||
*
|
||||
@ -42,15 +42,27 @@ test1()
|
||||
{
|
||||
SERVICE *service;
|
||||
int result;
|
||||
|
||||
int argc = 3;
|
||||
char buffer[1024];
|
||||
sprintf(buffer,"%s",TEST_LOG_DIR);
|
||||
char* argv[] = {
|
||||
"log_manager",
|
||||
"-j",
|
||||
buffer,
|
||||
NULL
|
||||
};
|
||||
skygw_logmanager_init(argc,argv);
|
||||
poll_init();
|
||||
/* Service tests */
|
||||
ss_dfprintf(stderr,
|
||||
"testservice : creating service called MyService with router nonexistent");
|
||||
service = service_alloc("MyService", "non-existent");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL == service, "New service with invalid router should be null");
|
||||
ss_info_dassert(0 == service_isvalid(service), "Service must not be valid after incorrect creation");
|
||||
ss_dfprintf(stderr, "\t..done\nValid service creation, router testroute.");
|
||||
service = service_alloc("MyService", "testroute");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL != service, "New service with valid router must not be null");
|
||||
ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation");
|
||||
ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name");
|
||||
@ -58,12 +70,16 @@ int result;
|
||||
ss_info_dassert(0 != serviceAddProtocol(service, "HTTPD", "localhost", 9876), "Add Protocol should succeed");
|
||||
ss_info_dassert(0 != serviceHasProtocol(service, "HTTPD", 9876), "Service should have new protocol as requested");
|
||||
serviceStartProtocol(service, "HTTPD", 9876);
|
||||
skygw_log_sync_all();
|
||||
ss_dfprintf(stderr, "\t..done\nStarting Service.");
|
||||
result = serviceStart(service);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 != result, "Start should succeed");
|
||||
result = serviceStop(service);
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 != result, "Stop should succeed");
|
||||
result = serviceStartAll();
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(0 != result, "Start all should succeed");
|
||||
|
||||
ss_dfprintf(stderr, "\t..done\nStopping Service.");
|
||||
|
@ -33,6 +33,8 @@
|
||||
|
||||
#include <users.h>
|
||||
|
||||
#include "log_manager.h"
|
||||
|
||||
/**
|
||||
* test1 Allocate table of users and mess around with it
|
||||
*
|
||||
@ -49,26 +51,39 @@ int result, count;
|
||||
ss_dfprintf(stderr,
|
||||
"testusers : Initialise the user table.");
|
||||
users = users_alloc();
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL != users, "Allocating user table should not return NULL.")
|
||||
ss_dfprintf(stderr, "\t..done\nAdd a user");
|
||||
count = users_add(users, "username", "authorisation");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(1 == count, "Should add one user");
|
||||
authdata = users_fetch(users, "username");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL != authdata, "Fetch valid user must not return NULL");
|
||||
ss_info_dassert(0 == strcmp("authorisation", authdata), "User authorisation should be correct");
|
||||
ss_dfprintf(stderr, "\t..done\nPrint users");
|
||||
usersPrint(users);
|
||||
skygw_log_sync_all();
|
||||
ss_dfprintf(stderr, "\t..done\nUpdate a user");
|
||||
count = users_update(users, "username", "newauth");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(1 == count, "Should update just one user");
|
||||
authdata = users_fetch(users, "username");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(NULL != authdata, "Fetch valid user must not return NULL");
|
||||
ss_info_dassert(0 == strcmp("newauth", authdata), "User authorisation should be correctly updated");
|
||||
|
||||
ss_dfprintf(stderr, "\t..done\nAdd another user");
|
||||
count = users_add(users, "username2", "authorisation2");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(1 == count, "Should add one user");
|
||||
ss_dfprintf(stderr, "\t..done\nDelete a user.");
|
||||
count = users_delete(users, "username");
|
||||
skygw_log_sync_all();
|
||||
ss_info_dassert(1 == count, "Should delete just one user");
|
||||
ss_dfprintf(stderr, "\t..done\nFree user table.");
|
||||
users_free(users);
|
||||
skygw_log_sync_all();
|
||||
ss_dfprintf(stderr, "\t..done\n");
|
||||
|
||||
return 0;
|
||||
|
@ -183,32 +183,41 @@ char *sep;
|
||||
void *user;
|
||||
|
||||
dcb_printf(dcb, "Users table data\n");
|
||||
dcb_hashtable_stats(dcb, users->data);
|
||||
if ((iter = hashtable_iterator(users->data)) != NULL)
|
||||
|
||||
if (users == NULL || users->data == NULL)
|
||||
{
|
||||
dcb_printf(dcb, "User names: ");
|
||||
sep = "";
|
||||
dcb_printf(dcb, "Users table is empty\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_hashtable_stats(dcb, users->data);
|
||||
|
||||
if ((iter = hashtable_iterator(users->data)) != NULL)
|
||||
{
|
||||
dcb_printf(dcb, "User names: ");
|
||||
sep = "";
|
||||
|
||||
if (users->usersCustomUserFormat != NULL) {
|
||||
while ((user = hashtable_next(iter)) != NULL)
|
||||
{
|
||||
char *custom_user;
|
||||
custom_user = users->usersCustomUserFormat(user);
|
||||
if (custom_user) {
|
||||
dcb_printf(dcb, "%s%s", sep, custom_user);
|
||||
free(custom_user);
|
||||
if (users->usersCustomUserFormat != NULL) {
|
||||
while ((user = hashtable_next(iter)) != NULL)
|
||||
{
|
||||
char *custom_user;
|
||||
custom_user = users->usersCustomUserFormat(user);
|
||||
if (custom_user) {
|
||||
dcb_printf(dcb, "%s%s", sep, custom_user);
|
||||
free(custom_user);
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ((user = hashtable_next(iter)) != NULL)
|
||||
{
|
||||
dcb_printf(dcb, "%s%s", sep, (char *)user);
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ((user = hashtable_next(iter)) != NULL)
|
||||
{
|
||||
dcb_printf(dcb, "%s%s", sep, (char *)user);
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
dcb_printf(dcb, "\n");
|
||||
hashtable_iterator_free(iter);
|
||||
hashtable_iterator_free(iter);
|
||||
}
|
||||
}
|
||||
dcb_printf(dcb, "\n");
|
||||
}
|
||||
|
@ -184,6 +184,7 @@ extern GWBUF *gwbuf_rtrim(GWBUF *head, unsigned int length);
|
||||
extern unsigned int gwbuf_length(GWBUF *head);
|
||||
extern GWBUF *gwbuf_clone_portion(GWBUF *head, size_t offset, size_t len);
|
||||
extern GWBUF *gwbuf_clone_transform(GWBUF *head, gwbuf_type_t type);
|
||||
extern GWBUF *gwbuf_clone_all(GWBUF* head);
|
||||
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);
|
||||
@ -195,7 +196,6 @@ void gwbuf_add_buffer_object(GWBUF* buf,
|
||||
void* data,
|
||||
void (*donefun_fp)(void *));
|
||||
void* gwbuf_get_buffer_object_data(GWBUF* buf, bufobj_id_t id);
|
||||
|
||||
EXTERN_C_BLOCK_END
|
||||
|
||||
|
||||
|
@ -130,7 +130,6 @@ typedef struct {
|
||||
#define GWPROTOCOL_VERSION {1, 0, 0}
|
||||
|
||||
#define DCBFD_CLOSED -1
|
||||
#define DCBFD_CLONED -2
|
||||
|
||||
/**
|
||||
* The statitics gathered on a descriptor control block
|
||||
@ -332,4 +331,8 @@ bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs);
|
||||
*/
|
||||
#define DCBF_CLONE 0x0001 /*< DCB is a clone */
|
||||
#define DCBF_HUNG 0x0002 /*< Hangup has been dispatched */
|
||||
#define DCBF_REPLIED 0x0004 /*< DCB was written to */
|
||||
|
||||
#define DCB_IS_CLONE(d) ((d)->flags & DCBF_CLONE)
|
||||
#define DCB_REPLIED(d) ((d)->flags & DCBF_REPLIED)
|
||||
#endif /* _DCB_H */
|
||||
|
@ -42,6 +42,8 @@ extern GWBUF *modutil_replace_SQL(GWBUF *, char *);
|
||||
extern char *modutil_get_query(GWBUF* buf);
|
||||
extern int modutil_send_mysql_err_packet(DCB *, int, int, int, const char *, const char *);
|
||||
GWBUF* modutil_get_next_MySQL_packet(GWBUF** p_readbuf);
|
||||
int modutil_MySQL_query_len(GWBUF* buf, int* nbytes_missing);
|
||||
|
||||
|
||||
GWBUF *modutil_create_mysql_err_msg(
|
||||
int packet_number,
|
||||
|
@ -91,6 +91,7 @@ typedef struct server {
|
||||
long master_id; /**< Master server id of this node */
|
||||
int depth; /**< Replication level in the tree */
|
||||
long *slaves; /**< Slaves of this node */
|
||||
bool master_err_is_logged; /*< If node failed, this indicates whether it is logged */
|
||||
} SERVER;
|
||||
|
||||
/**
|
||||
@ -123,8 +124,11 @@ typedef struct server {
|
||||
* Is the server a master? The server must be both running and marked as master
|
||||
* in order for the macro to return true
|
||||
*/
|
||||
#define SERVER_IS_MASTER(server) \
|
||||
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == (SERVER_RUNNING|SERVER_MASTER))
|
||||
#define SERVER_IS_MASTER(server) SRV_MASTER_STATUS((server)->status)
|
||||
|
||||
#define SRV_MASTER_STATUS(status) ((status & \
|
||||
(SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \
|
||||
(SERVER_RUNNING|SERVER_MASTER))
|
||||
|
||||
/**
|
||||
* Is the server valid candidate for root master. The server must be running,
|
||||
|
@ -99,6 +99,11 @@ typedef struct {
|
||||
time_t last;
|
||||
} SERVICE_REFRESH_RATE;
|
||||
|
||||
typedef struct server_ref_t{
|
||||
struct server_ref_t *next;
|
||||
SERVER* server;
|
||||
}SERVER_REF;
|
||||
|
||||
/**
|
||||
* Defines a service within the gateway.
|
||||
*
|
||||
@ -119,7 +124,7 @@ typedef struct service {
|
||||
void *router_instance;
|
||||
/**< The router instance for this service */
|
||||
char *version_string;/** version string for this service listeners */
|
||||
struct server *databases; /**< The set of servers in the backend */
|
||||
SERVER_REF *dbref; /** server references */
|
||||
SERVICE_USER credentials; /**< The cedentials of the service user */
|
||||
SPINLOCK spin; /**< The service spinlock */
|
||||
SERVICE_STATS stats; /**< The service statistics */
|
||||
@ -130,6 +135,7 @@ typedef struct service {
|
||||
CONFIG_PARAMETER*
|
||||
svc_config_param; /*< list of config params and values */
|
||||
int svc_config_version; /*< Version number of configuration */
|
||||
bool svc_do_shutdown; /*< tells the service to exit loops etc. */
|
||||
SPINLOCK
|
||||
users_table_spin; /**< The spinlock for users data refresh */
|
||||
SERVICE_REFRESH_RATE
|
||||
@ -144,6 +150,8 @@ typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOS
|
||||
|
||||
#define SERVICE_STATE_ALLOC 1 /**< The service has been allocated */
|
||||
#define SERVICE_STATE_STARTED 2 /**< The service has been started */
|
||||
#define SERVICE_STATE_FAILED 3 /**< The service failed to start */
|
||||
#define SERVICE_STATE_STOPPED 4 /**< The service has been stopped */
|
||||
|
||||
extern SERVICE *service_alloc(const char *, const char *);
|
||||
extern int service_free(SERVICE *);
|
||||
@ -184,4 +192,5 @@ extern void dprintService(DCB *, SERVICE *);
|
||||
extern void dListServices(DCB *);
|
||||
extern void dListListeners(DCB *);
|
||||
char* service_get_name(SERVICE* svc);
|
||||
void service_shutdown();
|
||||
#endif
|
||||
|
@ -61,6 +61,7 @@ typedef enum {
|
||||
SESSION_STATE_STOPPING, /*< session and router are being closed */
|
||||
SESSION_STATE_LISTENER, /*< for listener session */
|
||||
SESSION_STATE_LISTENER_STOPPED, /*< for listener session */
|
||||
SESSION_STATE_TO_BE_FREED, /*< ready to be freed as soon as there are no references */
|
||||
SESSION_STATE_FREE /*< for all sessions */
|
||||
} session_state_t;
|
||||
|
||||
@ -124,6 +125,7 @@ typedef struct session {
|
||||
UPSTREAM tail; /*< The tail of the filter chain */
|
||||
struct session *next; /*< Linked list of all sessions */
|
||||
int refcount; /*< Reference count on the session */
|
||||
bool ses_is_child; /*< this is a child session */
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t ses_chk_tail;
|
||||
#endif
|
||||
|
@ -213,7 +213,7 @@ HINT_MODE mode = HM_EXECUTE;
|
||||
/*
|
||||
* 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
|
||||
* character of the comment if it is a -- or \/\* comment
|
||||
*
|
||||
* Move to the next character in the SQL.
|
||||
*/
|
||||
|
@ -58,6 +58,10 @@
|
||||
#include <service.h>
|
||||
#include <router.h>
|
||||
#include <dcb.h>
|
||||
#include <sys/time.h>
|
||||
#include <poll.h>
|
||||
#include <mysql_client_server_protocol.h>
|
||||
#include <housekeeper.h>
|
||||
|
||||
#define MYSQL_COM_QUIT 0x01
|
||||
#define MYSQL_COM_INITDB 0x02
|
||||
@ -69,6 +73,13 @@
|
||||
#define MYSQL_COM_STMT_CLOSE 0x19
|
||||
#define MYSQL_COM_STMT_RESET 0x1a
|
||||
|
||||
#define REPLY_TIMEOUT_SECOND 5
|
||||
#define REPLY_TIMEOUT_MILLISECOND 1
|
||||
#define PARENT 0
|
||||
#define CHILD 1
|
||||
|
||||
#define PTR_IS_RESULTSET(b) (b[0] == 0x01 && b[1] == 0x0 && b[2] == 0x0 && b[3] == 0x01)
|
||||
#define PTR_IS_EOF(b) (b[4] == 0xfe)
|
||||
|
||||
static unsigned char required_packets[] = {
|
||||
MYSQL_COM_QUIT,
|
||||
@ -104,19 +115,20 @@ static void *newSession(FILTER *instance, SESSION *session);
|
||||
static void closeSession(FILTER *instance, void *session);
|
||||
static void freeSession(FILTER *instance, void *session);
|
||||
static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream);
|
||||
static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream);
|
||||
static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue);
|
||||
static int clientReply(FILTER *instance, void *fsession, GWBUF *queue);
|
||||
static void diagnostic(FILTER *instance, void *fsession, DCB *dcb);
|
||||
|
||||
|
||||
static FILTER_OBJECT MyObject = {
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
setDownstream,
|
||||
NULL, // No Upstream requirement
|
||||
setUpstream,
|
||||
routeQuery,
|
||||
NULL, // No client reply
|
||||
clientReply,
|
||||
diagnostic,
|
||||
};
|
||||
|
||||
@ -144,15 +156,135 @@ typedef struct {
|
||||
*/
|
||||
typedef struct {
|
||||
DOWNSTREAM down; /* The downstream filter */
|
||||
UPSTREAM up; /* The upstream filter */
|
||||
|
||||
FILTER_DEF* dummy_filterdef;
|
||||
int active; /* filter is active? */
|
||||
bool waiting[2]; /* if the client is waiting for a reply */
|
||||
int eof[2];
|
||||
int replies[2]; /* Number of queries received */
|
||||
DCB *branch_dcb; /* Client DCB for "branch" service */
|
||||
SESSION *branch_session;/* The branch service session */
|
||||
int n_duped; /* Number of duplicated queries */
|
||||
int n_rejected; /* Number of rejected queries */
|
||||
int residual; /* Any outstanding SQL text */
|
||||
GWBUF* tee_replybuf; /* Buffer for reply */
|
||||
SPINLOCK tee_lock;
|
||||
} TEE_SESSION;
|
||||
|
||||
typedef struct orphan_session_tt
|
||||
{
|
||||
SESSION* session;
|
||||
struct orphan_session_tt* next;
|
||||
}orphan_session_t;
|
||||
|
||||
static orphan_session_t* allOrphans = NULL;
|
||||
|
||||
static SPINLOCK orphanLock;
|
||||
static int packet_is_required(GWBUF *queue);
|
||||
static int detect_loops(TEE_INSTANCE *instance, HASHTABLE* ht, SERVICE* session);
|
||||
|
||||
static int hkfn(
|
||||
void* key)
|
||||
{
|
||||
if(key == NULL){
|
||||
return 0;
|
||||
}
|
||||
unsigned int hash = 0,c = 0;
|
||||
char* ptr = (char*)key;
|
||||
while((c = *ptr++)){
|
||||
hash = c + (hash << 6) + (hash << 16) - hash;
|
||||
}
|
||||
return *(int *)key;
|
||||
}
|
||||
|
||||
static int hcfn(
|
||||
void* v1,
|
||||
void* v2)
|
||||
{
|
||||
char* i1 = (char*) v1;
|
||||
char* i2 = (char*) v2;
|
||||
|
||||
return strcmp(i1,i2);
|
||||
}
|
||||
|
||||
static void
|
||||
orphan_free(void* data)
|
||||
{
|
||||
spinlock_acquire(&orphanLock);
|
||||
orphan_session_t *ptr = allOrphans, *finished = NULL, *tmp = NULL;
|
||||
#ifdef SS_DEBUG
|
||||
int o_stopping = 0, o_ready = 0, o_freed = 0;
|
||||
#endif
|
||||
while(ptr)
|
||||
{
|
||||
if(ptr->session->state == SESSION_STATE_TO_BE_FREED)
|
||||
{
|
||||
if(ptr == allOrphans)
|
||||
{
|
||||
tmp = ptr;
|
||||
allOrphans = ptr->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = allOrphans;
|
||||
while(tmp && tmp->next != ptr)
|
||||
tmp = tmp->next;
|
||||
if(tmp)
|
||||
{
|
||||
tmp->next = ptr->next;
|
||||
tmp = ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef SS_DEBUG
|
||||
else if(ptr->session->state == SESSION_STATE_STOPPING)
|
||||
{
|
||||
o_stopping++;
|
||||
}
|
||||
else if(ptr->session->state == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
o_ready++;
|
||||
}
|
||||
#endif
|
||||
ptr = ptr->next;
|
||||
if(tmp)
|
||||
{
|
||||
tmp->next = finished;
|
||||
finished = tmp;
|
||||
tmp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&orphanLock);
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
if(o_stopping + o_ready > 0)
|
||||
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans in "
|
||||
"SESSION_STATE_STOPPING, %d orphans in "
|
||||
"SESSION_STATE_ROUTER_READY. ", o_stopping, o_ready);
|
||||
#endif
|
||||
|
||||
while(finished)
|
||||
{
|
||||
o_freed++;
|
||||
tmp = finished;
|
||||
finished = finished->next;
|
||||
|
||||
tmp->session->service->router->freeSession(
|
||||
tmp->session->service->router_instance,
|
||||
tmp->session->router_session);
|
||||
|
||||
tmp->session->state = SESSION_STATE_FREE;
|
||||
free(tmp->session);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans freed.", o_freed);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
@ -171,6 +303,8 @@ version()
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
spinlock_init(&orphanLock);
|
||||
hktask_add("tee orphan cleanup",orphan_free,NULL,15);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,7 +391,8 @@ int i;
|
||||
free(my_instance->source);
|
||||
free(my_instance);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (my_instance->match &&
|
||||
regcomp(&my_instance->re, my_instance->match, REG_ICASE))
|
||||
{
|
||||
@ -313,12 +448,25 @@ char *remote, *userName;
|
||||
my_session = NULL;
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
|
||||
HASHTABLE* ht = hashtable_alloc(100,hkfn,hcfn);
|
||||
bool is_loop = detect_loops(my_instance,ht,session->service);
|
||||
hashtable_free(ht);
|
||||
|
||||
if(is_loop)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
|
||||
"Error : %s: Recursive use of tee filter in service.",
|
||||
session->service->name)));
|
||||
my_session = NULL;
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
if ((my_session = calloc(1, sizeof(TEE_SESSION))) != NULL)
|
||||
{
|
||||
my_session->active = 1;
|
||||
my_session->residual = 0;
|
||||
|
||||
spinlock_init(&my_session->tee_lock);
|
||||
if (my_instance->source &&
|
||||
(remote = session_get_remote(session)) != NULL)
|
||||
{
|
||||
@ -326,7 +474,7 @@ char *remote, *userName;
|
||||
{
|
||||
my_session->active = 0;
|
||||
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Warning : Tee filter is not active.")));
|
||||
}
|
||||
@ -339,7 +487,7 @@ char *remote, *userName;
|
||||
{
|
||||
my_session->active = 0;
|
||||
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Warning : Tee filter is not active.")));
|
||||
}
|
||||
@ -348,33 +496,88 @@ char *remote, *userName;
|
||||
{
|
||||
DCB* dcb;
|
||||
SESSION* ses;
|
||||
|
||||
FILTER_DEF* dummy;
|
||||
UPSTREAM* dummy_upstream;
|
||||
|
||||
if ((dcb = dcb_clone(session->client)) == NULL)
|
||||
{
|
||||
freeSession(my_instance, (void *)my_session);
|
||||
freeSession(instance, (void *)my_session);
|
||||
my_session = NULL;
|
||||
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Creating client DCB for Tee "
|
||||
"filter failed. Terminating session.")));
|
||||
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
if((dummy = filter_alloc("tee_dummy","tee_dummy")) == NULL)
|
||||
{
|
||||
dcb_close(dcb);
|
||||
freeSession(instance, (void *)my_session);
|
||||
my_session = NULL;
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : tee: Allocating memory for "
|
||||
"dummy filter definition failed."
|
||||
" Terminating session.")));
|
||||
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ((ses = session_alloc(my_instance->service, dcb)) == NULL)
|
||||
{
|
||||
dcb_close(dcb);
|
||||
freeSession(my_instance, (void *)my_session);
|
||||
freeSession(instance, (void *)my_session);
|
||||
my_session = NULL;
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Creating client session for Tee "
|
||||
"filter failed. Terminating session.")));
|
||||
|
||||
goto retblock;
|
||||
}
|
||||
my_session->branch_session = ses;
|
||||
my_session->branch_dcb = dcb;
|
||||
|
||||
ss_dassert(ses->ses_is_child);
|
||||
|
||||
dummy->obj = GetModuleObject();
|
||||
dummy->filter = NULL;
|
||||
|
||||
|
||||
if((dummy_upstream = filterUpstream(
|
||||
dummy, my_session, &ses->tail)) == NULL)
|
||||
{
|
||||
spinlock_acquire(&ses->ses_lock);
|
||||
ses->state = SESSION_STATE_STOPPING;
|
||||
spinlock_release(&ses->ses_lock);
|
||||
|
||||
ses->service->router->closeSession(
|
||||
ses->service->router_instance,
|
||||
ses->router_session);
|
||||
|
||||
ses->client = NULL;
|
||||
dcb->session = NULL;
|
||||
session_free(ses);
|
||||
dcb_close(dcb);
|
||||
freeSession(instance, (void *) my_session);
|
||||
my_session = NULL;
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : tee: Allocating memory for"
|
||||
"dummy upstream failed."
|
||||
" Terminating session.")));
|
||||
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
ses->tail = *dummy_upstream;
|
||||
my_session->branch_session = ses;
|
||||
my_session->branch_dcb = dcb;
|
||||
my_session->dummy_filterdef = dummy;
|
||||
free(dummy_upstream);
|
||||
}
|
||||
}
|
||||
retblock:
|
||||
@ -421,6 +624,7 @@ SESSION *bsession;
|
||||
* a side effect of closing the client DCB of the
|
||||
* session.
|
||||
*/
|
||||
|
||||
my_session->active = 0;
|
||||
}
|
||||
}
|
||||
@ -435,11 +639,57 @@ static void
|
||||
freeSession(FILTER *instance, void *session)
|
||||
{
|
||||
TEE_SESSION *my_session = (TEE_SESSION *)session;
|
||||
SESSION* ses = my_session->branch_session;
|
||||
|
||||
if (ses != NULL)
|
||||
{
|
||||
if (ses->state == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
session_free(ses);
|
||||
}
|
||||
|
||||
if (ses->state == SESSION_STATE_TO_BE_FREED)
|
||||
{
|
||||
/** Free branch router session */
|
||||
ses->service->router->freeSession(
|
||||
ses->service->router_instance,
|
||||
ses->router_session);
|
||||
/** Free memory of branch client session */
|
||||
ses->state = SESSION_STATE_FREE;
|
||||
free(ses);
|
||||
/** This indicates that branch session is not available anymore */
|
||||
my_session->branch_session = NULL;
|
||||
}
|
||||
else if(ses->state == SESSION_STATE_STOPPING)
|
||||
{
|
||||
orphan_session_t* orphan;
|
||||
if((orphan = malloc(sizeof(orphan_session_t))) == NULL)
|
||||
{
|
||||
skygw_log_write(LOGFILE_ERROR,"Error : Failed to "
|
||||
"allocate memory for orphan session struct, "
|
||||
"child session might leak memory.");
|
||||
}else{
|
||||
orphan->session = ses;
|
||||
spinlock_acquire(&orphanLock);
|
||||
orphan->next = allOrphans;
|
||||
allOrphans = orphan;
|
||||
spinlock_release(&orphanLock);
|
||||
}
|
||||
if(ses->refcount == 0)
|
||||
{
|
||||
ss_dassert(ses->refcount == 0 && ses->client == NULL);
|
||||
ses->state = SESSION_STATE_TO_BE_FREED;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (my_session->dummy_filterdef)
|
||||
{
|
||||
filter_free(my_session->dummy_filterdef);
|
||||
}
|
||||
free(session);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the downstream filter or router to which queries will be
|
||||
* passed from this filter.
|
||||
@ -451,9 +701,23 @@ TEE_SESSION *my_session = (TEE_SESSION *)session;
|
||||
static void
|
||||
setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
|
||||
{
|
||||
TEE_SESSION *my_session = (TEE_SESSION *)session;
|
||||
TEE_SESSION *my_session = (TEE_SESSION *) session;
|
||||
my_session->down = *downstream;
|
||||
}
|
||||
|
||||
my_session->down = *downstream;
|
||||
/**
|
||||
* Set the downstream filter or router to which queries will be
|
||||
* passed from this filter.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param downstream The downstream filter or router.
|
||||
*/
|
||||
static void
|
||||
setUpstream(FILTER *instance, void *session, UPSTREAM *upstream)
|
||||
{
|
||||
TEE_SESSION *my_session = (TEE_SESSION *) session;
|
||||
my_session->up = *upstream;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -483,51 +747,187 @@ char *ptr;
|
||||
int length, rval, residual = 0;
|
||||
GWBUF *clone = NULL;
|
||||
|
||||
if (my_session->residual)
|
||||
if (my_session->branch_session &&
|
||||
my_session->branch_session->state == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
clone = gwbuf_clone(queue);
|
||||
if (my_session->residual < GWBUF_LENGTH(clone))
|
||||
GWBUF_RTRIM(clone, GWBUF_LENGTH(clone) - residual);
|
||||
my_session->residual -= GWBUF_LENGTH(clone);
|
||||
if (my_session->residual < 0)
|
||||
my_session->residual = 0;
|
||||
}
|
||||
else if ( my_session->active && (ptr = modutil_get_SQL(queue)) != NULL)
|
||||
{
|
||||
if ((my_instance->match == NULL ||
|
||||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
||||
(my_instance->nomatch == NULL ||
|
||||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
||||
if (my_session->residual)
|
||||
{
|
||||
char *dummy;
|
||||
|
||||
modutil_MySQL_Query(queue, &dummy, &length, &residual);
|
||||
clone = gwbuf_clone(queue);
|
||||
my_session->residual = residual;
|
||||
|
||||
clone = gwbuf_clone_all(queue);
|
||||
|
||||
if (my_session->residual < GWBUF_LENGTH(clone))
|
||||
{
|
||||
GWBUF_RTRIM(clone, GWBUF_LENGTH(clone) - residual);
|
||||
}
|
||||
my_session->residual -= GWBUF_LENGTH(clone);
|
||||
|
||||
if (my_session->residual < 0)
|
||||
{
|
||||
my_session->residual = 0;
|
||||
}
|
||||
}
|
||||
else if (my_session->active && (ptr = modutil_get_SQL(queue)) != NULL)
|
||||
{
|
||||
if ((my_instance->match == NULL ||
|
||||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
||||
(my_instance->nomatch == NULL ||
|
||||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
||||
{
|
||||
length = modutil_MySQL_query_len(queue, &residual);
|
||||
clone = gwbuf_clone_all(queue);
|
||||
my_session->residual = residual;
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
else if (packet_is_required(queue))
|
||||
{
|
||||
clone = gwbuf_clone_all(queue);
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
else if (packet_is_required(queue))
|
||||
{
|
||||
clone = gwbuf_clone(queue);
|
||||
}
|
||||
|
||||
/* Pass the query downstream */
|
||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
|
||||
ss_dassert(my_session->tee_replybuf == NULL);
|
||||
|
||||
memset(my_session->replies,0,2*sizeof(int));
|
||||
memset(my_session->eof,0,2*sizeof(int));
|
||||
memset(my_session->waiting,0,2*sizeof(bool));
|
||||
|
||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
||||
my_session->down.session,
|
||||
queue);
|
||||
if (clone)
|
||||
{
|
||||
my_session->n_duped++;
|
||||
SESSION_ROUTE_QUERY(my_session->branch_session, clone);
|
||||
|
||||
if (my_session->branch_session->state == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
SESSION_ROUTE_QUERY(my_session->branch_session, clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Close tee session */
|
||||
my_session->active = 0;
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Closed tee filter session.")));
|
||||
gwbuf_free(clone);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (my_session->active)
|
||||
{
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Closed tee filter session.")));
|
||||
my_session->active = 0;
|
||||
}
|
||||
my_session->n_rejected++;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the GWBUF for EOF packets. If two packets for this session have been found
|
||||
* from either the parent or the child branch, mark the response set from that branch as over.
|
||||
* @param session The Tee filter session
|
||||
* @param branch Parent or child branch
|
||||
* @param reply Buffer to scan
|
||||
*/
|
||||
void
|
||||
scan_resultset(TEE_SESSION *session, int branch, GWBUF *reply)
|
||||
{
|
||||
unsigned char* ptr = (unsigned char*) reply->start;
|
||||
unsigned char* end = (unsigned char*) reply->end;
|
||||
int pktlen = 0;
|
||||
|
||||
while(ptr < end)
|
||||
{
|
||||
pktlen = gw_mysql_get_byte3(ptr) + 4;
|
||||
if(PTR_IS_EOF(ptr))
|
||||
{
|
||||
session->eof[branch]++;
|
||||
|
||||
if(session->eof[branch] == 2)
|
||||
{
|
||||
session->waiting[branch] = false;
|
||||
session->eof[branch] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ptr += pktlen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The clientReply entry point. This is passed the response buffer
|
||||
* to which the filter should be applied. Once processed the
|
||||
* query is passed to the upstream component
|
||||
* (filter or router) in the filter chain.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
* @param session The filter session
|
||||
* @param reply The response data
|
||||
*/
|
||||
static int
|
||||
clientReply (FILTER* instance, void *session, GWBUF *reply)
|
||||
{
|
||||
int rc, branch;
|
||||
TEE_SESSION *my_session = (TEE_SESSION *) session;
|
||||
|
||||
spinlock_acquire(&my_session->tee_lock);
|
||||
|
||||
ss_dassert(my_session->active);
|
||||
|
||||
branch = instance == NULL ? CHILD : PARENT;
|
||||
unsigned char *ptr = (unsigned char*)reply->start;
|
||||
|
||||
if(my_session->replies[branch] == 0)
|
||||
{
|
||||
if(PTR_IS_RESULTSET(ptr))
|
||||
{
|
||||
my_session->waiting[branch] = true;
|
||||
my_session->eof[branch] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(my_session->waiting[branch])
|
||||
{
|
||||
scan_resultset(my_session,branch,reply);
|
||||
}
|
||||
|
||||
if(branch == PARENT)
|
||||
{
|
||||
ss_dassert(my_session->tee_replybuf == NULL)
|
||||
my_session->tee_replybuf = reply;
|
||||
}
|
||||
else
|
||||
{
|
||||
gwbuf_free(reply);
|
||||
}
|
||||
|
||||
my_session->replies[branch]++;
|
||||
|
||||
if(my_session->tee_replybuf != NULL &&
|
||||
(my_session->branch_session == NULL ||
|
||||
my_session->waiting[PARENT] ||
|
||||
(!my_session->waiting[CHILD] && !my_session->waiting[PARENT])))
|
||||
{
|
||||
rc = my_session->up.clientReply (
|
||||
my_session->up.instance,
|
||||
my_session->up.session,
|
||||
my_session->tee_replybuf);
|
||||
my_session->tee_replybuf = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
spinlock_release(&my_session->tee_lock);
|
||||
return rc;
|
||||
}
|
||||
/**
|
||||
* Diagnostics routine
|
||||
*
|
||||
@ -589,3 +989,52 @@ int i;
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects possible loops in the query cloning chain.
|
||||
*/
|
||||
int detect_loops(TEE_INSTANCE *instance,HASHTABLE* ht, SERVICE* service)
|
||||
{
|
||||
SERVICE* svc = service;
|
||||
int i;
|
||||
|
||||
if(ht == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(hashtable_add(ht,(void*)service->name,(void*)true) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for(i = 0;i<svc->n_filters;i++)
|
||||
{
|
||||
if(strcmp(svc->filters[i]->module,"tee") == 0)
|
||||
{
|
||||
/*
|
||||
* Found a Tee filter, recurse down its path
|
||||
* if the service name isn't already in the hashtable.
|
||||
*/
|
||||
|
||||
TEE_INSTANCE* ninst = (TEE_INSTANCE*)svc->filters[i]->filter;
|
||||
if(ninst == NULL)
|
||||
{
|
||||
/**
|
||||
* This tee instance hasn't been initialized yet and full
|
||||
* resolution of recursion cannot be done now.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
SERVICE* tgt = ninst->service;
|
||||
|
||||
if(detect_loops((TEE_INSTANCE*)svc->filters[i]->filter,ht,tgt))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -11,16 +11,26 @@ add_executable(harness harness_util.c harness_common.c ${CORE})
|
||||
target_link_libraries(harness_ui fullcore log_manager utils)
|
||||
target_link_libraries(harness fullcore)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${ERRMSG} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter.cnf)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testdriver.sh ${CMAKE_CURRENT_BINARY_DIR}/testdriver.sh @ONLY)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.cnf ${CMAKE_CURRENT_BINARY_DIR}/hintfilter/hint_testing.cnf)
|
||||
add_test(TestHintfilter testdriver.sh hintfilter/hint_testing.cnf hintfilter/hint_testing.input hintfilter/hint_testing.output hintfilter/hint_testing.expected)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.cnf ${CMAKE_CURRENT_BINARY_DIR}/regexfilter/regextest.cnf)
|
||||
add_test(TestRegexfilter testdriver.sh regexfilter/regextest.cnf regexfilter/regextest.input regexfilter/regextest.output regexfilter/regextest.expected)
|
||||
|
||||
add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hintfilter/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hintfilter/hint_testing.expected ")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest.cnf)
|
||||
add_test(TestFwfilter1 testdriver.sh fwfilter/fwtest.cnf fwfilter/fwtest.input fwfilter/fwtest.output fwfilter/fwtest.expected)
|
||||
add_test(TestFwfilter2 testdriver.sh fwfilter/fwtest.cnf fwfilter/fwtest2.input fwfilter/fwtest2.output fwfilter/fwtest2.expected)
|
||||
|
||||
add_test(TestRegexfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.input -o ${CMAKE_CURRENT_BINARY_DIR}/regexfilter/regextest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/regexfilter/regextest.expected ")
|
||||
add_test(TestTeeRecursion ${CMAKE_CURRENT_SOURCE_DIR}/tee_recursion.sh
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${TEST_USER}
|
||||
${TEST_PASSWORD}
|
||||
${TEST_HOST}
|
||||
${TEST_PORT})
|
||||
|
||||
add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest.expected ")
|
||||
|
||||
add_test(TestFwfilter2 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest2.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest2.expected ")
|
||||
|
||||
add_test(TestFwfilter3 /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwtest3.output -c ${CMAKE_CURRENT_BINARY_DIR}/fwfilter/fwfilter.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwfilter/fwtest3.expected ")
|
||||
set_tests_properties(TestHintfilter TestRegexfilter TestFwfilter1 TestFwfilter2 TestTeeRecursion
|
||||
PROPERTIES
|
||||
ENVIRONMENT MAXSCALE_HOME=${CMAKE_BINARY_DIR}/)
|
||||
|
0
server/modules/filter/test/fwfilter/fwtest.cnf.in
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest.cnf.in
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest.input
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest.input
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest2.expected
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest2.expected
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest2.input
Normal file → Executable file
0
server/modules/filter/test/fwfilter/fwtest2.input
Normal file → Executable file
@ -10,7 +10,7 @@ int dcbfun(struct dcb* dcb, GWBUF * buffer)
|
||||
int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
|
||||
|
||||
|
||||
int i = 0;
|
||||
int i = 0,rval = 0;
|
||||
MYSQL_session* mysqlsess;
|
||||
DCB* dcb;
|
||||
char cwd[1024];
|
||||
@ -60,7 +60,7 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
|
||||
skygw_logmanager_init( 3, optstr);
|
||||
free(optstr);
|
||||
|
||||
process_opts(argc,argv);
|
||||
rval = process_opts(argc,argv);
|
||||
|
||||
if(!(instance.thrpool = malloc(instance.thrcount * sizeof(pthread_t)))){
|
||||
printf("Error: Out of memory\n");
|
||||
@ -72,10 +72,10 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
|
||||
pthread_mutex_lock(&instance.work_mtx);
|
||||
size_t thr_num = 1;
|
||||
for(i = 0;i<instance.thrcount;i++){
|
||||
pthread_create(&instance.thrpool[i],NULL,(void*)work_buffer,(void*)thr_num++);
|
||||
rval |= pthread_create(&instance.thrpool[i],NULL,(void*)work_buffer,(void*)thr_num++);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return rval;
|
||||
}
|
||||
|
||||
void free_filters()
|
||||
@ -543,10 +543,14 @@ int load_config( char* fname)
|
||||
{
|
||||
CONFIG* iter;
|
||||
CONFIG_ITEM* item;
|
||||
int config_ok = 1;
|
||||
int config_ok = 1,inirval;
|
||||
free_filters();
|
||||
if(ini_parse(fname,handler,instance.conf) < 0){
|
||||
if((inirval = ini_parse(fname,handler,instance.conf)) < 0){
|
||||
printf("Error parsing configuration file!\n");
|
||||
if(inirval == -1)
|
||||
printf("Inih file open error.\n");
|
||||
else if(inirval == -2)
|
||||
printf("inih memory error.\n");
|
||||
skygw_log_write(LOGFILE_ERROR,"Error parsing configuration file!\n");
|
||||
config_ok = 0;
|
||||
goto cleanup;
|
||||
@ -991,7 +995,7 @@ GWBUF* gen_packet(PACKET pkt)
|
||||
int process_opts(int argc, char** argv)
|
||||
{
|
||||
int fd, buffsize = 1024;
|
||||
int rd,rdsz, rval = 0;
|
||||
int rd,rdsz, rval = 0,error;
|
||||
size_t fsize;
|
||||
char *buff = calloc(buffsize,sizeof(char)), *tok = NULL;
|
||||
|
||||
@ -1071,6 +1075,7 @@ int process_opts(int argc, char** argv)
|
||||
free(conf_name);
|
||||
}
|
||||
conf_name = strdup(optarg);
|
||||
printf("Configuration: %s\n",optarg);
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
@ -1079,12 +1084,12 @@ int process_opts(int argc, char** argv)
|
||||
|
||||
case 's':
|
||||
instance.session_count = atoi(optarg);
|
||||
printf("Sessions: %i ",instance.session_count);
|
||||
printf("Sessions: %i\n",instance.session_count);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
instance.thrcount = atoi(optarg);
|
||||
printf("Threads: %i ",instance.thrcount);
|
||||
printf("Threads: %i\n",instance.thrcount);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
@ -1121,7 +1126,7 @@ int process_opts(int argc, char** argv)
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if(conf_name && load_config(conf_name)){
|
||||
if(conf_name && (error = load_config(conf_name))){
|
||||
load_query();
|
||||
}else{
|
||||
instance.running = 0;
|
||||
@ -1129,6 +1134,11 @@ int process_opts(int argc, char** argv)
|
||||
free(conf_name);
|
||||
close(fd);
|
||||
|
||||
if(!error)
|
||||
{
|
||||
rval = 1;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
86
server/modules/filter/test/tee_recursion.sh
Executable file
86
server/modules/filter/test/tee_recursion.sh
Executable file
@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
function execute_test()
|
||||
{
|
||||
|
||||
RVAL=$(mysql --connect-timeout=5 -u $USER -p$PWD -h $HOST -P $PORT -e "select 1;"|grep -i error)
|
||||
|
||||
if [[ ! -e $MAXPID ]]
|
||||
then
|
||||
echo "Test failed: $MAXPID was not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$RVAL" != "" ]]
|
||||
then
|
||||
echo "Test failed: Query to backend didn't return an error."
|
||||
return 1
|
||||
fi
|
||||
|
||||
LAST_LOG=$(ls $BINDIR/log -1|grep err|sort|uniq|tail -n 1)
|
||||
TEST_RESULT=$(cat $BINDIR/log/$LAST_LOG | grep -i recursive)
|
||||
if [[ "$TEST_RESULT" != "" ]]
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
echo "Test failed: Log file didn't mention tee recursion."
|
||||
return 1
|
||||
}
|
||||
|
||||
function reload_conf()
|
||||
{
|
||||
$BINDIR/bin/maxadmin --user=admin --password=skysql reload config
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "Test failed: maxadmin returned a non-zero value."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ $# -lt 6 ]]
|
||||
then
|
||||
echo "usage: $0 <build dir> <source dir>"
|
||||
exit 1
|
||||
fi
|
||||
BINDIR=$1
|
||||
SRCDIR=$2
|
||||
USER=$3
|
||||
PWD=$4
|
||||
HOST=$5
|
||||
PORT=$6
|
||||
CONF=$BINDIR/etc/MaxScale.cnf
|
||||
OLDCONF=$BINDIR/etc/MaxScale.cnf.old
|
||||
MAXPID=$BINDIR/log/$(ls -1 $BINDIR/log|grep maxscale)
|
||||
TEST1=$SRCDIR/server/modules/filter/test/tee_recursion1.cnf
|
||||
TEST2=$SRCDIR/server/modules/filter/test/tee_recursion2.cnf
|
||||
|
||||
$BINDIR/bin/maxadmin --user=admin --password=skysql flush logs
|
||||
|
||||
mv $CONF $OLDCONF
|
||||
cp $TEST1 $CONF
|
||||
reload_conf
|
||||
execute_test
|
||||
T1RVAL=$?
|
||||
mv $CONF $CONF.test1
|
||||
cp $TEST2 $CONF
|
||||
reload_conf
|
||||
execute_test
|
||||
T2RVAL=$?
|
||||
mv $CONF $CONF.test2
|
||||
mv $OLDCONF $CONF
|
||||
reload_conf
|
||||
|
||||
if [[ $T1RVAL -ne 0 ]]
|
||||
then
|
||||
echo "Test 1 failed."
|
||||
exit 1
|
||||
elif [[ $T2RVAL -ne 0 ]]
|
||||
then
|
||||
echo "Test 2 failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Test successful: log mentions recursive tee usage."
|
||||
fi
|
||||
|
||||
exit 0
|
114
server/modules/filter/test/tee_recursion1.cnf
Normal file
114
server/modules/filter/test/tee_recursion1.cnf
Normal file
@ -0,0 +1,114 @@
|
||||
[maxscale]
|
||||
threads=4
|
||||
|
||||
[MySQL Monitor]
|
||||
type=monitor
|
||||
module=mysqlmon
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
monitor_interval=10000
|
||||
|
||||
[RW Split Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=recurse1
|
||||
|
||||
[RW Split Hint Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=recurse2
|
||||
|
||||
|
||||
[Read Connection Router]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=master
|
||||
servers=server1
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=recurse3
|
||||
|
||||
[recurse3]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Router
|
||||
|
||||
[recurse2]
|
||||
type=filter
|
||||
module=tee
|
||||
service=Read Connection Router
|
||||
|
||||
[recurse1]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Hint Router
|
||||
|
||||
|
||||
[Debug Interface]
|
||||
type=service
|
||||
router=debugcli
|
||||
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[Read Connection Listener]
|
||||
type=listener
|
||||
service=Read Connection Router
|
||||
protocol=MySQLClient
|
||||
port=4008
|
||||
|
||||
[RW Split Listener]
|
||||
type=listener
|
||||
service=RW Split Router
|
||||
protocol=MySQLClient
|
||||
port=4006
|
||||
|
||||
[RW Split Hint Listener]
|
||||
type=listener
|
||||
service=RW Split Hint Router
|
||||
protocol=MySQLClient
|
||||
port=4009
|
||||
|
||||
[Debug Listener]
|
||||
type=listener
|
||||
service=Debug Interface
|
||||
protocol=telnetd
|
||||
port=4442
|
||||
|
||||
[CLI Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
port=6603
|
||||
|
||||
[server1]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3000
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server2]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3001
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server3]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3002
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server4]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3003
|
||||
protocol=MySQLBackend
|
112
server/modules/filter/test/tee_recursion2.cnf
Normal file
112
server/modules/filter/test/tee_recursion2.cnf
Normal file
@ -0,0 +1,112 @@
|
||||
[maxscale]
|
||||
threads=4
|
||||
|
||||
[MySQL Monitor]
|
||||
type=monitor
|
||||
module=mysqlmon
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
monitor_interval=10000
|
||||
|
||||
[RW Split Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=recurse1|recurse2
|
||||
|
||||
[RW Split Hint Router]
|
||||
type=service
|
||||
router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
|
||||
[Read Connection Router]
|
||||
type=service
|
||||
router=readconnroute
|
||||
router_options=master
|
||||
servers=server1
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
filters=recurse3
|
||||
|
||||
[recurse3]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Router
|
||||
|
||||
[recurse2]
|
||||
type=filter
|
||||
module=tee
|
||||
service=Read Connection Router
|
||||
|
||||
[recurse1]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Hint Router
|
||||
|
||||
|
||||
[Debug Interface]
|
||||
type=service
|
||||
router=debugcli
|
||||
|
||||
[CLI]
|
||||
type=service
|
||||
router=cli
|
||||
|
||||
[Read Connection Listener]
|
||||
type=listener
|
||||
service=Read Connection Router
|
||||
protocol=MySQLClient
|
||||
port=4008
|
||||
|
||||
[RW Split Listener]
|
||||
type=listener
|
||||
service=RW Split Router
|
||||
protocol=MySQLClient
|
||||
port=4006
|
||||
|
||||
[RW Split Hint Listener]
|
||||
type=listener
|
||||
service=RW Split Hint Router
|
||||
protocol=MySQLClient
|
||||
port=4009
|
||||
|
||||
[Debug Listener]
|
||||
type=listener
|
||||
service=Debug Interface
|
||||
protocol=telnetd
|
||||
port=4442
|
||||
|
||||
[CLI Listener]
|
||||
type=listener
|
||||
service=CLI
|
||||
protocol=maxscaled
|
||||
port=6603
|
||||
|
||||
[server1]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3000
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server2]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3001
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server3]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3002
|
||||
protocol=MySQLBackend
|
||||
|
||||
[server4]
|
||||
type=server
|
||||
address=127.0.0.1
|
||||
port=3003
|
||||
protocol=MySQLBackend
|
11
server/modules/filter/test/testdriver.sh
Executable file
11
server/modules/filter/test/testdriver.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#! /bin/bash
|
||||
if [[ $# -lt 4 ]]
|
||||
then
|
||||
echo "Usage: $0 <config file> <input> <output> <expected>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TESTDIR=@CMAKE_CURRENT_BINARY_DIR@
|
||||
SRCDIR=@CMAKE_CURRENT_SOURCE_DIR@
|
||||
$TESTDIR/harness -i $SRCDIR/$2 -o $TESTDIR/$3 -c $TESTDIR/$1 -t 1 -s 1 -e $SRCDIR/$4
|
||||
exit $?
|
@ -161,6 +161,7 @@ typedef struct router_slave {
|
||||
int binlog_pos; /*< Binlog position for this slave */
|
||||
char binlogfile[BINLOG_FNAMELEN+1];
|
||||
/*< Current binlog file for this slave */
|
||||
char *uuid; /*< Slave UUID */
|
||||
BLFILE *file; /*< Currently open binlog file */
|
||||
int serverid; /*< Server-id of the slave */
|
||||
char *hostname; /*< Hostname of the slave, if known */
|
||||
@ -227,6 +228,8 @@ typedef struct {
|
||||
GWBUF *utf8; /*< Set NAMES utf8 */
|
||||
GWBUF *select1; /*< select 1 */
|
||||
GWBUF *selectver; /*< select version() */
|
||||
GWBUF *selectvercom; /*< select @@version_comment */
|
||||
GWBUF *selecthostname;/*< select @@hostname */
|
||||
uint8_t *fde_event; /*< Format Description Event */
|
||||
int fde_len; /*< Length of fde_event */
|
||||
} MASTER_RESPONSES;
|
||||
@ -300,16 +303,19 @@ typedef struct router_instance {
|
||||
#define BLRM_UTF8 0x000C
|
||||
#define BLRM_SELECT1 0x000D
|
||||
#define BLRM_SELECTVER 0x000E
|
||||
#define BLRM_REGISTER 0x000F
|
||||
#define BLRM_BINLOGDUMP 0x0010
|
||||
#define BLRM_SELECTVERCOM 0x000F
|
||||
#define BLRM_SELECTHOSTNAME 0x0010
|
||||
#define BLRM_REGISTER 0x0011
|
||||
#define BLRM_BINLOGDUMP 0x0012
|
||||
|
||||
#define BLRM_MAXSTATE 0x0010
|
||||
#define BLRM_MAXSTATE 0x0012
|
||||
|
||||
static char *blrm_states[] = { "Unconnected", "Connecting", "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" };
|
||||
"select version()", "select @@version_comment", "select @@hostname",
|
||||
"Register slave", "Binlog Dump" };
|
||||
|
||||
#define BLRS_CREATED 0x0000
|
||||
#define BLRS_UNREGISTERED 0x0001
|
||||
@ -338,6 +344,8 @@ static char *blrs_states[] = { "Created", "Unregistered", "Registered",
|
||||
*/
|
||||
#define COM_QUIT 0x01
|
||||
#define COM_QUERY 0x03
|
||||
#define COM_STATISTICS 0x09
|
||||
#define COM_PING 0x0e
|
||||
#define COM_REGISTER_SLAVE 0x15
|
||||
#define COM_BINLOG_DUMP 0x12
|
||||
|
||||
@ -429,9 +437,9 @@ extern void blr_slave_rotate(ROUTER_SLAVE *slave, uint8_t *ptr);
|
||||
extern int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large);
|
||||
extern void blr_init_cache(ROUTER_INSTANCE *);
|
||||
|
||||
extern void blr_file_init(ROUTER_INSTANCE *);
|
||||
extern void blr_write_binlog_record(ROUTER_INSTANCE *, REP_HEADER *,uint8_t *);
|
||||
extern void blr_file_rotate(ROUTER_INSTANCE *, char *, uint64_t);
|
||||
extern int blr_file_init(ROUTER_INSTANCE *);
|
||||
extern int blr_write_binlog_record(ROUTER_INSTANCE *, REP_HEADER *,uint8_t *);
|
||||
extern int blr_file_rotate(ROUTER_INSTANCE *, char *, uint64_t);
|
||||
extern void blr_file_flush(ROUTER_INSTANCE *);
|
||||
extern BLFILE *blr_open_binlog(ROUTER_INSTANCE *, char *);
|
||||
extern GWBUF *blr_read_binlog(ROUTER_INSTANCE *, BLFILE *, unsigned int, REP_HEADER *);
|
||||
|
@ -111,9 +111,15 @@ typedef enum {
|
||||
*
|
||||
*/
|
||||
typedef struct mysql_session {
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t myses_chk_top;
|
||||
#endif
|
||||
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(passowrd) */
|
||||
char user[MYSQL_USER_MAXLEN+1]; /*< username */
|
||||
char db[MYSQL_DATABASE_MAXLEN+1]; /*< database */
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t myses_chk_tail;
|
||||
#endif
|
||||
} MYSQL_session;
|
||||
|
||||
|
||||
@ -303,11 +309,9 @@ typedef struct {
|
||||
|
||||
#endif /** _MYSQL_PROTOCOL_H */
|
||||
|
||||
void gw_mysql_close(MySQLProtocol **ptr);
|
||||
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd);
|
||||
void mysql_protocol_done (DCB* dcb);
|
||||
MySQLProtocol *gw_mysql_init(MySQLProtocol *data);
|
||||
void gw_mysql_close(MySQLProtocol **ptr);
|
||||
int gw_receive_backend_auth(MySQLProtocol *protocol);
|
||||
int gw_decode_mysql_server_handshake(MySQLProtocol *protocol, uint8_t *payload);
|
||||
int gw_read_backend_handshake(MySQLProtocol *protocol);
|
||||
|
@ -323,8 +323,5 @@ typedef struct router_instance {
|
||||
#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \
|
||||
(SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED));
|
||||
|
||||
#define RSES_SESSION(r) (r->rses_backend_ref->bref_dcb->session)
|
||||
|
||||
#define RSES_CLIENT_DCB(r) (RSES_SESSION(r)->client)
|
||||
|
||||
|
||||
#endif /*< _RWSPLITROUTER_H */
|
||||
|
@ -671,7 +671,27 @@ int log_no_master = 1;
|
||||
|
||||
if (mon_status_changed(ptr))
|
||||
{
|
||||
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
|
||||
if (SRV_MASTER_STATUS(ptr->mon_prev_status))
|
||||
{
|
||||
/** Master failed, can't recover */
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"Server %s:%d lost the master status.",
|
||||
ptr->server->name,
|
||||
ptr->server->port)));
|
||||
}
|
||||
/**
|
||||
* Here we say: If the server's state changed
|
||||
* so that it isn't running or some other way
|
||||
* lost cluster membership, call call-back function
|
||||
* of every DCB for which such callback was
|
||||
* registered for this kind of issue (DCB_REASON_...)
|
||||
*/
|
||||
if (!(SERVER_IS_RUNNING(ptr->server)) ||
|
||||
!(SERVER_IS_IN_CLUSTER(ptr->server)))
|
||||
{
|
||||
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
|
||||
}
|
||||
}
|
||||
|
||||
if (mon_status_changed(ptr))
|
||||
@ -734,7 +754,13 @@ int log_no_master = 1;
|
||||
{
|
||||
if (! SERVER_IN_MAINT(ptr->server)) {
|
||||
/* 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)) {
|
||||
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_statu
|
||||
* Set the STALE bit for this server in server struct
|
||||
@ -744,55 +770,71 @@ int log_no_master = 1;
|
||||
/* log it once */
|
||||
if (mon_status_changed(ptr)) {
|
||||
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)));
|
||||
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)));
|
||||
}
|
||||
} else {
|
||||
ptr->server->status = ptr->pending_status;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = ptr->next;
|
||||
}
|
||||
|
||||
/* log master detection failure od first master becomes available after failure */
|
||||
if (root_master && mon_status_changed(root_master) && !(root_master->server->status & SERVER_STALE_STATUS)) {
|
||||
if (root_master &&
|
||||
mon_status_changed(root_master) &&
|
||||
!(root_master->server->status & SERVER_STALE_STATUS))
|
||||
{
|
||||
if (root_master->pending_status & (SERVER_MASTER)) {
|
||||
if (!(root_master->mon_prev_status & SERVER_STALE_STATUS) && !(root_master->server->status & SERVER_MAINT)) {
|
||||
if (!(root_master->mon_prev_status & SERVER_STALE_STATUS) &&
|
||||
!(root_master->server->status & SERVER_MAINT))
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"Info: A Master Server is now available: %s:%i",
|
||||
"Info : A Master Server is now available: %s:%i",
|
||||
root_master->server->name,
|
||||
root_master->server->port)));
|
||||
}
|
||||
} else {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: No Master can be determined. Last known was %s:%i",
|
||||
"Error : No Master can be determined. Last known was %s:%i",
|
||||
root_master->server->name,
|
||||
root_master->server->port)));
|
||||
}
|
||||
log_no_master = 1;
|
||||
} else {
|
||||
if (!root_master && log_no_master) {
|
||||
if (!root_master && log_no_master)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: No Master can be determined")));
|
||||
"Error : No Master can be determined")));
|
||||
log_no_master = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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))) {
|
||||
if (replication_heartbeat &&
|
||||
root_master &&
|
||||
(SERVER_IS_MASTER(root_master->server) ||
|
||||
SERVER_IS_RELAY_SERVER(root_master->server)))
|
||||
{
|
||||
set_master_heartbeat(handle, root_master);
|
||||
ptr = handle->databases;
|
||||
|
||||
while (ptr) {
|
||||
if( (! SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server))
|
||||
{
|
||||
if (ptr->server->node_id != root_master->server->node_id && (SERVER_IS_SLAVE(ptr->server) || SERVER_IS_RELAY_SERVER(ptr->server))) {
|
||||
if (ptr->server->node_id != root_master->server->node_id &&
|
||||
(SERVER_IS_SLAVE(ptr->server) ||
|
||||
SERVER_IS_RELAY_SERVER(ptr->server)))
|
||||
{
|
||||
set_slave_heartbeat(handle, ptr);
|
||||
}
|
||||
}
|
||||
|
@ -510,6 +510,16 @@ static int gw_read_backend_event(DCB *dcb) {
|
||||
if (nbytes_read < 5) /*< read at least command type */
|
||||
{
|
||||
rc = 0;
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"%p [gw_read_backend_event] Read %d bytes "
|
||||
"from DCB %p, fd %d, session %s. "
|
||||
"Returning to poll wait.\n",
|
||||
pthread_self(),
|
||||
nbytes_read,
|
||||
dcb,
|
||||
dcb->fd,
|
||||
dcb->session)));
|
||||
goto return_rc;
|
||||
}
|
||||
/** There is at least length and command type. */
|
||||
@ -699,16 +709,6 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
|
||||
switch (backend_protocol->protocol_auth_state) {
|
||||
case MYSQL_HANDSHAKE_FAILED:
|
||||
case MYSQL_AUTH_FAILED:
|
||||
{
|
||||
size_t len;
|
||||
char* str;
|
||||
uint8_t* packet = (uint8_t *)queue->start;
|
||||
uint8_t* startpoint;
|
||||
|
||||
len = (size_t)MYSQL_GET_PACKET_LEN(packet);
|
||||
startpoint = &packet[5];
|
||||
str = (char *)malloc(len+1);
|
||||
snprintf(str, len+1, "%s", startpoint);
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Unable to write to backend due to "
|
||||
@ -717,13 +717,11 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
|
||||
while ((queue = gwbuf_consume(
|
||||
queue,
|
||||
GWBUF_LENGTH(queue))) != NULL);
|
||||
free(str);
|
||||
rc = 0;
|
||||
spinlock_release(&dcb->authlock);
|
||||
goto return_rc;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case MYSQL_IDLE:
|
||||
{
|
||||
uint8_t* ptr = GWBUF_DATA(queue);
|
||||
@ -1165,22 +1163,37 @@ gw_backend_close(DCB *dcb)
|
||||
mysql_send_com_quit(dcb, 0, quitbuf);
|
||||
|
||||
mysql_protocol_done(dcb);
|
||||
|
||||
/**
|
||||
* The lock is needed only to protect the read of session->state and
|
||||
* session->client values. Client's state may change by other thread
|
||||
* but client's close and adding client's DCB to zombies list is executed
|
||||
* only if client's DCB's state does _not_ change in parallel.
|
||||
*/
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
/**
|
||||
* If session->state is STOPPING, start closing client session.
|
||||
* Otherwise only this backend connection is closed.
|
||||
*/
|
||||
if (session != NULL && session->state == SESSION_STATE_STOPPING)
|
||||
{
|
||||
client_dcb = session->client;
|
||||
|
||||
if (client_dcb != NULL &&
|
||||
client_dcb->state == DCB_STATE_POLLING)
|
||||
if (session != NULL &&
|
||||
session->state == SESSION_STATE_STOPPING &&
|
||||
session->client != NULL)
|
||||
{
|
||||
if (session->client->state == DCB_STATE_POLLING)
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
|
||||
/** Close client DCB */
|
||||
dcb_close(client_dcb);
|
||||
dcb_close(session->client);
|
||||
}
|
||||
else
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1495,7 +1508,7 @@ static GWBUF* process_response_data (
|
||||
|
||||
/** Get command which was stored in gw_MySQLWrite_backend */
|
||||
p = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
CHK_PROTOCOL(p);
|
||||
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
|
||||
|
||||
/** All buffers processed here are sescmd responses */
|
||||
gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE);
|
||||
@ -1510,7 +1523,14 @@ static GWBUF* process_response_data (
|
||||
bool succp;
|
||||
|
||||
srvcmd = protocol_get_srv_command(p, false);
|
||||
|
||||
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [process_response_data] Read command %s for DCB %p fd %d.",
|
||||
pthread_self(),
|
||||
STRPACKETTYPE(srvcmd),
|
||||
dcb,
|
||||
dcb->fd)));
|
||||
/**
|
||||
* Read values from protocol structure, fails if values are
|
||||
* uninitialized.
|
||||
@ -1582,7 +1602,7 @@ static GWBUF* process_response_data (
|
||||
if (nbytes_left == 0)
|
||||
{
|
||||
/** No more packets in this response */
|
||||
if (npackets_left == 0)
|
||||
if (npackets_left == 0 && outbuf != NULL)
|
||||
{
|
||||
GWBUF* b = outbuf;
|
||||
|
||||
@ -1622,7 +1642,7 @@ static bool sescmd_response_complete(
|
||||
bool succp;
|
||||
|
||||
p = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
CHK_PROTOCOL(p);
|
||||
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
|
||||
|
||||
protocol_get_response_status(p, &npackets_left, &nbytes_left);
|
||||
|
||||
|
@ -377,15 +377,18 @@ MySQLSendHandshake(DCB* dcb)
|
||||
*
|
||||
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue
|
||||
*
|
||||
* The useful data: user, db, client_sha1 are copied into the MYSQL_session * dcb->session->data
|
||||
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
|
||||
* the dcb->data and later to dcb->session->data.
|
||||
*
|
||||
* client_capabilitiesa are copied into the dcb->protocol
|
||||
*
|
||||
* @param dcb Descriptor Control Block of the client
|
||||
* @param queue The GWBUF with data from client
|
||||
* @return 0 If succeed, otherwise non-zero value
|
||||
*
|
||||
* @note in case of failure, dcb->data is freed before returning. If succeed,
|
||||
* dcb->data is freed in session.c:session_free.
|
||||
*/
|
||||
|
||||
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
MySQLProtocol *protocol = NULL;
|
||||
/* int compress = -1; */
|
||||
@ -405,6 +408,13 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
CHK_PROTOCOL(protocol);
|
||||
client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session));
|
||||
#if defined(SS_DEBUG)
|
||||
client_data->myses_chk_top = CHK_NUM_MYSQLSES;
|
||||
client_data->myses_chk_tail = CHK_NUM_MYSQLSES;
|
||||
#endif
|
||||
/**
|
||||
* Assign authentication structure with client DCB.
|
||||
*/
|
||||
dcb->data = client_data;
|
||||
|
||||
stage1_hash = client_data->client_sha1;
|
||||
@ -425,7 +435,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
*/
|
||||
|
||||
/* Detect now if there are enough bytes to continue */
|
||||
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) {
|
||||
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -618,7 +629,7 @@ int gw_read_client_event(
|
||||
* Now there should be at least one complete mysql packet in read_buffer.
|
||||
*/
|
||||
switch (protocol->protocol_auth_state) {
|
||||
|
||||
|
||||
case MYSQL_AUTH_SENT:
|
||||
{
|
||||
int auth_val;
|
||||
@ -657,8 +668,8 @@ int gw_read_client_event(
|
||||
"%lu [gw_read_client_event] session "
|
||||
"creation failed. fd %d, "
|
||||
"state = MYSQL_AUTH_FAILED.",
|
||||
protocol->owner_dcb->fd,
|
||||
pthread_self())));
|
||||
pthread_self(),
|
||||
protocol->owner_dcb->fd)));
|
||||
|
||||
/** Send ERR 1045 to client */
|
||||
mysql_send_auth_error(
|
||||
@ -670,7 +681,7 @@ int gw_read_client_event(
|
||||
dcb_close(dcb);
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
char* fail_str = NULL;
|
||||
|
||||
@ -703,7 +714,15 @@ int gw_read_client_event(
|
||||
"state = MYSQL_AUTH_FAILED.",
|
||||
protocol->owner_dcb->fd,
|
||||
pthread_self())));
|
||||
|
||||
/**
|
||||
* Release MYSQL_session since it is not used anymore.
|
||||
*/
|
||||
if (!DCB_IS_CLONE(dcb))
|
||||
{
|
||||
free(dcb->data);
|
||||
}
|
||||
dcb->data = NULL;
|
||||
|
||||
dcb_close(dcb);
|
||||
}
|
||||
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
|
||||
@ -1395,15 +1414,13 @@ gw_client_close(DCB *dcb)
|
||||
dcb->state == DCB_STATE_NOPOLLING ||
|
||||
dcb->state == DCB_STATE_ZOMBIE)
|
||||
{
|
||||
CHK_PROTOCOL(protocol);
|
||||
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(protocol);
|
||||
}
|
||||
#endif
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"%lu [gw_client_close]",
|
||||
pthread_self())));
|
||||
|
||||
mysql_protocol_done(dcb);
|
||||
|
||||
mysql_protocol_done(dcb);
|
||||
session = dcb->session;
|
||||
/**
|
||||
* session may be NULL if session_alloc failed.
|
||||
|
@ -151,38 +151,6 @@ retblock:
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* gw_mysql_close
|
||||
*
|
||||
* close a connection if opened
|
||||
* free data scructure for MySQLProtocol
|
||||
*
|
||||
* @param ptr The MySQLProtocol ** to close/free
|
||||
*
|
||||
*/
|
||||
void gw_mysql_close(MySQLProtocol **ptr) {
|
||||
MySQLProtocol *conn = *ptr;
|
||||
|
||||
ss_dassert(*ptr != NULL);
|
||||
|
||||
if (*ptr == NULL)
|
||||
return;
|
||||
|
||||
|
||||
if (conn->fd > 0) {
|
||||
/* COM_QUIT will not be sent here, but from the caller of this routine! */
|
||||
close(conn->fd);
|
||||
} else {
|
||||
// no socket here
|
||||
}
|
||||
|
||||
free(*ptr);
|
||||
|
||||
*ptr = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the backend server MySQL handshake
|
||||
*
|
||||
@ -573,6 +541,16 @@ int gw_send_authentication_to_backend(
|
||||
uint8_t *curr_passwd = NULL;
|
||||
unsigned int charset;
|
||||
|
||||
/**
|
||||
* If session is stopping return with error.
|
||||
*/
|
||||
if (conn->owner_dcb->session == NULL ||
|
||||
(conn->owner_dcb->session->state != SESSION_STATE_READY &&
|
||||
conn->owner_dcb->session->state != SESSION_STATE_ROUTER_READY))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strlen(dbname))
|
||||
curr_db = dbname;
|
||||
|
||||
@ -1659,7 +1637,9 @@ mysql_send_auth_error (
|
||||
* Buffer contains at least one of the following:
|
||||
* complete [complete] [partial] mysql packet
|
||||
*
|
||||
* return pointer to gwbuf containing a complete packet or
|
||||
* @param p_readbuf Address of read buffer pointer
|
||||
*
|
||||
* @return pointer to gwbuf containing a complete packet or
|
||||
* NULL if no complete packet was found.
|
||||
*/
|
||||
GWBUF* gw_MySQL_get_next_packet(
|
||||
|
@ -144,7 +144,7 @@ static ROUTER *
|
||||
GHACreateInstance(SERVICE *service, char **options)
|
||||
{
|
||||
ROUTER_INSTANCE *inst;
|
||||
SERVER *server;
|
||||
SERVER_REF *server;
|
||||
int i, n;
|
||||
|
||||
if ((inst = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) {
|
||||
@ -159,7 +159,7 @@ int i, n;
|
||||
* that we can maintain a count of the number of connections to each
|
||||
* backend server.
|
||||
*/
|
||||
for (server = service->databases, n = 0; server; server = server->nextdb)
|
||||
for (server = service->dbref, n = 0; server; server = server->next)
|
||||
n++;
|
||||
|
||||
inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *));
|
||||
@ -169,7 +169,7 @@ int i, n;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (server = service->databases, n = 0; server; server = server->nextdb)
|
||||
for (server = service->dbref, n = 0; server; server = server->next)
|
||||
{
|
||||
if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL)
|
||||
{
|
||||
@ -179,7 +179,7 @@ int i, n;
|
||||
free(inst);
|
||||
return NULL;
|
||||
}
|
||||
inst->servers[n]->server = server;
|
||||
inst->servers[n]->server = server->server;
|
||||
inst->servers[n]->current_connection_count = 0;
|
||||
n++;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ CLIOBJ=$(CLISRCS:.c=.o)
|
||||
SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c
|
||||
OBJ=$(SRCS:.c=.o)
|
||||
LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
|
||||
MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so
|
||||
MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so libbinlogrouter.so
|
||||
|
||||
|
||||
all: $(MODULES)
|
||||
@ -68,12 +68,16 @@ libcli.so: $(CLIOBJ)
|
||||
libreadwritesplit.so:
|
||||
(cd readwritesplit; touch depend.mk ; make; cp $@ ..)
|
||||
|
||||
libbinlogrouter.so:
|
||||
(cd binlog; touch depend.mk ; make; cp $@ ..)
|
||||
|
||||
.c.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
$(DEL) $(OBJ) $(MODULES)
|
||||
(cd readwritesplit; touch depend.mk; make clean)
|
||||
(cd binlog; touch depend.mk; make clean)
|
||||
|
||||
tags:
|
||||
ctags $(SRCS) $(HDRS)
|
||||
@ -83,10 +87,12 @@ depend:
|
||||
@$(DEL) depend.mk
|
||||
cc -M $(CFLAGS) $(SRCS) > depend.mk
|
||||
(cd readwritesplit; touch depend.mk ; make depend)
|
||||
(cd binlog; touch depend.mk ; make depend)
|
||||
|
||||
install: $(MODULES)
|
||||
install -D $(MODULES) $(DEST)/modules
|
||||
(cd readwritesplit; make DEST=$(DEST) install)
|
||||
(cd binlog; make DEST=$(DEST) install)
|
||||
|
||||
cleantests:
|
||||
$(MAKE) -C test cleantests
|
||||
|
@ -201,7 +201,7 @@ int i;
|
||||
* which of these servers is currently the master and replicate from
|
||||
* that server.
|
||||
*/
|
||||
if (service->databases == NULL || service->databases->nextdb != NULL)
|
||||
if (service->dbref == NULL || service->dbref->next != NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
@ -332,16 +332,6 @@ int i;
|
||||
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;
|
||||
@ -353,12 +343,31 @@ int i;
|
||||
/*
|
||||
* Initialise the binlog file and position
|
||||
*/
|
||||
blr_file_init(inst);
|
||||
if (blr_file_init(inst) == 0)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"%s: Service not started due to lack of binlog directory.",
|
||||
service->name)));
|
||||
free(inst);
|
||||
return NULL;
|
||||
}
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Binlog router: current binlog file is: %s, current position %u\n",
|
||||
inst->binlog_name, inst->binlog_position)));
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Initialise the binlog cache for this router instance
|
||||
*/
|
||||
@ -423,6 +432,8 @@ ROUTER_SLAVE *slave;
|
||||
slave->cstate = 0;
|
||||
slave->pthread = 0;
|
||||
slave->overrun = 0;
|
||||
slave->uuid = NULL;
|
||||
slave->hostname = NULL;
|
||||
spinlock_init(&slave->catch_lock);
|
||||
slave->dcb = session->client;
|
||||
slave->router = inst;
|
||||
@ -532,7 +543,7 @@ ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Binlog router close session with master server %s",
|
||||
router->service->databases->unique_name)));
|
||||
router->service->dbref->server->unique_name)));
|
||||
blr_master_reconnect(router);
|
||||
return;
|
||||
}
|
||||
@ -777,8 +788,10 @@ struct tm tm;
|
||||
session->serverid);
|
||||
if (session->hostname)
|
||||
dcb_printf(dcb, "\t\tHostname: %s\n", session->hostname);
|
||||
if (session->uuid)
|
||||
dcb_printf(dcb, "\t\tSlave UUID: %s\n", session->uuid);
|
||||
dcb_printf(dcb,
|
||||
"\t\tSlave: %d\n",
|
||||
"\t\tSlave: %s\n",
|
||||
session->dcb->remote);
|
||||
dcb_printf(dcb,
|
||||
"\t\tSlave DCB: %p\n",
|
||||
@ -1034,3 +1047,144 @@ ROUTER_SLAVE *slave;
|
||||
}
|
||||
spinlock_release(&router->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return some basic statistics from the router in response to a COM_STATISTICS
|
||||
* request.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param slave The "slave" connection that requested the statistics
|
||||
* @param queue The statistics request
|
||||
*
|
||||
* @return non-zero on sucessful send
|
||||
*/
|
||||
int
|
||||
blr_statistics(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
char result[1000], *ptr;
|
||||
GWBUF *ret;
|
||||
int len;
|
||||
|
||||
snprintf(result, 1000,
|
||||
"Uptime: %u Threads: %u Events: %u Slaves: %u Master State: %s",
|
||||
time(0) - router->connect_time,
|
||||
config_threadcount(),
|
||||
router->stats.n_binlogs_ses,
|
||||
router->stats.n_slaves,
|
||||
blrm_states[router->master_state]);
|
||||
if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL)
|
||||
return 0;
|
||||
len = strlen(result);
|
||||
ptr = GWBUF_DATA(ret);
|
||||
*ptr++ = len & 0xff;
|
||||
*ptr++ = (len & 0xff00) >> 8;
|
||||
*ptr++ = (len & 0xff0000) >> 16;
|
||||
*ptr++ = 1;
|
||||
strncpy(ptr, result, len);
|
||||
|
||||
return slave->dcb->func.write(slave->dcb, ret);
|
||||
}
|
||||
|
||||
int
|
||||
blr_ping(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
{
|
||||
char *ptr;
|
||||
GWBUF *ret;
|
||||
int len;
|
||||
|
||||
if ((ret = gwbuf_alloc(5)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(ret);
|
||||
*ptr++ = 0x01;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 1;
|
||||
*ptr = 0; // OK
|
||||
|
||||
return slave->dcb->func.write(slave->dcb, ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* mysql_send_custom_error
|
||||
*
|
||||
* Send a MySQL protocol Generic ERR message, to the dcb
|
||||
* Note the errno and state are still fixed now
|
||||
*
|
||||
* @param dcb Owner_Dcb Control Block for the connection to which the OK is sent
|
||||
* @param packet_number
|
||||
* @param in_affected_rows
|
||||
* @param msg
|
||||
* @return 1 Non-zero if data was sent
|
||||
*
|
||||
*/
|
||||
int
|
||||
blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg)
|
||||
{
|
||||
uint8_t *outbuf = NULL;
|
||||
uint32_t mysql_payload_size = 0;
|
||||
uint8_t mysql_packet_header[4];
|
||||
uint8_t *mysql_payload = NULL;
|
||||
uint8_t field_count = 0;
|
||||
uint8_t mysql_err[2];
|
||||
uint8_t mysql_statemsg[6];
|
||||
unsigned int mysql_errno = 0;
|
||||
const char *mysql_error_msg = NULL;
|
||||
const char *mysql_state = NULL;
|
||||
GWBUF *errbuf = NULL;
|
||||
|
||||
mysql_errno = 2003;
|
||||
mysql_error_msg = "An errorr occurred ...";
|
||||
mysql_state = "HY000";
|
||||
|
||||
field_count = 0xff;
|
||||
gw_mysql_set_byte2(mysql_err, mysql_errno);
|
||||
mysql_statemsg[0]='#';
|
||||
memcpy(mysql_statemsg+1, mysql_state, 5);
|
||||
|
||||
if (msg != NULL) {
|
||||
mysql_error_msg = msg;
|
||||
}
|
||||
|
||||
mysql_payload_size = sizeof(field_count) +
|
||||
sizeof(mysql_err) +
|
||||
sizeof(mysql_statemsg) +
|
||||
strlen(mysql_error_msg);
|
||||
|
||||
/** allocate memory for packet header + payload */
|
||||
errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
|
||||
ss_dassert(errbuf != NULL);
|
||||
|
||||
if (errbuf == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
outbuf = GWBUF_DATA(errbuf);
|
||||
|
||||
/** write packet header and packet number */
|
||||
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
||||
mysql_packet_header[3] = packet_number;
|
||||
|
||||
/** write header */
|
||||
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
||||
|
||||
mysql_payload = outbuf + sizeof(mysql_packet_header);
|
||||
|
||||
/** write field */
|
||||
memcpy(mysql_payload, &field_count, sizeof(field_count));
|
||||
mysql_payload = mysql_payload + sizeof(field_count);
|
||||
|
||||
/** write errno */
|
||||
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_err);
|
||||
|
||||
/** write sqlstate */
|
||||
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
|
||||
|
||||
/** write error message */
|
||||
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
|
||||
|
||||
return dcb->func.write(dcb, errbuf);
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
|
||||
static void blr_file_create(ROUTER_INSTANCE *router, char *file);
|
||||
static int 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);
|
||||
static void blr_log_header(logfile_id_t file, char *msg, uint8_t *ptr);
|
||||
@ -68,7 +68,7 @@ static void blr_log_header(logfile_id_t file, char *msg, uint8_t *ptr);
|
||||
*
|
||||
* @param router The router instance this defines the master for this replication chain
|
||||
*/
|
||||
void
|
||||
int
|
||||
blr_file_init(ROUTER_INSTANCE *router)
|
||||
{
|
||||
char *ptr, path[1024], filename[1050];
|
||||
@ -92,16 +92,28 @@ struct dirent *dp;
|
||||
|
||||
router->binlogdir = strdup(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(path, router->binlogdir, 1024);
|
||||
}
|
||||
if (access(router->binlogdir, R_OK) == -1)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"%s: Unable to read the binlog directory %s.",
|
||||
router->service->name, router->binlogdir)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* First try to find a binlog file number by reading the directory */
|
||||
root_len = strlen(router->fileroot);
|
||||
dirp = opendir(path);
|
||||
if ((dirp = opendir(path)) == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"%s: Unable to read the binlog directory %s, %s.",
|
||||
router->service->name, router->binlogdir,
|
||||
strerror(errno))));
|
||||
return 0;
|
||||
}
|
||||
while ((dp = readdir(dirp)) != NULL)
|
||||
{
|
||||
if (strncmp(dp->d_name, router->fileroot, root_len) == 0)
|
||||
@ -134,20 +146,21 @@ struct dirent *dp;
|
||||
router->initbinlog);
|
||||
else
|
||||
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, 1);
|
||||
blr_file_create(router, filename);
|
||||
if (! blr_file_create(router, filename))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, n);
|
||||
blr_file_append(router, filename);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
blr_file_rotate(ROUTER_INSTANCE *router, char *file, uint64_t pos)
|
||||
{
|
||||
blr_file_create(router, file);
|
||||
return blr_file_create(router, file);
|
||||
}
|
||||
|
||||
|
||||
@ -156,8 +169,9 @@ blr_file_rotate(ROUTER_INSTANCE *router, char *file, uint64_t pos)
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param file The binlog file name
|
||||
* @return Non-zero if the fie creation succeeded
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
blr_file_create(ROUTER_INSTANCE *router, char *file)
|
||||
{
|
||||
char path[1024];
|
||||
@ -175,7 +189,9 @@ unsigned char magic[] = BINLOG_MAGIC;
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"Failed to create binlog file %s", path)));
|
||||
"%s: Failed to create binlog file %s, %s.",
|
||||
router->service->name, path, strerror(errno))));
|
||||
return 0;
|
||||
}
|
||||
fsync(fd);
|
||||
close(router->binlog_fd);
|
||||
@ -184,6 +200,7 @@ unsigned char magic[] = BINLOG_MAGIC;
|
||||
router->binlog_position = 4; /* Initial position after the magic number */
|
||||
spinlock_release(&router->binlog_lock);
|
||||
router->binlog_fd = fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -225,15 +242,31 @@ int fd;
|
||||
* @param router The router instance
|
||||
* @param buf The binlog record
|
||||
* @param len The length of the binlog record
|
||||
* @return Return the number of bytes written
|
||||
*/
|
||||
void
|
||||
int
|
||||
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);
|
||||
int n;
|
||||
|
||||
if ((n = pwrite(router->binlog_fd, buf, hdr->event_size,
|
||||
hdr->next_pos - hdr->event_size)) != hdr->event_size)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
|
||||
"%s: Failed to write binlog record at %d of %s, %s. "
|
||||
"Truncating to previous record.",
|
||||
router->service->name, hdr->next_pos - hdr->event_size,
|
||||
router->binlog_name,
|
||||
strerror(errno))));
|
||||
/* Remove any partual event that was written */
|
||||
ftruncate(router->binlog_fd, hdr->next_pos - hdr->event_size);
|
||||
return 0;
|
||||
}
|
||||
spinlock_acquire(&router->binlog_lock);
|
||||
router->binlog_position = hdr->next_pos;
|
||||
router->last_written = hdr->next_pos - hdr->event_size;
|
||||
spinlock_release(&router->binlog_lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,13 +71,13 @@ static GWBUF *blr_make_registration(ROUTER_INSTANCE *router);
|
||||
static GWBUF *blr_make_binlog_dump(ROUTER_INSTANCE *router);
|
||||
void encode_value(unsigned char *data, unsigned int value, int len);
|
||||
void blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt);
|
||||
static void blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *pkt, REP_HEADER *hdr);
|
||||
static int blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *pkt, REP_HEADER *hdr);
|
||||
void blr_distribute_binlog_record(ROUTER_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
|
||||
static void *CreateMySQLAuthData(char *username, char *password, char *database);
|
||||
void blr_extract_header(uint8_t *pkt, REP_HEADER *hdr);
|
||||
inline uint32_t extract_field(uint8_t *src, int bits);
|
||||
static void blr_log_packet(logfile_id_t file, char *msg, uint8_t *ptr, int len);
|
||||
|
||||
static void blr_master_close(ROUTER_INSTANCE *);
|
||||
static int keepalive = 1;
|
||||
|
||||
/**
|
||||
@ -121,7 +121,7 @@ GWBUF *buf;
|
||||
return;
|
||||
}
|
||||
client->session = router->session;
|
||||
if ((router->master = dcb_connect(router->service->databases, router->session, BLR_PROTOCOL)) == NULL)
|
||||
if ((router->master = dcb_connect(router->service->dbref->server, router->session, BLR_PROTOCOL)) == NULL)
|
||||
{
|
||||
char *name;
|
||||
if ((name = malloc(strlen(router->service->name)
|
||||
@ -135,10 +135,10 @@ GWBUF *buf;
|
||||
router->retry_backoff = BLR_MAX_BACKOFF;
|
||||
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
|
||||
"Binlog router: failed to connect to master server '%s'",
|
||||
router->service->databases->unique_name)));
|
||||
router->service->dbref->server->unique_name)));
|
||||
return;
|
||||
}
|
||||
router->master->remote = strdup(router->service->databases->name);
|
||||
router->master->remote = strdup(router->service->dbref->server->name);
|
||||
LOGIF(LM,(skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"%s: atempting to connect to master server %s.",
|
||||
@ -247,6 +247,37 @@ int do_reconnect = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown a connection to the master
|
||||
*
|
||||
* @param router The router instance
|
||||
*/
|
||||
void
|
||||
blr_master_close(ROUTER_INSTANCE *router)
|
||||
{
|
||||
dcb_close(router->master);
|
||||
router->master_state = BLRM_UNCONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this master connection for a delayed reconnect, used during
|
||||
* error recovery to cause a reconnect after 60 seconds.
|
||||
*
|
||||
* @param router The router instance
|
||||
*/
|
||||
void
|
||||
blr_master_delayed_connect(ROUTER_INSTANCE *router)
|
||||
{
|
||||
char *name;
|
||||
|
||||
if ((name = malloc(strlen(router->service->name)
|
||||
+ strlen(" Master Recovery")+1)) != NULL);
|
||||
{
|
||||
sprintf(name, "%s Master Recovery", router->service->name);
|
||||
hktask_oneshot(name, blr_start_master, router, 60);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binlog router master side state machine event handler.
|
||||
*
|
||||
@ -407,6 +438,20 @@ char query[128];
|
||||
case BLRM_SELECTVER:
|
||||
// Response to SELECT VERSION should be stored
|
||||
router->saved_master.selectver = buf;
|
||||
buf = blr_make_query("SELECT @@version_comment limit 1;");
|
||||
router->master_state = BLRM_SELECTVERCOM;
|
||||
router->master->func.write(router->master, buf);
|
||||
break;
|
||||
case BLRM_SELECTVERCOM:
|
||||
// Response to SELECT @@version_comment should be stored
|
||||
router->saved_master.selectvercom = buf;
|
||||
buf = blr_make_query("SELECT @@hostname;");
|
||||
router->master_state = BLRM_SELECTHOSTNAME;
|
||||
router->master->func.write(router->master, buf);
|
||||
break;
|
||||
case BLRM_SELECTHOSTNAME:
|
||||
// Response to SELECT @@hostname should be stored
|
||||
router->saved_master.selecthostname = buf;
|
||||
buf = blr_make_registration(router);
|
||||
router->master_state = BLRM_REGISTER;
|
||||
router->master->func.write(router->master, buf);
|
||||
@ -809,10 +854,36 @@ static REP_HEADER phdr;
|
||||
// into the binlog file
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
router->rotating = 1;
|
||||
blr_write_binlog_record(router, &hdr, ptr);
|
||||
if (blr_write_binlog_record(router, &hdr, ptr) == 0)
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt,
|
||||
GWBUF_LENGTH(pkt))) != NULL);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
{
|
||||
blr_rotate_event(router, ptr, &hdr);
|
||||
if (!blr_rotate_event(router, ptr, &hdr))
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt,
|
||||
GWBUF_LENGTH(pkt))) != NULL);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
}
|
||||
blr_distribute_binlog_record(router, &hdr, ptr);
|
||||
}
|
||||
@ -833,7 +904,20 @@ static REP_HEADER phdr;
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
{
|
||||
router->rotating = 1;
|
||||
blr_rotate_event(router, ptr, &hdr);
|
||||
if (!blr_rotate_event(router, ptr, &hdr))
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt,
|
||||
GWBUF_LENGTH(pkt))) != NULL);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -933,7 +1017,7 @@ register uint32_t rval = 0, shift = 0;
|
||||
* @param ptr The packet containing the rotate event
|
||||
* @param hdr The replication message header
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
blr_rotate_event(ROUTER_INSTANCE *router, uint8_t *ptr, REP_HEADER *hdr)
|
||||
{
|
||||
int len, slen;
|
||||
@ -963,9 +1047,14 @@ char file[BINLOG_FNAMELEN+1];
|
||||
if (strncmp(router->binlog_name, file, slen) != 0)
|
||||
{
|
||||
router->stats.n_rotates++;
|
||||
blr_file_rotate(router, file, pos);
|
||||
if (blr_file_rotate(router, file, pos) == 0)
|
||||
{
|
||||
router->rotating = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
router->rotating = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,12 +111,20 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
case COM_BINLOG_DUMP:
|
||||
return blr_slave_binlog_dump(router, slave, queue);
|
||||
break;
|
||||
case COM_STATISTICS:
|
||||
return blr_statistics(router, slave, queue);
|
||||
break;
|
||||
case COM_PING:
|
||||
return blr_ping(router, slave, queue);
|
||||
break;
|
||||
case COM_QUIT:
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"COM_QUIT received from slave with server_id %d",
|
||||
slave->serverid)));
|
||||
break;
|
||||
default:
|
||||
blr_send_custom_error(slave->dcb, 1, 0,
|
||||
"MySQL command not supported by the binlog router.");
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Unexpected MySQL Command (%d) received from slave",
|
||||
@ -133,12 +141,14 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
* when MaxScale registered as a slave. The exception to the rule is the
|
||||
* request to obtain the current timestamp value of the server.
|
||||
*
|
||||
* Five select statements are currently supported:
|
||||
* Seven select statements are currently supported:
|
||||
* SELECT UNIX_TIMESTAMP();
|
||||
* SELECT @master_binlog_checksum
|
||||
* SELECT @@GLOBAL.GTID_MODE
|
||||
* SELECT VERSION()
|
||||
* SELECT 1
|
||||
* SELECT @@version_comment limit 1
|
||||
* SELECT @@hostname
|
||||
*
|
||||
* Two show commands are supported:
|
||||
* SHOW VARIABLES LIKE 'SERVER_ID'
|
||||
@ -208,6 +218,16 @@ int query_len;
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.selectver);
|
||||
}
|
||||
else if (strcasecmp(word, "@@version_comment") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.selectvercom);
|
||||
}
|
||||
else if (strcasecmp(word, "@@hostname") == 0)
|
||||
{
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.selecthostname);
|
||||
}
|
||||
}
|
||||
else if (strcasecmp(word, "SHOW") == 0)
|
||||
{
|
||||
@ -251,6 +271,8 @@ int query_len;
|
||||
}
|
||||
else if (strcasecmp(word, "@slave_uuid") == 0)
|
||||
{
|
||||
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
|
||||
slave->uuid = strdup(word);
|
||||
free(query_text);
|
||||
return blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
|
||||
}
|
||||
@ -1075,3 +1097,81 @@ uint32_t chksum;
|
||||
encode_value(ptr, chksum, 32);
|
||||
slave->dcb->func.write(slave->dcb, head);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send the field count packet in a response packet sequence.
|
||||
*
|
||||
* @param router The router
|
||||
* @param slave The slave connection
|
||||
* @param count Number of columns in the result set
|
||||
* @return Non-zero on success
|
||||
*/
|
||||
static int
|
||||
blr_slave_send_fieldcount(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int count)
|
||||
{
|
||||
GWBUF *pkt;
|
||||
uint8_t *ptr;
|
||||
|
||||
if ((pkt = gwbuf_alloc(5)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(pkt);
|
||||
encode_value(ptr, 1, 24); // Add length of data packet
|
||||
ptr += 3;
|
||||
*ptr++ = 0x01; // Sequence number in response
|
||||
*ptr++ = count; // Length of result string
|
||||
return slave->dcb->func.write(slave->dcb, pkt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the column definition packet in a response packet sequence.
|
||||
*
|
||||
* @param router The router
|
||||
* @param slave The slave connection
|
||||
* @param name Name of the column
|
||||
* @param type Column type
|
||||
* @param len Column length
|
||||
* @param seqno Packet sequence number
|
||||
* @return Non-zero on success
|
||||
*/
|
||||
static int
|
||||
blr_slave_send_columndef(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *name, int type, int len, uint8_t seqno)
|
||||
{
|
||||
GWBUF *pkt;
|
||||
uint8_t *ptr;
|
||||
|
||||
if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(pkt);
|
||||
encode_value(ptr, 22 + strlen(name), 24); // Add length of data packet
|
||||
ptr += 3;
|
||||
*ptr++ = seqno; // Sequence number in response
|
||||
*ptr++ = 3; // Catalog is always def
|
||||
*ptr++ = 'd';
|
||||
*ptr++ = 'e';
|
||||
*ptr++ = 'f';
|
||||
*ptr++ = 0; // Schema name length
|
||||
*ptr++ = 0; // virtal table name length
|
||||
*ptr++ = 0; // Table name length
|
||||
*ptr++ = strlen(name); // Column name length;
|
||||
while (*name)
|
||||
*ptr++ = *name++; // Copy the column name
|
||||
*ptr++ = 0; // Orginal column name
|
||||
*ptr++ = 0x0c; // Length of next fields always 12
|
||||
*ptr++ = 0x3f; // Character set
|
||||
*ptr++ = 0;
|
||||
encode_value(ptr, len, 32); // Add length of column
|
||||
ptr += 4;
|
||||
*ptr++ = type;
|
||||
*ptr++ = 0x81; // Two bytes of flags
|
||||
if (type == 0xfd)
|
||||
*ptr++ = 0x1f;
|
||||
else
|
||||
*ptr++ = 0x00;
|
||||
*ptr++= 0;
|
||||
*ptr++= 0;
|
||||
*ptr++= 0;
|
||||
return slave->dcb->func.write(slave->dcb, pkt);
|
||||
}
|
||||
|
@ -678,7 +678,7 @@ SERVICE *service;
|
||||
if (service)
|
||||
return (unsigned long)(service->users);
|
||||
else
|
||||
return 0;
|
||||
return 1; /*< invalid argument */
|
||||
}
|
||||
return rval;
|
||||
case ARG_TYPE_DCB:
|
||||
@ -798,7 +798,7 @@ bool in_space = false;
|
||||
}
|
||||
}
|
||||
*lptr = 0;
|
||||
args[i+1] = NULL;
|
||||
args[MIN(MAXARGS-1,i+1)] = NULL;
|
||||
|
||||
if (args[0] == NULL || *args[0] == 0)
|
||||
return 1;
|
||||
@ -886,11 +886,15 @@ bool in_space = false;
|
||||
break;
|
||||
case 1:
|
||||
arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
|
||||
if (arg1)
|
||||
cmds[i].options[j].fn(dcb, arg1);
|
||||
else
|
||||
if (arg1 == 0x1)
|
||||
{
|
||||
dcb_printf(dcb, "Invalid argument: %s\n",
|
||||
args[2]);
|
||||
args[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmds[i].options[j].fn(dcb, arg1);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
|
||||
|
@ -146,7 +146,8 @@ static void rses_end_locked_router_action(
|
||||
|
||||
static BACKEND *get_root_master(
|
||||
BACKEND **servers);
|
||||
|
||||
static int handle_state_switch(
|
||||
DCB* dcb,DCB_REASON reason, void * routersession);
|
||||
static SPINLOCK instlock;
|
||||
static ROUTER_INSTANCE *instances;
|
||||
|
||||
@ -203,6 +204,7 @@ createInstance(SERVICE *service, char **options)
|
||||
{
|
||||
ROUTER_INSTANCE *inst;
|
||||
SERVER *server;
|
||||
SERVER_REF *sref;
|
||||
int i, n;
|
||||
BACKEND *backend;
|
||||
char *weightby;
|
||||
@ -219,7 +221,7 @@ char *weightby;
|
||||
* that we can maintain a count of the number of connections to each
|
||||
* backend server.
|
||||
*/
|
||||
for (server = service->databases, n = 0; server; server = server->nextdb)
|
||||
for (sref = service->dbref, n = 0; sref; sref = sref->next)
|
||||
n++;
|
||||
|
||||
inst->servers = (BACKEND **)calloc(n + 1, sizeof(BACKEND *));
|
||||
@ -229,7 +231,7 @@ char *weightby;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (server = service->databases, n = 0; server; server = server->nextdb)
|
||||
for (sref = service->dbref, n = 0; sref; sref = sref->next)
|
||||
{
|
||||
if ((inst->servers[n] = malloc(sizeof(BACKEND))) == NULL)
|
||||
{
|
||||
@ -239,7 +241,7 @@ char *weightby;
|
||||
free(inst);
|
||||
return NULL;
|
||||
}
|
||||
inst->servers[n]->server = server;
|
||||
inst->servers[n]->server = sref->server;
|
||||
inst->servers[n]->current_connection_count = 0;
|
||||
inst->servers[n]->weight = 1000;
|
||||
n++;
|
||||
@ -409,12 +411,12 @@ BACKEND *master_host = NULL;
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [newSession] Examine server in port %d with "
|
||||
"%d connections. Status is %d, "
|
||||
"%d connections. Status is %s, "
|
||||
"inst->bitvalue is %d",
|
||||
pthread_self(),
|
||||
inst->servers[i]->server->port,
|
||||
inst->servers[i]->current_connection_count,
|
||||
inst->servers[i]->server->status,
|
||||
STRSRVSTATUS(inst->servers[i]->server),
|
||||
inst->bitmask)));
|
||||
}
|
||||
|
||||
@ -536,7 +538,12 @@ BACKEND *master_host = NULL;
|
||||
free(client_rses);
|
||||
return NULL;
|
||||
}
|
||||
inst->stats.n_sessions++;
|
||||
dcb_add_callback(
|
||||
client_rses->backend_dcb,
|
||||
DCB_REASON_NOT_RESPONDING,
|
||||
&handle_state_switch,
|
||||
client_rses);
|
||||
inst->stats.n_sessions++;
|
||||
|
||||
/**
|
||||
* Add this session to the list of active sessions.
|
||||
@ -693,7 +700,8 @@ routeQuery(ROUTER *instance, void *router_session, GWBUF *queue)
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
}
|
||||
|
||||
if (rses_is_closed || backend_dcb == NULL)
|
||||
if (rses_is_closed || backend_dcb == NULL ||
|
||||
SERVER_IS_DOWN(router_cli_ses->backend->server))
|
||||
{
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
@ -797,7 +805,7 @@ clientReply(
|
||||
GWBUF *queue,
|
||||
DCB *backend_dcb)
|
||||
{
|
||||
DCB *client = NULL;
|
||||
DCB *client ;
|
||||
|
||||
client = backend_dcb->session->client;
|
||||
|
||||
@ -841,18 +849,21 @@ static void handleError(
|
||||
else
|
||||
{
|
||||
backend_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
}
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
sesstate = session->state;
|
||||
client_dcb = session->client;
|
||||
spinlock_release(&session->ses_lock);
|
||||
ss_dassert(client_dcb != NULL);
|
||||
|
||||
if (sesstate == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
CHK_DCB(client_dcb);
|
||||
spinlock_release(&session->ses_lock);
|
||||
client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
|
||||
}
|
||||
else
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
}
|
||||
|
||||
/** false because connection is not available anymore */
|
||||
*succp = false;
|
||||
@ -954,3 +965,41 @@ static BACKEND *get_root_master(BACKEND **servers) {
|
||||
}
|
||||
return master_host;
|
||||
}
|
||||
|
||||
static int handle_state_switch(DCB* dcb,DCB_REASON reason, void * routersession)
|
||||
{
|
||||
ss_dassert(dcb != NULL);
|
||||
SESSION* session = dcb->session;
|
||||
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*)routersession;
|
||||
SERVICE* service = session->service;
|
||||
ROUTER* router = (ROUTER *)service->router;
|
||||
|
||||
switch(reason)
|
||||
{
|
||||
case DCB_REASON_CLOSE:
|
||||
dcb->func.close(dcb);
|
||||
break;
|
||||
case DCB_REASON_DRAINED:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_HIGH_WATER:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_LOW_WATER:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_ERROR:
|
||||
dcb->func.error(dcb);
|
||||
break;
|
||||
case DCB_REASON_HUP:
|
||||
dcb->func.hangup(dcb);
|
||||
break;
|
||||
case DCB_REASON_NOT_RESPONDING:
|
||||
dcb->func.hangup(dcb);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ static int router_get_servercount(ROUTER_INSTANCE* router);
|
||||
static int rses_get_max_slavecount(ROUTER_CLIENT_SES* rses, int router_nservers);
|
||||
static int rses_get_max_replication_lag(ROUTER_CLIENT_SES* rses);
|
||||
static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb);
|
||||
static DCB* rses_get_client_dcb(ROUTER_CLIENT_SES* rses);
|
||||
|
||||
static route_target_t get_route_target (
|
||||
skygw_query_type_t qtype,
|
||||
@ -391,7 +392,7 @@ ROUTER_OBJECT* GetModuleObject()
|
||||
|
||||
|
||||
/**
|
||||
* Refresh the instance by hte given parameter value.
|
||||
* Refresh the instance by the given parameter value.
|
||||
*
|
||||
* @param router Router instance
|
||||
* @param singleparam Parameter fo be reloaded
|
||||
@ -557,6 +558,7 @@ createInstance(SERVICE *service, char **options)
|
||||
{
|
||||
ROUTER_INSTANCE* router;
|
||||
SERVER* server;
|
||||
SERVER_REF* sref;
|
||||
int nservers;
|
||||
int i;
|
||||
CONFIG_PARAMETER* param;
|
||||
@ -569,13 +571,13 @@ createInstance(SERVICE *service, char **options)
|
||||
spinlock_init(&router->lock);
|
||||
|
||||
/** Calculate number of servers */
|
||||
server = service->databases;
|
||||
sref = service->dbref;
|
||||
nservers = 0;
|
||||
|
||||
while (server != NULL)
|
||||
while (sref != NULL)
|
||||
{
|
||||
nservers++;
|
||||
server=server->nextdb;
|
||||
sref=sref->next;
|
||||
}
|
||||
router->servers = (BACKEND **)calloc(nservers + 1, sizeof(BACKEND *));
|
||||
|
||||
@ -589,10 +591,11 @@ createInstance(SERVICE *service, char **options)
|
||||
* maintain a count of the number of connections to each
|
||||
* backend server.
|
||||
*/
|
||||
server = service->databases;
|
||||
|
||||
sref = service->dbref;
|
||||
nservers= 0;
|
||||
|
||||
while (server != NULL) {
|
||||
while (sref != NULL) {
|
||||
if ((router->servers[nservers] = malloc(sizeof(BACKEND))) == NULL)
|
||||
{
|
||||
/** clean up */
|
||||
@ -603,7 +606,7 @@ createInstance(SERVICE *service, char **options)
|
||||
free(router);
|
||||
return NULL;
|
||||
}
|
||||
router->servers[nservers]->backend_server = server;
|
||||
router->servers[nservers]->backend_server = sref->server;
|
||||
router->servers[nservers]->backend_conn_count = 0;
|
||||
router->servers[nservers]->be_valid = false;
|
||||
router->servers[nservers]->weight = 1000;
|
||||
@ -612,7 +615,7 @@ createInstance(SERVICE *service, char **options)
|
||||
router->servers[nservers]->be_chk_tail = CHK_NUM_BACKEND;
|
||||
#endif
|
||||
nservers += 1;
|
||||
server = server->nextdb;
|
||||
sref = sref->next;
|
||||
}
|
||||
router->servers[nservers] = NULL;
|
||||
|
||||
@ -1026,19 +1029,10 @@ static void freeSession(
|
||||
ROUTER_CLIENT_SES* router_cli_ses;
|
||||
ROUTER_INSTANCE* router;
|
||||
int i;
|
||||
backend_ref_t* backend_ref;
|
||||
|
||||
router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session;
|
||||
router = (ROUTER_INSTANCE *)router_instance;
|
||||
backend_ref = router_cli_ses->rses_backend_ref;
|
||||
|
||||
for (i=0; i<router_cli_ses->rses_nbackends; i++)
|
||||
{
|
||||
if (!BREF_IS_IN_USE((&backend_ref[i])))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
spinlock_acquire(&router->lock);
|
||||
|
||||
if (router->connections == router_cli_ses) {
|
||||
@ -1779,6 +1773,33 @@ static void check_create_tmp_table(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client DCB pointer of the router client session.
|
||||
* This routine must be protected by Router client session lock.
|
||||
*
|
||||
* @param rses Router client session pointer
|
||||
*
|
||||
* @return Pointer to client DCB
|
||||
*/
|
||||
static DCB* rses_get_client_dcb(
|
||||
ROUTER_CLIENT_SES* rses)
|
||||
{
|
||||
DCB* dcb = NULL;
|
||||
int i;
|
||||
|
||||
for (i=0; i<rses->rses_nbackends; i++)
|
||||
{
|
||||
if ((dcb = rses->rses_backend_ref[i].bref_dcb) != NULL &&
|
||||
BREF_IS_IN_USE(&rses->rses_backend_ref[i]) &&
|
||||
dcb->session != NULL &&
|
||||
dcb->session->client != NULL)
|
||||
{
|
||||
return dcb->session->client;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main routing entry, this is called with every packet that is
|
||||
* received and has to be forwarded to the backend database.
|
||||
@ -1815,6 +1836,9 @@ static int routeQuery(
|
||||
CHK_CLIENT_RSES(router_cli_ses);
|
||||
|
||||
/**
|
||||
* GWBUF is called "type undefined" when the incoming data isn't parsed
|
||||
* and MySQL packets haven't been extracted to separate buffers.
|
||||
* "Undefined" == "untyped".
|
||||
* Untyped GWBUF means that it can consist of incomplete and/or multiple
|
||||
* MySQL packets.
|
||||
* Read and route found MySQL packets one by one and store potential
|
||||
@ -1833,7 +1857,7 @@ static int routeQuery(
|
||||
{
|
||||
if (GWBUF_LENGTH(tmpbuf) > 0)
|
||||
{
|
||||
DCB* dcb = RSES_CLIENT_DCB(router_cli_ses);
|
||||
DCB* dcb = rses_get_client_dcb(router_cli_ses);
|
||||
|
||||
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, tmpbuf);
|
||||
}
|
||||
@ -2211,7 +2235,7 @@ static bool route_single_stmt(
|
||||
{
|
||||
rlag_max = rses_get_max_replication_lag(rses);
|
||||
}
|
||||
btype = BE_UNDEFINED; /*< target may be master or slave */
|
||||
btype = route_target & TARGET_SLAVE ? BE_SLAVE : BE_MASTER; /*< target may be master or slave */
|
||||
/**
|
||||
* Search backend server by name or replication lag.
|
||||
* If it fails, then try to find valid slave or master.
|
||||
@ -2259,8 +2283,6 @@ static bool route_single_stmt(
|
||||
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE,
|
||||
"Found DCB for slave.")));
|
||||
#endif
|
||||
ss_dassert(get_bref_from_dcb(rses, target_dcb) !=
|
||||
rses->rses_master_ref);
|
||||
ss_dassert(get_root_master_bref(rses) ==
|
||||
rses->rses_master_ref);
|
||||
atomic_add(&inst->stats.n_slave, 1);
|
||||
@ -2339,7 +2361,10 @@ static bool route_single_stmt(
|
||||
*
|
||||
* !!! Note that according to MySQL protocol
|
||||
* there can only be one such non-sescmd stmt at the time.
|
||||
*
|
||||
* It is possible that bref->bref_pending_cmd includes a pending
|
||||
* command if rwsplit is parent or child for another router,
|
||||
* which runs all the same commands.
|
||||
*
|
||||
* If the assertion below traps, pending queries are treated
|
||||
* somehow wrong, or client is sending more queries before
|
||||
* previous is received.
|
||||
@ -2702,8 +2727,9 @@ static void clientReply (
|
||||
|
||||
CHK_GWBUF(bref->bref_pending_cmd);
|
||||
|
||||
if ((ret = bref->bref_dcb->func.write(bref->bref_dcb,
|
||||
gwbuf_clone(bref->bref_pending_cmd))) == 1)
|
||||
if ((ret = bref->bref_dcb->func.write(
|
||||
bref->bref_dcb,
|
||||
gwbuf_clone(bref->bref_pending_cmd))) == 1)
|
||||
{
|
||||
ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance;
|
||||
atomic_add(&inst->stats.n_queries, 1);
|
||||
@ -3083,8 +3109,10 @@ static bool select_connect_backend_servers(
|
||||
*/
|
||||
execute_sescmd_history(&backend_ref[i]);
|
||||
/**
|
||||
* When server fails, this callback
|
||||
* is called.
|
||||
* Here we actually say : When this
|
||||
* type of issue occurs (DCB_REASON_...)
|
||||
* for this particular DCB,
|
||||
* call this function.
|
||||
*/
|
||||
dcb_add_callback(
|
||||
backend_ref[i].bref_dcb,
|
||||
@ -3205,6 +3233,7 @@ static bool select_connect_backend_servers(
|
||||
|
||||
if (slaves_connected == 0 && slaves_found > 0)
|
||||
{
|
||||
#if defined(SS_EXTRA_DEBUG)
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Warning : Couldn't connect to any of the %d "
|
||||
@ -3218,9 +3247,11 @@ static bool select_connect_backend_servers(
|
||||
"slaves. Routing to %s only.",
|
||||
slaves_found,
|
||||
(is_synced_master ? "Galera nodes" : "Master"))));
|
||||
#endif
|
||||
}
|
||||
else if (slaves_found == 0)
|
||||
{
|
||||
#if defined(SS_EXTRA_DEBUG)
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Warning : Couldn't find any slaves from existing "
|
||||
@ -3234,6 +3265,7 @@ static bool select_connect_backend_servers(
|
||||
"%d servers. Routing to %s only.",
|
||||
router_nservers,
|
||||
(is_synced_master ? "Galera nodes" : "Master"))));
|
||||
#endif
|
||||
}
|
||||
else if (slaves_connected < max_nslaves)
|
||||
{
|
||||
@ -3812,7 +3844,7 @@ static bool execute_sescmd_in_backend(
|
||||
tmpbuf = scur->scmd_cur_cmd->my_sescmd_buf;
|
||||
qlen = MYSQL_GET_PACKET_LEN((unsigned char*)tmpbuf->start);
|
||||
memset(data->db,0,MYSQL_DATABASE_MAXLEN+1);
|
||||
if(qlen > 0)
|
||||
if(qlen > 0 && qlen < MYSQL_DATABASE_MAXLEN+1)
|
||||
strncpy(data->db,tmpbuf->start+5,qlen - 1);
|
||||
}
|
||||
/** Fallthrough */
|
||||
@ -4027,8 +4059,16 @@ return_rc:
|
||||
* Suppress redundant OK packets sent by backends.
|
||||
*
|
||||
* The first OK packet is replied to the client.
|
||||
* Return true if succeed, false is returned if router session was closed or
|
||||
* if execute_sescmd_in_backend failed.
|
||||
*
|
||||
* @param router_cli_ses Client's router session pointer
|
||||
* @param querybuf GWBUF including the query to be routed
|
||||
* @param inst Router instance
|
||||
* @param packet_type Type of MySQL packet
|
||||
* @param qtype Query type from query_classifier
|
||||
*
|
||||
* @return True if at least one backend is used and routing succeed to all
|
||||
* backends being used, otherwise false.
|
||||
*
|
||||
*/
|
||||
static bool route_session_write(
|
||||
ROUTER_CLIENT_SES* router_cli_ses,
|
||||
@ -4041,11 +4081,18 @@ static bool route_session_write(
|
||||
rses_property_t* prop;
|
||||
backend_ref_t* backend_ref;
|
||||
int i;
|
||||
int max_nslaves;
|
||||
int nbackends;
|
||||
int nsucc;
|
||||
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Session write, routing to all servers.")));
|
||||
|
||||
/** Maximum number of slaves in this router client session */
|
||||
max_nslaves = rses_get_max_slavecount(router_cli_ses,
|
||||
router_cli_ses->rses_nbackends);
|
||||
nsucc = 0;
|
||||
nbackends = 0;
|
||||
backend_ref = router_cli_ses->rses_backend_ref;
|
||||
|
||||
/**
|
||||
@ -4059,13 +4106,10 @@ static bool route_session_write(
|
||||
packet_type == MYSQL_COM_STMT_CLOSE)
|
||||
{
|
||||
int rc;
|
||||
|
||||
succp = true;
|
||||
|
||||
/** Lock router session */
|
||||
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
succp = false;
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
@ -4087,12 +4131,11 @@ static bool route_session_write(
|
||||
|
||||
if (BREF_IS_IN_USE((&backend_ref[i])))
|
||||
{
|
||||
rc = dcb->func.write(dcb, gwbuf_clone(querybuf));
|
||||
|
||||
if (rc != 1)
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
nbackends += 1;
|
||||
if ((rc = dcb->func.write(dcb, gwbuf_clone(querybuf))) == 1)
|
||||
{
|
||||
nsucc += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
@ -4102,13 +4145,16 @@ static bool route_session_write(
|
||||
/** Lock router session */
|
||||
if (!rses_begin_locked_router_action(router_cli_ses))
|
||||
{
|
||||
succp = false;
|
||||
goto return_succp;
|
||||
}
|
||||
|
||||
if (router_cli_ses->rses_nbackends <= 0)
|
||||
{
|
||||
succp = false;
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Router session doesn't have any backends in use. "
|
||||
"Routing failed. <")));
|
||||
|
||||
goto return_succp;
|
||||
}
|
||||
/**
|
||||
@ -4128,6 +4174,8 @@ static bool route_session_write(
|
||||
{
|
||||
sescmd_cursor_t* scur;
|
||||
|
||||
nbackends += 1;
|
||||
|
||||
if (LOG_IS_ENABLED(LOGFILE_TRACE))
|
||||
{
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
@ -4155,8 +4203,7 @@ static bool route_session_write(
|
||||
*/
|
||||
if (sescmd_cursor_is_active(scur))
|
||||
{
|
||||
succp = true;
|
||||
|
||||
nsucc += 1;
|
||||
LOGIF(LT, (skygw_log_write(
|
||||
LOGFILE_TRACE,
|
||||
"Backend %s:%d already executing sescmd.",
|
||||
@ -4165,10 +4212,12 @@ static bool route_session_write(
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = execute_sescmd_in_backend(&backend_ref[i]);
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
if (execute_sescmd_in_backend(&backend_ref[i]))
|
||||
{
|
||||
nsucc += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Failed to execute session "
|
||||
@ -4178,18 +4227,20 @@ static bool route_session_write(
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
}
|
||||
/** Unlock router session */
|
||||
rses_end_locked_router_action(router_cli_ses);
|
||||
|
||||
return_succp:
|
||||
/**
|
||||
* Routing must succeed to all backends that are used.
|
||||
* There must be at leas one and at most max_nslaves+1 backends.
|
||||
*/
|
||||
succp = (nbackends > 0 && nsucc == nbackends && nbackends <= max_nslaves+1);
|
||||
return succp;
|
||||
}
|
||||
|
||||
|
||||
#if defined(NOT_USED)
|
||||
|
||||
static bool router_option_configured(
|
||||
@ -4308,6 +4359,7 @@ static void handleError (
|
||||
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *)router_session;
|
||||
|
||||
CHK_DCB(backend_dcb);
|
||||
|
||||
/** Don't handle same error twice on same DCB */
|
||||
if (backend_dcb->dcb_errhandle_called)
|
||||
{
|
||||
@ -4332,21 +4384,33 @@ static void handleError (
|
||||
switch (action) {
|
||||
case ERRACT_NEW_CONNECTION:
|
||||
{
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
{
|
||||
*succp = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rses->rses_master_ref->bref_dcb == backend_dcb &&
|
||||
!SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server))
|
||||
SERVER* srv;
|
||||
|
||||
if (!rses_begin_locked_router_action(rses))
|
||||
{
|
||||
/** Master failed, can't recover */
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Master node have failed. "
|
||||
"Session will be closed.")));
|
||||
|
||||
*succp = false;
|
||||
return;
|
||||
}
|
||||
srv = rses->rses_master_ref->bref_backend->backend_server;
|
||||
/**
|
||||
* If master has lost its Master status error can't be
|
||||
* handled so that session could continue.
|
||||
*/
|
||||
if (rses->rses_master_ref->bref_dcb == backend_dcb &&
|
||||
!SERVER_IS_MASTER(srv))
|
||||
{
|
||||
if (!srv->master_err_is_logged)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : server %s:%d lost the "
|
||||
"master status. Readwritesplit "
|
||||
"service can't locate the master. "
|
||||
"Client sessions will be closed.",
|
||||
srv->name,
|
||||
srv->port)));
|
||||
srv->master_err_is_logged = true;
|
||||
}
|
||||
*succp = false;
|
||||
}
|
||||
else
|
||||
@ -4374,7 +4438,7 @@ static void handleError (
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
default:
|
||||
*succp = false;
|
||||
break;
|
||||
}
|
||||
@ -4620,15 +4684,16 @@ static bool have_enough_servers(
|
||||
}
|
||||
if (nservers < min_nsrv)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
double dbgpct = ((double)min_nsrv/(double)router_nsrv)*100.0;
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Unable to start %s service. There are "
|
||||
"too few backend servers configured in "
|
||||
"MaxScale.cnf. Found %d%% when at least %d%% "
|
||||
"MaxScale.cnf. Found %d%% when at least %.0f%% "
|
||||
"would be required.",
|
||||
router->service->name,
|
||||
(*p_rses)->rses_config.rw_max_slave_conn_percent,
|
||||
min_nsrv/(router_nsrv/100))));
|
||||
dbgpct)));
|
||||
}
|
||||
}
|
||||
free(*p_rses);
|
||||
@ -4746,13 +4811,21 @@ static int router_handle_state_switch(
|
||||
bref = (backend_ref_t *)data;
|
||||
CHK_BACKEND_REF(bref);
|
||||
|
||||
srv = bref->bref_backend->backend_server;
|
||||
|
||||
srv = bref->bref_backend->backend_server;
|
||||
|
||||
if (SERVER_IS_RUNNING(srv) && SERVER_IS_IN_CLUSTER(srv))
|
||||
{
|
||||
goto return_rc;
|
||||
}
|
||||
ses = dcb->session;
|
||||
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"%lu [router_handle_state_switch] %s %s:%d in state %s",
|
||||
pthread_self(),
|
||||
STRDCBREASON(reason),
|
||||
srv->name,
|
||||
srv->port,
|
||||
STRSRVSTATUS(srv))));
|
||||
ses = dcb->session;
|
||||
CHK_SESSION(ses);
|
||||
|
||||
rses = (ROUTER_CLIENT_SES *)dcb->session->router_session;
|
||||
|
@ -1,3 +1,3 @@
|
||||
add_test(NAME SimpleHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} simple_tests ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME ComplexHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} complex_tests ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME StackHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} stack_tests ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME SimpleHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/simple_tests ${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_test(NAME ComplexHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/complex_tests ${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_test(NAME StackHintTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit_hints.sh ${CMAKE_CURRENT_BINARY_DIR}/hints.log ${TEST_HOST} ${TEST_PORT_RW_HINT} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR}/stack_tests ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
@ -7,16 +7,16 @@ TMASTER_ID=$4
|
||||
TUSER=$5
|
||||
TPWD=$6
|
||||
TESTINPUT=$7
|
||||
|
||||
TESTFILE=$PWD/$(basename -z $TESTINPUT)
|
||||
if [ $# -lt $(( NARGS - 1 )) ] ;
|
||||
then
|
||||
echo""
|
||||
echo "Wrong number of arguments, gave "$#" but "$(( NARGS - 1 ))" is required"
|
||||
echo ""
|
||||
echo "Usage :"
|
||||
echo " rwsplit_hints.sh <log filename> <host> <port> <master id> <user> <password> <test file>"
|
||||
echo ""
|
||||
exit 1
|
||||
echo""
|
||||
echo "Wrong number of arguments, gave "$#" but "$(( NARGS - 1 ))" is required"
|
||||
echo ""
|
||||
echo "Usage :"
|
||||
echo " rwsplit_hints.sh <log filename> <host> <port> <master id> <user> <password> <test file>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $# -eq $NARGS ]
|
||||
@ -26,20 +26,18 @@ else
|
||||
TDIR=.
|
||||
fi
|
||||
|
||||
TESTINPUT=$TDIR/$TESTINPUT
|
||||
|
||||
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))
|
||||
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]}" >> $TESTFILE.sql
|
||||
i=$((i+1))
|
||||
done < $TESTINPUT
|
||||
|
||||
`$RUNCMD < $TESTINPUT.sql > $TESTINPUT.output`
|
||||
`$RUNCMD < $TESTFILE.sql > $TESTFILE.output`
|
||||
|
||||
x=0
|
||||
crash=1
|
||||
@ -47,32 +45,31 @@ 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
|
||||
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 < $TESTFILE.output
|
||||
|
||||
if [ $crash -eq 1 ]
|
||||
then
|
||||
all_passed=0
|
||||
for ((v=0;v<$i;v++))
|
||||
do
|
||||
echo "${TINPUT[v]} FAILED, nothing was returned">>$TLOG;
|
||||
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
|
||||
|
||||
if [ $# -eq $NARGS ]
|
||||
then
|
||||
echo "Test set: PASSED">>$TLOG;
|
||||
cat $TLOG
|
||||
exit 0
|
||||
else
|
||||
echo "Test set: FAILED">>$TLOG;
|
||||
cat $TLOG
|
||||
exit 1
|
||||
fi
|
||||
|
@ -15,6 +15,7 @@ router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
max_slave_connections=100%
|
||||
|
||||
[RW Split Hint Router]
|
||||
type=service
|
||||
@ -22,6 +23,7 @@ router=readwritesplit
|
||||
servers=server1,server2,server3,server4
|
||||
user=maxuser
|
||||
passwd=maxpwd
|
||||
max_slave_connections=100%
|
||||
filters=Hint
|
||||
|
||||
|
||||
@ -37,6 +39,22 @@ passwd=maxpwd
|
||||
type=filter
|
||||
module=hintfilter
|
||||
|
||||
[recurse3]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Router
|
||||
|
||||
[recurse2]
|
||||
type=filter
|
||||
module=tee
|
||||
service=Read Connection Router
|
||||
|
||||
[recurse1]
|
||||
type=filter
|
||||
module=tee
|
||||
service=RW Split Hint Router
|
||||
|
||||
|
||||
[Debug Interface]
|
||||
type=service
|
||||
router=debugcli
|
||||
|
9
server/test/maxscale_test.h.in
Normal file
9
server/test/maxscale_test.h.in
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef MAXSCALE_TEST_H
|
||||
#define MAXSCALE_TEST_H
|
||||
#define TEST_DIR "${CMAKE_BINARY_DIR}"
|
||||
#define TEST_LOG_DIR "${CMAKE_BINARY_DIR}/log"
|
||||
#define TEST_BIN_DIR "${CMAKE_BINARY_DIR}/bin"
|
||||
#define TEST_MOD_DIR "${CMAKE_BINARY_DIR}/modules"
|
||||
#define TEST_LIB_DIR "${CMAKE_BINARY_DIR}/lib"
|
||||
#define TEST_ETC_DIR "${CMAKE_BINARY_DIR}/etc"
|
||||
#endif
|
Reference in New Issue
Block a user