Merge branch 'develop' into firewall

Conflicts:
	server/modules/filter/test/CMakeLists.txt
This commit is contained in:
Markus Makela
2015-01-13 07:48:43 +02:00
107 changed files with 2718 additions and 800 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

0
server/modules/filter/test/fwfilter/fwtest.input Normal file → Executable file
View File

0
server/modules/filter/test/fwfilter/fwtest2.expected Normal file → Executable file
View File

0
server/modules/filter/test/fwfilter/fwtest2.input Normal file → Executable file
View File

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

View 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

View 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

View 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

View 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 $?

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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