Merge branch 'develop' into plainrouter

This commit is contained in:
Markus Makela
2015-03-24 17:51:41 +02:00
150 changed files with 18236 additions and 1038 deletions

View File

@ -26,14 +26,19 @@ add_library(topfilter SHARED topfilter.c)
target_link_libraries(topfilter log_manager utils)
install(TARGETS topfilter DESTINATION modules)
add_library(fwfilter SHARED fwfilter.c)
target_link_libraries(fwfilter log_manager utils query_classifier)
install(TARGETS fwfilter DESTINATION modules)
add_library(dbfwfilter SHARED dbfwfilter.c)
target_link_libraries(dbfwfilter log_manager utils query_classifier)
install(TARGETS dbfwfilter DESTINATION modules)
add_library(namedserverfilter SHARED namedserverfilter.c)
target_link_libraries(namedserverfilter log_manager utils)
install(TARGETS namedserverfilter DESTINATION modules)
if(BUILD_SLAVELAG)
add_library(slavelag SHARED slavelag.c)
target_link_libraries(slavelag log_manager utils query_classifier)
install(TARGETS slavelag DESTINATION modules)
endif()
add_subdirectory(hint)

View File

@ -1300,6 +1300,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue
char *ptr,*where,*msg = NULL;
char emsg[512];
int qlen;
unsigned char* memptr = (unsigned char*)queue->start;
bool is_sql, is_real, matches;
skygw_query_op_t optype = QUERY_OP_UNDEFINED;
STRLINK* strln = NULL;
@ -1312,15 +1313,15 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue
tm_now = localtime(&time_now);
matches = false;
is_sql = modutil_is_SQL(queue);
is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue);
if(is_sql){
if(!query_is_parsed(queue)){
parse_query(queue);
}
optype = query_classifier_get_operation(queue);
modutil_extract_SQL(queue, &ptr, &qlen);
is_real = skygw_is_real_query(queue);
qlen = gw_mysql_get_byte3(memptr) - 1;
}
if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){
@ -1547,18 +1548,20 @@ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu
bool is_sql, rval = false;
int qlen;
char *fullquery = NULL,*ptr;
unsigned char* memptr = (unsigned char*)queue->start;
RULELIST* rulelist;
is_sql = modutil_is_SQL(queue);
is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue);
if(is_sql){
if(!query_is_parsed(queue)){
parse_query(queue);
}
modutil_extract_SQL(queue, &ptr, &qlen);
fullquery = malloc((qlen + 1) * sizeof(char));
memcpy(fullquery,ptr,qlen);
memset(fullquery + qlen,0,1);
qlen = gw_mysql_get_byte3(memptr);
qlen = qlen < 0xffffff ? qlen : 0xffffff;
fullquery = malloc((qlen) * sizeof(char));
memcpy(fullquery,memptr + 5,qlen - 1);
memset(fullquery + qlen - 1,0,1);
}
if((rulelist = user->rules_or) == NULL)
@ -1598,21 +1601,22 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu
{
bool is_sql, rval = true;
int qlen;
unsigned char* memptr = (unsigned char*)queue->start;
char *fullquery = NULL,*ptr;
RULELIST* rulelist;
is_sql = modutil_is_SQL(queue);
is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue);
if(is_sql){
if(!query_is_parsed(queue)){
parse_query(queue);
}
modutil_extract_SQL(queue, &ptr, &qlen);
fullquery = malloc((qlen + 1) * sizeof(char));
memcpy(fullquery,ptr,qlen);
memset(fullquery + qlen,0,1);
qlen = gw_mysql_get_byte3(memptr);
qlen = qlen < 0xffffff ? qlen : 0xffffff;
fullquery = malloc((qlen) * sizeof(char));
memcpy(fullquery,memptr + 5,qlen - 1);
memset(fullquery + qlen - 1,0,1);
}
if(strict_all)

View File

@ -99,7 +99,7 @@ static FILTER_OBJECT MyObject = {
*/
typedef struct {
int sessions; /* The count of sessions */
char *filebase; /* The filemane base */
char *filebase; /* The filename base */
char *source; /* The source of the client connection */
char *userName; /* The user name to filter on */
char *match; /* Optional text to match against */
@ -424,8 +424,8 @@ struct timeval tv;
"%02d:%02d:%02d.%-3d %d/%02d/%d, ",
t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000),
t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year);
fwrite(ptr, sizeof(char), length, my_session->fp);
fwrite("\n", sizeof(char), 1, my_session->fp);
fprintf(my_session->fp,"%s\n",ptr);
}
free(ptr);
}

View File

@ -0,0 +1,403 @@
/*
* This file is distributed as part of MaxScale by MariaDB Corporation. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2014
*/
#include <stdio.h>
#include <filter.h>
#include <modinfo.h>
#include <modutil.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <string.h>
#include <hint.h>
#include <query_classifier.h>
#include <regex.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
/**
* @file slavelag.c - a very simple filter designed to send queries to the
* master server after data modification has occurred. This is done to prevent
* replication lag affecting the outcome of a select query.
*
* @verbatim
*
* Two optional parameters that define the behavior after a data modifying query
* is executed:
*
* count=<number of queries> Queries to route to master after data modification.
* time=<time period> Seconds to wait before queries are routed to slaves.
* match=<regex> Regex for matching
* ignore=<regex> Regex for ignoring
*
* The filter also has two options: @c case, which makes the regex case-sensitive, and @c ignorecase, which does the opposite.
* Date Who Description
* 03/03/2015 Markus Mäkelä Written for demonstrative purposes
* @endverbatim
*/
MODULE_INFO info = {
MODULE_API_FILTER,
MODULE_GA,
FILTER_VERSION,
"A routing hint filter that send queries to the master after data modification"
};
static char *version_str = "V1.1.0";
static FILTER *createInstance(char **options, FILTER_PARAMETER **params);
static void *newSession(FILTER *instance, SESSION *session);
static void closeSession(FILTER *instance, void *session);
static void freeSession(FILTER *instance, void *session);
static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream);
static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue);
static void diagnostic(FILTER *instance, void *fsession, DCB *dcb);
static FILTER_OBJECT MyObject = {
createInstance,
newSession,
closeSession,
freeSession,
setDownstream,
NULL, // No Upstream requirement
routeQuery,
NULL,
diagnostic,
};
struct LAGSTATS{
int n_add_count; /*< No. of statements diverted based on count */
int n_add_time; /*< No. of statements diverted based on time */
int n_modified; /*< No. of statements not diverted */
};
/**
* Instance structure
*/
typedef struct {
char *match; /* Regular expression to match */
char *nomatch; /* Regular expression to ignore */
int time; /*< The number of seconds to wait before routing queries
* to slave servers after a data modification operation
* is done. */
int count; /*< Number of hints to add after each operation
* that modifies data. */
struct LAGSTATS stats;
regex_t re; /* Compiled regex text of match */
regex_t nore; /* Compiled regex text of ignore */
} LAG_INSTANCE;
/**
* The session structure for this filter
*/
typedef struct {
DOWNSTREAM down; /*< The downstream filter */
int hints_left; /*< Number of hints left to add to queries*/
time_t last_modification; /*< Time of the last modifying operation */
int active; /*< Is filter active */
} LAG_SESSION;
/**
* Implementation of the mandatory version entry point
*
* @return version string of the module
*/
char *
version()
{
return version_str;
}
/**
* The module initialization routine, called when the module
* is first loaded.
*/
void
ModuleInit()
{
}
/**
* The module entry point routine. It is this routine that
* must populate the structure that is referred to as the
* "module object", this is a structure with the set of
* external entry points for this module.
*
* @return The module object
*/
FILTER_OBJECT *
GetModuleObject()
{
return &MyObject;
}
/**
* Create an instance of the filter for a particular service
* within MaxScale.
*
* @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
*
* @return The instance data for this new instance
*/
static FILTER *
createInstance(char **options, FILTER_PARAMETER **params)
{
LAG_INSTANCE *my_instance;
int i,cflags = 0;
if ((my_instance = calloc(1, sizeof(LAG_INSTANCE))) != NULL)
{
my_instance->count = 0;
my_instance->time = 0;
my_instance->stats.n_add_count = 0;
my_instance->stats.n_add_time = 0;
my_instance->stats.n_modified = 0;
my_instance->match = NULL;
my_instance->nomatch = NULL;
for (i = 0; params && params[i]; i++)
{
if (!strcmp(params[i]->name, "count"))
my_instance->count = atoi(params[i]->value);
else if (!strcmp(params[i]->name, "time"))
my_instance->time = atoi(params[i]->value);
else if (!strcmp(params[i]->name, "match"))
my_instance->match = strdup(params[i]->value);
else if (!strcmp(params[i]->name, "ignore"))
my_instance->nomatch = strdup(params[i]->value);
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"lagfilter: Unexpected parameter '%s'.\n",
params[i]->name)));
}
}
if (options)
{
for (i = 0; options[i]; i++)
{
if (!strcasecmp(options[i], "ignorecase"))
{
cflags |= REG_ICASE;
}
else if (!strcasecmp(options[i], "case"))
{
cflags &= ~REG_ICASE;
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"lagfilter: unsupported option '%s'.",
options[i])));
}
}
}
if(my_instance->match)
{
if(regcomp(&my_instance->re,my_instance->match,cflags))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"lagfilter: Failed to compile regex '%s'.",
my_instance->match)));
}
}
if(my_instance->nomatch)
{
if(regcomp(&my_instance->nore,my_instance->nomatch,cflags))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"lagfilter: Failed to compile regex '%s'.",
my_instance->nomatch)));
}
}
}
return (FILTER *)my_instance;
}
/**
* Associate a new session with this instance of the filter.
*
* @param instance The filter instance data
* @param session The session itself
* @return Session specific data for this session
*/
static void *
newSession(FILTER *instance, SESSION *session)
{
LAG_INSTANCE *my_instance = (LAG_INSTANCE *)instance;
LAG_SESSION *my_session;
if ((my_session = malloc(sizeof(LAG_SESSION))) != NULL)
{
my_session->active = 1;
my_session->hints_left = 0;
my_session->last_modification = 0;
}
return my_session;
}
/**
* Close a session with the filter, this is the mechanism
* by which a filter may cleanup data structure etc.
*
* @param instance The filter instance data
* @param session The session being closed
*/
static void
closeSession(FILTER *instance, void *session)
{
}
/**
* Free the memory associated with this filter session.
*
* @param instance The filter instance data
* @param session The session being closed
*/
static void
freeSession(FILTER *instance, void *session)
{
free(session);
return;
}
/**
* Set the downstream component for this filter.
*
* @param instance The filter instance data
* @param session The session being closed
* @param downstream The downstream filter or router
*/
static void
setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
{
LAG_SESSION *my_session = (LAG_SESSION *)session;
my_session->down = *downstream;
}
/**
* The routeQuery entry point. This is passed the query buffer
* to which the filter should be applied. Once applied the
* query should normally be passed to the downstream component
* (filter or router) in the filter chain.
*
* If the regular expressed configured in the match parameter of the
* filter definition matches the SQL text then add the hint
* "Route to named server" with the name defined in the server parameter
*
* @param instance The filter instance data
* @param session The filter session
* @param queue The query data
*/
static int
routeQuery(FILTER *instance, void *session, GWBUF *queue)
{
LAG_INSTANCE *my_instance = (LAG_INSTANCE *)instance;
LAG_SESSION *my_session = (LAG_SESSION *)session;
char *sql;
time_t now = time(NULL);
if (modutil_is_SQL(queue))
{
if (queue->next != NULL)
{
queue = gwbuf_make_contiguous(queue);
}
if(!query_is_parsed(queue))
{
parse_query(queue);
}
if(query_classifier_get_operation(queue) & (QUERY_OP_DELETE|QUERY_OP_INSERT|QUERY_OP_UPDATE))
{
if((sql = modutil_get_SQL(queue)) != NULL)
{
if(my_instance->nomatch == NULL||(my_instance->nomatch && regexec(&my_instance->nore,sql,0,NULL,0) != 0))
{
if(my_instance->match == NULL||
(my_instance->match && regexec(&my_instance->re,sql,0,NULL,0) == 0))
{
my_session->hints_left = my_instance->count;
my_session->last_modification = now;
my_instance->stats.n_modified++;
}
}
free(sql);
}
}
else if(my_session->hints_left > 0)
{
queue->hint = hint_create_route(queue->hint,
HINT_ROUTE_TO_MASTER,
NULL);
my_session->hints_left--;
my_instance->stats.n_add_count++;
}
else if(difftime(now,my_session->last_modification) < my_instance->time)
{
queue->hint = hint_create_route(queue->hint,
HINT_ROUTE_TO_MASTER,
NULL);
my_instance->stats.n_add_time++;
}
}
return my_session->down.routeQuery(my_session->down.instance,
my_session->down.session, queue);
}
/**
* Diagnostics routine
*
* If fsession is NULL then print diagnostics on the filter
* instance as a whole, otherwise print diagnostics for the
* particular session.
*
* @param instance The filter instance
* @param fsession Filter session, may be NULL
* @param dcb The DCB for diagnostic output
*/
static void
diagnostic(FILTER *instance, void *fsession, DCB *dcb)
{
LAG_INSTANCE *my_instance = (LAG_INSTANCE *)instance;
LAG_SESSION *my_session = (LAG_SESSION *)fsession;
dcb_printf(dcb, "Configuration:\n\tCount: %d\n",
my_instance->count);
dcb_printf(dcb, "\tTime: %d seconds\n\n",
my_instance->time);
dcb_printf(dcb, "Statistics:\n");
dcb_printf(dcb, "\tNo. of data modifications: %d\n",
my_instance->stats.n_modified);
dcb_printf(dcb, "\tNo. of hints added based on count: %d\n",
my_instance->stats.n_add_count);
dcb_printf(dcb, "\tNo. of hints added based on time: %d\n",
my_instance->stats.n_add_time);
}

View File

@ -72,12 +72,17 @@
#define MYSQL_COM_STMT_SEND_LONG_DATA 0x18
#define MYSQL_COM_STMT_CLOSE 0x19
#define MYSQL_COM_STMT_RESET 0x1a
#define MYSQL_COM_CONNECT 0x1b
#define REPLY_TIMEOUT_SECOND 5
#define REPLY_TIMEOUT_MILLISECOND 1
#define PARENT 0
#define CHILD 1
#ifdef SS_DEBUG
static int debug_seq = 0;
#endif
static unsigned char required_packets[] = {
MYSQL_COM_QUIT,
MYSQL_COM_INITDB,
@ -88,6 +93,7 @@ static unsigned char required_packets[] = {
MYSQL_COM_STMT_SEND_LONG_DATA,
MYSQL_COM_STMT_CLOSE,
MYSQL_COM_STMT_RESET,
MYSQL_COM_CONNECT,
0 };
/** Defined in log_manager.cc */
@ -158,19 +164,28 @@ typedef struct {
FILTER_DEF* dummy_filterdef;
int active; /* filter is active? */
bool use_ok;
int client_multistatement;
bool multipacket[2];
unsigned char command;
bool waiting[2]; /* if the client is waiting for a reply */
int eof[2];
int replies[2]; /* Number of queries received */
int reply_packets[2]; /* Number of OK, ERR, LOCAL_INFILE_REQUEST or RESULT_SET packets received */
DCB *branch_dcb; /* Client DCB for "branch" service */
SESSION *branch_session;/* The branch service session */
TEE_INSTANCE *instance;
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 */
GWBUF* tee_partials[2];
GWBUF* querybuf;
SPINLOCK tee_lock;
DCB* client_dcb;
int statements; /*< Number of statements in the query,
* used to identify and track multi-statement
* queries and that both the parent and the child
* branch are in sync. */
#ifdef SS_DEBUG
long d_id;
#endif
@ -178,7 +193,8 @@ typedef struct {
typedef struct orphan_session_tt
{
SESSION* session;
SESSION* session; /*< The child branch session whose parent was freed before
* the child session was in a suitable state. */
struct orphan_session_tt* next;
}orphan_session_t;
@ -192,6 +208,7 @@ 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);
int internal_route(DCB* dcb);
static int hkfn(
void* key)
@ -328,7 +345,6 @@ void
ModuleInit()
{
spinlock_init(&orphanLock);
//hktask_add("tee orphan cleanup",orphan_free,NULL,15);
#ifdef SS_DEBUG
spinlock_init(&debug_lock);
#endif
@ -493,6 +509,12 @@ char *remote, *userName;
{
my_session->active = 1;
my_session->residual = 0;
my_session->statements = 0;
my_session->tee_replybuf = NULL;
my_session->client_dcb = session->client;
my_session->instance = my_instance;
my_session->client_multistatement = false;
spinlock_init(&my_session->tee_lock);
if (my_instance->source &&
(remote = session_get_remote(session)) != NULL)
@ -538,7 +560,7 @@ char *remote, *userName;
goto retblock;
}
if((dummy = filter_alloc("tee_dummy","tee_dummy")) == NULL)
{
dcb_close(dcb);
@ -630,9 +652,12 @@ TEE_SESSION *my_session = (TEE_SESSION *)session;
ROUTER_OBJECT *router;
void *router_instance, *rsession;
SESSION *bsession;
#ifdef SS_DEBUG
skygw_log_write(LOGFILE_TRACE,"Tee close: %d", atomic_add(&debug_seq,1));
#endif
if (my_session->active)
{
if ((bsession = my_session->branch_session) != NULL)
{
CHK_SESSION(bsession);
@ -654,8 +679,21 @@ SESSION *bsession;
* a side effect of closing the client DCB of the
* session.
*/
my_session->active = 0;
if(my_session->waiting[PARENT])
{
if(my_session->command != 0x01 &&
my_session->client_dcb &&
my_session->client_dcb->state == DCB_STATE_POLLING)
{
skygw_log_write(LOGFILE_TRACE,"Tee session closed mid-query.");
GWBUF* errbuf = modutil_create_mysql_err_msg(1,0,1,"00000","Session closed.");
my_session->client_dcb->func.write(my_session->client_dcb,errbuf);
}
}
my_session->active = 0;
}
}
@ -671,6 +709,9 @@ freeSession(FILTER *instance, void *session)
TEE_SESSION *my_session = (TEE_SESSION *)session;
SESSION* ses = my_session->branch_session;
session_state_t state;
#ifdef SS_DEBUG
skygw_log_write(LOGFILE_TRACE,"Tee free: %d", atomic_add(&debug_seq,1));
#endif
if (ses != NULL)
{
state = ses->state;
@ -777,6 +818,24 @@ char *ptr;
int length, rval, residual = 0;
GWBUF *clone = NULL;
unsigned char command = *((unsigned char*)queue->start + 4);
#ifdef SS_DEBUG
skygw_log_write(LOGFILE_TRACE,"Tee routeQuery: %d : %s",
atomic_add(&debug_seq,1),
((char*)queue->start + 5));
#endif
spinlock_acquire(&my_session->tee_lock);
if(!my_session->active)
{
skygw_log_write(LOGFILE_TRACE, "Tee: Received a reply when the session was closed.");
gwbuf_free(queue);
rval = 0;
goto retblock;
}
if (my_session->branch_session &&
my_session->branch_session->state == SESSION_STATE_ROUTER_READY)
{
@ -813,12 +872,15 @@ unsigned char command = *((unsigned char*)queue->start + 4);
clone = gwbuf_clone_all(queue);
}
}
spinlock_release(&my_session->tee_lock);
/* Pass the query downstream */
ss_dassert(my_session->tee_replybuf == NULL);
switch(command)
{
case 0x1b:
my_session->client_multistatement = *((unsigned char*) queue->start + 5);
case 0x03:
case 0x16:
case 0x17:
@ -832,8 +894,10 @@ unsigned char command = *((unsigned char*)queue->start + 4);
}
memset(my_session->replies,0,2*sizeof(int));
memset(my_session->reply_packets,0,2*sizeof(int));
memset(my_session->eof,0,2*sizeof(int));
memset(my_session->waiting,1,2*sizeof(bool));
my_session->statements = modutil_count_statements(queue);
my_session->command = command;
#ifdef SS_DEBUG
spinlock_acquire(&debug_lock);
@ -850,6 +914,16 @@ unsigned char command = *((unsigned char*)queue->start + 4);
}
spinlock_release(&debug_lock);
#endif
spinlock_acquire(&my_session->tee_lock);
if(!my_session->active ||
my_session->branch_session == NULL ||
my_session->branch_session->state != SESSION_STATE_ROUTER_READY)
{
rval = 0;
my_session->active = 0;
goto retblock;
}
rval = my_session->down.routeQuery(my_session->down.instance,
my_session->down.session,
queue);
@ -865,9 +939,10 @@ unsigned char command = *((unsigned char*)queue->start + 4);
{
/** Close tee session */
my_session->active = 0;
rval = 0;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Closed tee filter session.")));
"Closed tee filter session: Child session in invalid state.")));
gwbuf_free(clone);
}
}
@ -877,15 +952,84 @@ unsigned char command = *((unsigned char*)queue->start + 4);
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Closed tee filter session.")));
"Closed tee filter session: Child session is NULL.")));
my_session->active = 0;
rval = 0;
}
my_session->n_rejected++;
}
retblock:
spinlock_release(&my_session->tee_lock);
return rval;
}
int count_replies(GWBUF* buffer)
{
unsigned char* ptr = (unsigned char*)buffer->start;
unsigned char* end = (unsigned char*) buffer->end;
int pktlen, eof = 0;
int replies = 0;
while(ptr < end)
{
pktlen = MYSQL_GET_PACKET_LEN(ptr) + 4;
if(PTR_IS_OK(ptr) || PTR_IS_ERR(ptr) || PTR_IS_LOCAL_INFILE(ptr))
{
replies++;
ptr += pktlen;
}
else
{
while(ptr < end && eof < 2)
{
pktlen = MYSQL_GET_PACKET_LEN(ptr) + 4;
if(PTR_IS_EOF(ptr) || PTR_IS_ERR(ptr)) eof++;
ptr += pktlen;
}
if(eof == 2) replies++;
eof = 0;
}
}
return replies;
}
int lenenc_length(uint8_t* ptr)
{
char val = *ptr;
if(val < 251)
return 1;
else if(val == 0xfc)
return 3;
else if(val == 0xfd)
return 4;
else
return 9;
}
uint16_t get_response_flags(uint8_t* datastart, bool ok_packet)
{
uint8_t* ptr = datastart;
uint16_t rval = 0;
int pktlen = gw_mysql_get_byte3(ptr);
ptr += 4;
if(ok_packet)
{
ptr += lenenc_length(ptr);
ptr += lenenc_length(ptr);
memcpy(&rval,ptr,sizeof(uint8_t)*2);
}
else
{
/** This is an EOF packet*/
ptr += 2;
memcpy(&rval,ptr,sizeof(uint8_t)*2);
}
return rval;
}
/**
* The clientReply entry point. This is passed the response buffer
* to which the filter should be applied. Once processed the
@ -904,17 +1048,49 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
bool route = false,mpkt;
GWBUF *complete = NULL;
unsigned char *ptr;
uint16_t flags = 0;
int min_eof = my_session->command != 0x04 ? 2 : 1;
int more_results = 0;
#ifdef SS_DEBUG
ptr = (unsigned char*) reply->start;
skygw_log_write(LOGFILE_TRACE,"Tee clientReply [%s] [%s] [%s]: %d",
instance ? "parent":"child",
my_session->active ? "open" : "closed",
PTR_IS_ERR(ptr) ? "ERR" : PTR_IS_OK(ptr) ? "OK" : "RSET",
atomic_add(&debug_seq,1));
#endif
spinlock_acquire(&my_session->tee_lock);
ss_dassert(my_session->active);
if(!my_session->active)
{
gwbuf_free(reply);
rc = 0;
if(my_session->waiting[PARENT])
{
GWBUF* errbuf = modutil_create_mysql_err_msg(1,0,1,"0000","Session closed.");
my_session->waiting[PARENT] = false;
my_session->up.clientReply (my_session->up.instance,
my_session->up.session,
errbuf);
}
goto retblock;
}
branch = instance == NULL ? CHILD : PARENT;
my_session->tee_partials[branch] = gwbuf_append(my_session->tee_partials[branch], reply);
my_session->tee_partials[branch] = gwbuf_make_contiguous(my_session->tee_partials[branch]);
complete = modutil_get_complete_packets(&my_session->tee_partials[branch]);
if(complete == NULL)
{
/** Incomplete packet */
skygw_log_write(LOGFILE_DEBUG,"tee.c: Incomplete packet, "
"waiting for a complete packet before forwarding.");
rc = 1;
goto retblock;
}
complete = gwbuf_make_contiguous(complete);
if(my_session->tee_partials[branch] &&
@ -936,26 +1112,28 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
{
my_session->waiting[branch] = false;
my_session->multipacket[branch] = false;
if(PTR_IS_OK(ptr))
{
flags = get_response_flags(ptr,true);
more_results = (flags & 0x08) && my_session->client_multistatement;
}
}
#ifdef SS_DEBUG
else
{
ss_dassert(PTR_IS_RESULTSET(ptr));
skygw_log_write_flush(LOGFILE_DEBUG,"tee.c: [%d] Waiting for a result set from %s session.",
my_session->d_id,
branch == PARENT?"parent":"child");
}
ss_dassert(PTR_IS_ERR(ptr) || PTR_IS_LOCAL_INFILE(ptr)||
PTR_IS_OK(ptr) || my_session->waiting[branch] ||
!my_session->multipacket);
#endif
}
if(my_session->waiting[branch])
{
eof = modutil_count_signal_packets(complete,my_session->use_ok,my_session->eof[branch] > 0);
eof = modutil_count_signal_packets(complete,my_session->use_ok,my_session->eof[branch] > 0,&more_results);
more_results &= my_session->client_multistatement;
my_session->eof[branch] += eof;
if(my_session->eof[branch] >= min_eof)
{
#ifdef SS_DEBUG
@ -963,27 +1141,27 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
my_session->d_id,
branch == PARENT?"parent":"child");
#endif
ss_dassert(my_session->eof[branch] < 3)
my_session->waiting[branch] = false;
my_session->waiting[branch] = more_results;
if(more_results)
{
my_session->eof[branch] = 0;
}
}
}
if(branch == PARENT)
{
ss_dassert(my_session->tee_replybuf == NULL);
my_session->tee_replybuf = complete;
my_session->tee_replybuf = gwbuf_append(my_session->tee_replybuf,complete);
}
else
{
if(complete)
gwbuf_free(complete);
gwbuf_free(complete);
}
my_session->replies[branch]++;
rc = 1;
mpkt = my_session->multipacket[PARENT] || my_session->multipacket[CHILD];
if(my_session->tee_replybuf != NULL)
{
@ -1002,16 +1180,10 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
if(my_session->waiting[PARENT])
{
route = true;
#ifdef SS_DEBUG
ss_dassert(my_session->replies[PARENT] < 2 ||
modutil_count_signal_packets(my_session->tee_replybuf,
my_session->use_ok,
my_session->eof[PARENT]) == 0);
skygw_log_write_flush(LOGFILE_DEBUG,"tee.c:[%d] Routing partial response set.",my_session->d_id);
#endif
}
else if(my_session->eof[PARENT] == min_eof &&
my_session->eof[CHILD] == min_eof)
else if(my_session->eof[PARENT] >= min_eof &&
my_session->eof[CHILD] >= min_eof)
{
route = true;
#ifdef SS_DEBUG
@ -1026,7 +1198,7 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
skygw_log_write_flush(LOGFILE_DEBUG,"tee.c:[%d] Routing single packet response.",my_session->d_id);
#endif
route = true;
}
}
}
if(route)
@ -1049,10 +1221,11 @@ clientReply (FILTER* instance, void *session, GWBUF *reply)
my_session->tee_replybuf);
my_session->tee_replybuf = NULL;
}
retblock:
spinlock_release(&my_session->tee_lock);
return rc;
}
}
/**
* Diagnostics routine
*
@ -1163,3 +1336,13 @@ int detect_loops(TEE_INSTANCE *instance,HASHTABLE* ht, SERVICE* service)
return false;
}
int internal_route(DCB* dcb)
{
GWBUF* buffer = dcb->dcb_readqueue;
/** This was set in the newSession function*/
TEE_SESSION* session = dcb->data;
return routeQuery((FILTER*)session->instance,session,buffer);
}

View File

@ -1,4 +1,4 @@
[Firewall]
type=filter
module=fwfilter
module=dbfwfilter
rules=@CMAKE_CURRENT_SOURCE_DIR@/rules

View File

@ -29,7 +29,7 @@ function execute_test()
function reload_conf()
{
$BINDIR/bin/maxadmin --user=admin --password=skysql reload config
$BINDIR/bin/maxadmin --user=admin --password=mariadb reload config
if [[ $? -ne 0 ]]
then
echo "Test failed: maxadmin returned a non-zero value."
@ -55,7 +55,7 @@ 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
$BINDIR/bin/maxadmin --user=admin --password=mariadb flush logs
mv $CONF $OLDCONF
cp $TEST1 $CONF

View File

@ -0,0 +1,132 @@
#ifndef _MAXINFO_H
#define _MAXINFO_H
/*
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2013-2014
*/
#include <service.h>
#include <session.h>
#include <spinlock.h>
/**
* @file maxinfo.h The MaxScale information schema provider
*
* @verbatim
* Revision History
*
* Date Who Description
* 16/02/15 Mark Riddoch Initial implementation
*
* @endverbatim
*/
struct maxinfo_session;
/**
* The INFO_INSTANCE structure. There is one instane of the maxinfo "router" for
* each service that uses the MaxScale information schema.
*/
typedef struct maxinfo_instance {
SPINLOCK lock; /*< The instance spinlock */
SERVICE *service; /*< The debug cli service */
struct maxinfo_session
*sessions; /*< Linked list of sessions within this instance */
struct maxinfo_instance
*next; /*< The next pointer for the list of instances */
} INFO_INSTANCE;
/**
* The INFO_SESSION structure. As INFO_SESSION is created for each user that logs into
* the MaxScale information schema.
*/
typedef struct maxinfo_session {
SESSION *session; /*< The MaxScale session */
DCB *dcb; /*< DCB of the client side */
GWBUF *queue; /*< Queue for building contiguous requests */
struct maxinfo_session
*next; /*< The next pointer for the list of sessions */
} INFO_SESSION;
/**
* The operators that can be in the parse tree
*/
typedef enum
{
MAXOP_SHOW,
MAXOP_SELECT,
MAXOP_TABLE,
MAXOP_COLUMNS,
MAXOP_ALL_COLUMNS,
MAXOP_LITERAL,
MAXOP_PREDICATE,
MAXOP_LIKE,
MAXOP_EQUAL
} MAXINFO_OPERATOR;
/**
* The Parse tree nodes for the MaxInfo parser
*/
typedef struct maxinfo_tree {
MAXINFO_OPERATOR op; /*< The operator */
char *value; /*< The value */
struct maxinfo_tree *left; /*< The left hand side of the operator */
struct maxinfo_tree *right; /*< The right hand side of the operator */
} MAXINFO_TREE;
#define MYSQL_COMMAND(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4))
/**
* MySQL protocol OpCodes needed for replication
*/
#define COM_QUIT 0x01
#define COM_QUERY 0x03
#define COM_STATISTICS 0x09
#define COM_PING 0x0e
/**
* Token values for the tokeniser used by the parser for maxinfo
*/
#define LT_STRING 1
#define LT_SHOW 2
#define LT_LIKE 3
#define LT_SELECT 4
#define LT_EQUAL 5
#define LT_COMMA 6
#define LT_FROM 7
#define LT_STAR 8
#define LT_VARIABLE 9
/**
* Possible parse errors
*/
typedef enum {
PARSE_NOERROR,
PARSE_MALFORMED_SHOW,
PARSE_EXPECTED_LIKE,
PARSE_SYNTAX_ERROR
} PARSE_ERROR;
extern MAXINFO_TREE *maxinfo_parse(char *, PARSE_ERROR *);
extern void maxinfo_execute(DCB *, MAXINFO_TREE *);
extern void maxinfo_send_error(DCB *, int, char *);
extern void maxinfo_send_parse_error(DCB *, char *, PARSE_ERROR);
extern void maxinfo_send_error(DCB *, int, char *);
extern RESULTSET *maxinfo_variables();
extern RESULTSET *maxinfo_status();
#endif

View File

@ -31,7 +31,7 @@
*/
#include <dcb.h>
#include <spinlock.h>
#include <housekeeper.h>
/**
* The telnetd specific protocol structure to put in the DCB.
*/

View File

@ -61,6 +61,7 @@
#include <users.h>
#include <dbusers.h>
#include <version.h>
#include <housekeeper.h>
#define GW_MYSQL_VERSION "MaxScale " MAXSCALE_VERSION
#define GW_MYSQL_LOOP_TIMEOUT 300000000
@ -304,6 +305,7 @@ typedef struct {
#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11]))
#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff)
#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01)
#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02)
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
#define MYSQL_GET_NATTR(payload) ((int)payload[4])

View File

@ -24,7 +24,7 @@
* @verbatim
* Revision History
*
* See GitHub https://github.com/skysql/MaxScale
* See GitHub https://github.com/mariadb-corporation/MaxScale
*
* @endverbatim
*/
@ -246,7 +246,8 @@ typedef struct rwsplit_config_st {
int rw_max_slave_conn_count;
select_criteria_t rw_slave_select_criteria;
int rw_max_slave_replication_lag;
target_t rw_use_sql_variables_in;
target_t rw_use_sql_variables_in;
int rw_max_sescmd_history_size;
} rwsplit_config_t;
@ -285,9 +286,11 @@ struct router_client_session {
backend_ref_t* rses_backend_ref; /*< Pointer to backend reference array */
rwsplit_config_t rses_config; /*< copied config info from router instance */
int rses_nbackends;
int rses_nsescmd; /*< Number of executed session commands */
int rses_capabilities; /*< input type, for example */
bool rses_autocommit_enabled;
bool rses_transaction_active;
DCB* client_dcb;
#if defined(PREP_STMT_CACHING)
HASHTABLE* rses_prep_stmt[2];
#endif

View File

@ -0,0 +1,313 @@
#ifndef _SCHEMAROUTER_H
#define _SCHEMAROUTER_H
/*
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2013-2014
*/
/**
* @file schemarouter.h - The schemarouter router module header file
*
* @verbatim
* Revision History
*
* See GitHub https://github.com/mariadb-corporation/MaxScale
*
* @endverbatim
*/
#include <dcb.h>
#include <hashtable.h>
#include <mysql_client_server_protocol.h>
/**
* Bitmask values for the router session's initialization. These values are used
* to prevent responses from internal commands being forwarded to the client.
*/
typedef enum init_mask
{
INIT_READY = 0x0,
INIT_MAPPING = 0x1,
INIT_USE_DB = 0x02,
INIT_UNINT = 0x04
} init_mask_t;
/**
* The state of the backend server reference
*/
typedef enum bref_state {
BREF_IN_USE = 0x01,
BREF_WAITING_RESULT = 0x02, /*< for session commands only */
BREF_QUERY_ACTIVE = 0x04, /*< for other queries */
BREF_CLOSED = 0x08,
BREF_DB_MAPPED = 0x10
} bref_state_t;
#define BREF_IS_NOT_USED(s) ((s)->bref_state & ~BREF_IN_USE)
#define BREF_IS_IN_USE(s) ((s)->bref_state & BREF_IN_USE)
#define BREF_IS_WAITING_RESULT(s) ((s)->bref_num_result_wait > 0)
#define BREF_IS_QUERY_ACTIVE(s) ((s)->bref_state & BREF_QUERY_ACTIVE)
#define BREF_IS_CLOSED(s) ((s)->bref_state & BREF_CLOSED)
#define BREF_IS_MAPPED(s) ((s)->bref_mapped)
/**
* The type of the backend server
*/
typedef enum backend_type_t {
BE_UNDEFINED=-1,
BE_MASTER,
BE_JOINED = BE_MASTER,
BE_SLAVE,
BE_COUNT
} backend_type_t;
struct router_instance;
/**
* Route target types
*/
typedef enum {
TARGET_UNDEFINED = 0x00,
TARGET_MASTER = 0x01,
TARGET_SLAVE = 0x02,
TARGET_NAMED_SERVER = 0x04,
TARGET_ALL = 0x08,
TARGET_RLAG_MAX = 0x10,
TARGET_ANY = 0x20
} route_target_t;
#define TARGET_IS_UNDEFINED(t) (t == TARGET_UNDEFINED)
#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER)
#define TARGET_IS_ALL(t) (t & TARGET_ALL)
#define TARGET_IS_ANY(t) (t & TARGET_ANY)
typedef struct rses_property_st rses_property_t;
typedef struct router_client_session ROUTER_CLIENT_SES;
/**
* Router session properties
*/
typedef enum rses_property_type_t {
RSES_PROP_TYPE_UNDEFINED=-1,
RSES_PROP_TYPE_SESCMD=0,
RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD,
RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1
} rses_property_type_t;
/** default values for rwsplit configuration parameters */
#define CONFIG_MAX_SLAVE_CONN 1
#define CONFIG_MAX_SLAVE_RLAG -1 /*< not used */
#define CONFIG_SQL_VARIABLES_IN TYPE_ALL
#define GET_SELECT_CRITERIA(s) \
(strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \
LEAST_GLOBAL_CONNECTIONS : ( \
strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \
LEAST_BEHIND_MASTER : ( \
strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \
LEAST_ROUTER_CONNECTIONS : ( \
strncmp(s,"LEAST_CURRENT_OPERATIONS", strlen("LEAST_CURRENT_OPERATIONS")) == 0 ? \
LEAST_CURRENT_OPERATIONS : UNDEFINED_CRITERIA))))
/**
* Session variable command
*/
typedef struct mysql_sescmd_st {
#if defined(SS_DEBUG)
skygw_chk_t my_sescmd_chk_top;
#endif
rses_property_t* my_sescmd_prop; /*< Parent property */
GWBUF* my_sescmd_buf; /*< Query buffer */
unsigned char my_sescmd_packet_type;/*< Packet type */
bool my_sescmd_is_replied; /*< Is cmd replied to client */
#if defined(SS_DEBUG)
skygw_chk_t my_sescmd_chk_tail;
#endif
} mysql_sescmd_t;
/**
* Property structure
*/
struct rses_property_st {
#if defined(SS_DEBUG)
skygw_chk_t rses_prop_chk_top;
#endif
ROUTER_CLIENT_SES* rses_prop_rsession; /*< Parent router session */
int rses_prop_refcount; /*< Reference count*/
rses_property_type_t rses_prop_type; /*< Property type */
union rses_prop_data {
mysql_sescmd_t sescmd; /*< Session commands */
HASHTABLE* temp_tables; /*< Hashtable of table names */
} rses_prop_data;
rses_property_t* rses_prop_next; /*< Next property of same type */
#if defined(SS_DEBUG)
skygw_chk_t rses_prop_chk_tail;
#endif
};
typedef struct sescmd_cursor_st {
#if defined(SS_DEBUG)
skygw_chk_t scmd_cur_chk_top;
#endif
ROUTER_CLIENT_SES* scmd_cur_rses; /*< pointer to owning router session */
rses_property_t** scmd_cur_ptr_property; /*< address of pointer to owner property */
mysql_sescmd_t* scmd_cur_cmd; /*< pointer to current session command */
bool scmd_cur_active; /*< true if command is being executed */
#if defined(SS_DEBUG)
skygw_chk_t scmd_cur_chk_tail;
#endif
} sescmd_cursor_t;
/**
* Internal structure used to define the set of backend servers we are routing
* connections to. This provides the storage for routing module specific data
* that is required for each of the backend servers.
*
* Owned by router_instance, referenced by each routing session.
*/
typedef struct backend_st {
#if defined(SS_DEBUG)
skygw_chk_t be_chk_top;
#endif
SERVER* backend_server; /*< The server itself */
int backend_conn_count; /*< Number of connections to
* the server
*/
bool be_valid; /*< Valid when belongs to the
* router's configuration
*/
int weight; /*< Desired weighting on the
* load. Expressed in .1%
* increments
*/
struct stats{
int queries;
} stats;
#if defined(SS_DEBUG)
skygw_chk_t be_chk_tail;
#endif
} BACKEND;
/**
* Reference to BACKEND.
*
* Owned by router client session.
*/
typedef struct backend_ref_st {
#if defined(SS_DEBUG)
skygw_chk_t bref_chk_top;
#endif
BACKEND* bref_backend; /*< Backend server */
DCB* bref_dcb; /*< Backend DCB */
bref_state_t bref_state; /*< State of the backend */
bool bref_mapped; /*< Whether the backend has been mapped */
int bref_num_result_wait; /*< Number of not yet received results */
sescmd_cursor_t bref_sescmd_cur; /*< Session command cursor */
GWBUF* bref_pending_cmd; /*< For stmt which can't be routed due active sescmd execution */
#if defined(SS_DEBUG)
skygw_chk_t bref_chk_tail;
#endif
} backend_ref_t;
/**
* Configuration values
*/
typedef struct schemarouter_config_st {
int rw_max_slave_conn_percent;
int rw_max_slave_conn_count;
target_t rw_use_sql_variables_in;
} schemarouter_config_t;
/**
* The client session structure used within this router.
*/
struct router_client_session {
#if defined(SS_DEBUG)
skygw_chk_t rses_chk_top;
#endif
SPINLOCK rses_lock; /*< protects rses_deleted */
int rses_versno; /*< even = no active update, else odd. not used 4/14 */
bool rses_closed; /*< true when closeSession is called */
DCB* rses_client_dcb;
MYSQL_session* rses_mysql_session; /*< Session client data (username, password, SHA1). */
/** Properties listed by their type */
rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; /*< Session properties */
backend_ref_t* rses_master_ref; /*< Router session master reference */
backend_ref_t* rses_backend_ref; /*< Pointer to backend reference array */
schemarouter_config_t rses_config; /*< Copied config info from router instance */
int rses_nbackends; /*< Number of backends */
int rses_capabilities; /*< Input type, for example */
bool rses_autocommit_enabled; /*< Is autocommit enabled */
bool rses_transaction_active; /*< Is a transaction active */
struct router_instance *router; /*< The router instance */
struct router_client_session* next; /*< List of router sessions */
HASHTABLE* dbhash; /*< Database hash containing names of the databases mapped to the servers that contain them */
char connect_db[MYSQL_DATABASE_MAXLEN+1]; /*< Database the user was trying to connect to */
init_mask_t init; /*< Initialization state bitmask */
GWBUF* queue; /*< Query that was received before the session was ready */
DCB* dcb_route; /*< Internal DCB used to trigger re-routing of buffers */
DCB* dcb_reply; /*< Internal DCB used to send replies to the client */
#if defined(SS_DEBUG)
skygw_chk_t rses_chk_tail;
#endif
};
/**
* The statistics for this router instance
*/
typedef struct {
int n_sessions; /*< Number sessions created */
int n_queries; /*< Number of queries forwarded */
int n_master; /*< Number of stmts sent to master */
int n_slave; /*< Number of stmts sent to slave */
int n_all; /*< Number of stmts sent to all */
} ROUTER_STATS;
/**
* The per instance data for the router.
*/
typedef struct router_instance {
SERVICE* service; /*< Pointer to service */
ROUTER_CLIENT_SES* connections; /*< List of client connections */
SPINLOCK lock; /*< Lock for the instance data */
BACKEND** servers; /*< Backend servers */
BACKEND* master; /*< NULL or pointer */
schemarouter_config_t schemarouter_config; /*< expanded config info from SERVICE */
int schemarouter_version;/*< version number for router's config */
unsigned int bitmask; /*< Bitmask to apply to server->status */
unsigned int bitvalue; /*< Required value of server->status */
ROUTER_STATS stats; /*< Statistics for this router */
struct router_instance* next; /*< Next router on the list */
bool available_slaves; /*< The router has some slaves available */
//HASHTABLE* dbnames_hash; /** Hashtable containing the database names and where to find them */
//char** ignore_list;
} ROUTER_INSTANCE;
#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \
(SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED));
#if 0
void* dbnames_hash_init(ROUTER_INSTANCE* inst,BACKEND** backends);
bool update_dbnames_hash(ROUTER_INSTANCE* inst,BACKEND** backends, HASHTABLE* hashtable);
#endif
#endif /*< _SCHEMAROUTER_H */

View File

@ -0,0 +1,233 @@
#ifndef _SHARDROUTER_H
#define _SHARDROUTER_H
/*
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2013-2014
*/
/**
* @file shardrouter.h - The sharding router module header file
*
* @verbatim
* Revision History
*
* See GitHub https://github.com/mariadb-corporation/MaxScale
*
* @endverbatim
*/
#include <dcb.h>
#include <hashtable.h>
#include <mysql_client_server_protocol.h>
struct router_instance;
typedef enum {
TARGET_UNDEFINED = 0x00,
TARGET_MASTER = 0x01,
TARGET_SLAVE = 0x02,
TARGET_NAMED_SERVER = 0x04,
TARGET_ALL = 0x08,
TARGET_RLAG_MAX = 0x10,
TARGET_ANY = 0x20
} route_target_t;
#define TARGET_IS_UNDEFINED(t) (t == TARGET_UNDEFINED)
#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER)
#define TARGET_IS_ALL(t) (t & TARGET_ALL)
#define TARGET_IS_ANY(t) (t & TARGET_ANY)
typedef struct rses_property_st rses_property_t;
typedef struct router_client_session ROUTER_CLIENT_SES;
typedef enum rses_property_type_t {
RSES_PROP_TYPE_UNDEFINED=-1,
RSES_PROP_TYPE_SESCMD=0,
RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD,
RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1
} rses_property_type_t;
/** default values for rwsplit configuration parameters */
#define CONFIG_MAX_SLAVE_CONN 1
#define CONFIG_MAX_SLAVE_RLAG -1 /*< not used */
#define CONFIG_SQL_VARIABLES_IN TYPE_ALL
#define GET_SELECT_CRITERIA(s) \
(strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \
LEAST_GLOBAL_CONNECTIONS : ( \
strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \
LEAST_BEHIND_MASTER : ( \
strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \
LEAST_ROUTER_CONNECTIONS : ( \
strncmp(s,"LEAST_CURRENT_OPERATIONS", strlen("LEAST_CURRENT_OPERATIONS")) == 0 ? \
LEAST_CURRENT_OPERATIONS : UNDEFINED_CRITERIA))))
#define SUBSVC_IS_MAPPED(s) (s->state & SUBSVC_MAPPED)
#define SUBSVC_IS_CLOSED(s) (s->state & SUBSVC_CLOSED)
#define SUBSVC_IS_OK(s) (s->state & SUBSVC_OK)
#define SUBSVC_IS_WAITING(s) (s->state & SUBSVC_WAITING_RESULT)
/**
* Session variable command
*/
typedef struct mysql_sescmd_st {
#if defined(SS_DEBUG)
skygw_chk_t my_sescmd_chk_top;
#endif
rses_property_t* my_sescmd_prop; /*< parent property */
GWBUF* my_sescmd_buf; /*< query buffer */
unsigned char my_sescmd_packet_type;/*< packet type */
bool my_sescmd_is_replied; /*< is cmd replied to client */
#if defined(SS_DEBUG)
skygw_chk_t my_sescmd_chk_tail;
#endif
} mysql_sescmd_t;
/**
* Property structure
*/
struct rses_property_st {
#if defined(SS_DEBUG)
skygw_chk_t rses_prop_chk_top;
#endif
ROUTER_CLIENT_SES* rses_prop_rsession; /*< parent router session */
int rses_prop_refcount;
rses_property_type_t rses_prop_type;
union rses_prop_data {
mysql_sescmd_t sescmd;
HASHTABLE* temp_tables;
} rses_prop_data;
rses_property_t* rses_prop_next; /*< next property of same type */
#if defined(SS_DEBUG)
skygw_chk_t rses_prop_chk_tail;
#endif
};
typedef struct sescmd_cursor_st {
#if defined(SS_DEBUG)
skygw_chk_t scmd_cur_chk_top;
#endif
ROUTER_CLIENT_SES* scmd_cur_rses; /*< pointer to owning router session */
rses_property_t** scmd_cur_ptr_property; /*< address of pointer to owner property */
mysql_sescmd_t* scmd_cur_cmd; /*< pointer to current session command */
bool scmd_cur_active; /*< true if command is being executed */
#if defined(SS_DEBUG)
skygw_chk_t scmd_cur_chk_tail;
#endif
} sescmd_cursor_t;
typedef struct shardrouter_config_st {
int rw_max_slave_conn_percent;
int rw_max_slave_conn_count;
target_t rw_use_sql_variables_in;
} shard_config_t;
typedef enum{
SUBSVC_ALLOC = 0,
SUBSVC_OK = 1,
SUBSVC_CLOSED = (1<<1), /* This is when the service was cleanly closed */
SUBSVC_FAILED = (1<<2), /* This is when something went wrong */
SUBSVC_QUERY_ACTIVE = (1<<3),
SUBSVC_WAITING_RESULT = (1<<4),
SUBSVC_MAPPED = (1<<5)
}subsvc_state_t;
typedef struct subservice_t{
SERVICE* service;
SESSION* session;
DCB* dcb;
GWBUF* pending_cmd;
sescmd_cursor_t* scur;
int state;
int n_res_waiting;
bool mapped;
}SUBSERVICE;
/**
* The client session structure used within this router.
*/
struct router_client_session {
#if defined(SS_DEBUG)
skygw_chk_t rses_chk_top;
#endif
SPINLOCK rses_lock; /*< protects rses_deleted */
int rses_versno; /*< even = no active update, else odd. not used 4/14 */
bool rses_closed; /*< true when closeSession is called */
DCB* rses_client_dcb;
DCB* dummy_dcb; /* DCB used to send the client write messages from the router itself */
DCB* queue_dcb; /* DCB used to send queued queries to the router */
MYSQL_session* rses_mysql_session;
/** Properties listed by their type */
rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT];
shard_config_t rses_config; /*< copied config info from router instance */
int rses_capabilities; /*< input type, for example */
bool rses_autocommit_enabled;
bool rses_transaction_active;
struct router_instance *router; /*< The router instance */
struct router_client_session* next;
HASHTABLE* dbhash;
SUBSERVICE* *subservice;
int n_subservice;
bool hash_init;
SESSION* session;
GWBUF* queue;
#if defined(SS_DEBUG)
skygw_chk_t rses_chk_tail;
#endif
};
/**
* The statistics for this router instance
*/
typedef struct {
int n_sessions; /*< Number sessions created */
int n_queries; /*< Number of queries forwarded */
int n_master; /*< Number of stmts sent to master */
int n_slave; /*< Number of stmts sent to slave */
int n_all; /*< Number of stmts sent to all */
} ROUTER_STATS;
/**
* The per instance data for the router.
*/
typedef struct router_instance {
SERVICE* service; /*< Pointer to owning service */
ROUTER_CLIENT_SES* connections; /*< List of client connections */
SERVICE** services; /*< List of services to map for sharding */
int n_services;
SUBSERVICE* all_subsvc;
SPINLOCK lock; /*< Lock for the instance data */
shard_config_t shardrouter_config; /*< expanded config info from SERVICE */
int shardrouter_version;/*< version number for router's config */
unsigned int bitmask; /*< Bitmask to apply to server->status */
unsigned int bitvalue; /*< Required value of server->status */
ROUTER_STATS stats; /*< Statistics for this router */
struct router_instance* next; /*< Next router on the list */
bool available_slaves; /*< The router has some slaves available */
DCB* dumy_backend;
} ROUTER_INSTANCE;
#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \
(SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED));
#endif /*< _SHARDROUTER_H */

View File

@ -30,7 +30,7 @@
* @endverbatim
*/
#include <dcb.h>
#include <housekeeper.h>
/**
* The telnetd specific protocol structure to put in the DCB.
*/

View File

@ -9,3 +9,8 @@ install(TARGETS galeramon DESTINATION modules)
add_library(ndbclustermon SHARED ndbcluster_mon.c)
target_link_libraries(ndbclustermon log_manager utils)
install(TARGETS ndbclustermon DESTINATION modules)
if(BUILD_MMMON)
add_library(mmmon SHARED mm_mon.c)
target_link_libraries(mmmon log_manager utils)
install(TARGETS mmmon DESTINATION modules)
endif()

View File

@ -49,6 +49,7 @@
#include <secrets.h>
#include <dcb.h>
#include <modinfo.h>
#include <maxconfig.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
@ -66,7 +67,7 @@ MODULE_INFO info = {
"A Galera cluster monitor"
};
static void *startMonitor(void *);
static void *startMonitor(void *,void*);
static void stopMonitor(void *);
static void registerServer(void *, SERVER *);
static void unregisterServer(void *, SERVER *);
@ -88,11 +89,7 @@ static MONITOR_OBJECT MyObject = {
defaultUsers,
diagnostics,
setInterval,
setNetworkTimeout,
NULL,
NULL,
NULL,
disableMasterFailback
setNetworkTimeout
};
/**
@ -141,10 +138,10 @@ GetModuleObject()
* @return A handle to use when interacting with the monitor
*/
static void *
startMonitor(void *arg)
startMonitor(void *arg,void* opt)
{
MYSQL_MONITOR *handle;
CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
if (arg != NULL)
{
handle = (MYSQL_MONITOR *)arg;
@ -167,6 +164,15 @@ MYSQL_MONITOR *handle;
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
spinlock_init(&handle->lock);
}
while(params)
{
if(!strcmp(params->name,"disable_master_failback"))
handle->disableMasterFailback = config_truth_value(params->value);
params = params->next;
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
return handle;
}
@ -542,10 +548,17 @@ int log_no_members = 1;
ptr->server->port,
STRSRVSTATUS(ptr->server))));
}
if (!(SERVER_IS_RUNNING(ptr->server)) ||
!(SERVER_IS_IN_CLUSTER(ptr->server)))
{
dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING);
}
if (SERVER_IS_DOWN(ptr->server))
{
/** Increase this server'e error count */
dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING);
ptr->mon_err_count += 1;
}
else

View File

@ -41,8 +41,11 @@
#include <secrets.h>
#include <dcb.h>
#include <modinfo.h>
extern int lm_enabled_logfiles_bitmask;
#include <maxconfig.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
static void monitorMain(void *);
@ -55,7 +58,7 @@ MODULE_INFO info = {
"A MySQL Multi Master monitor"
};
static void *startMonitor(void *);
static void *startMonitor(void *,void*);
static void stopMonitor(void *);
static void registerServer(void *, SERVER *);
static void unregisterServer(void *, SERVER *);
@ -76,12 +79,7 @@ static MONITOR_OBJECT MyObject = {
unregisterServer,
defaultUser,
diagnostics,
setInterval,
NULL,
NULL,
NULL,
detectStaleMaster,
NULL
setInterval
};
/**
@ -131,10 +129,10 @@ GetModuleObject()
* @return A handle to use when interacting with the monitor
*/
static void *
startMonitor(void *arg)
startMonitor(void *arg,void* opt)
{
MYSQL_MONITOR *handle;
CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
if (arg)
{
handle = arg; /* Must be a restart */
@ -155,6 +153,14 @@ MYSQL_MONITOR *handle;
handle->master = NULL;
spinlock_init(&handle->lock);
}
while(params)
{
if(!strcmp(params->name,"detect_stale_master"))
handle->detectStaleMaster = config_truth_value(params->value);
params = params->next;
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
return handle;
}
@ -632,7 +638,7 @@ size_t nrounds = 0;
if (mon_status_changed(ptr))
{
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING);
}
if (mon_status_changed(ptr) ||

View File

@ -63,6 +63,7 @@
#include <secrets.h>
#include <dcb.h>
#include <modinfo.h>
#include <maxconfig.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
@ -80,7 +81,7 @@ MODULE_INFO info = {
"A MySQL Master/Slave replication monitor"
};
static void *startMonitor(void *);
static void *startMonitor(void *,void*);
static void stopMonitor(void *);
static void registerServer(void *, SERVER *);
static void unregisterServer(void *, SERVER *);
@ -88,8 +89,6 @@ static void defaultUser(void *, char *, char *);
static void diagnostics(DCB *, void *);
static void setInterval(void *, size_t);
static void defaultId(void *, unsigned long);
static void replicationHeartbeat(void *, int);
static void detectStaleMaster(void *, int);
static void setNetworkTimeout(void *, int, int);
static bool mon_status_changed(MONITOR_SERVERS* mon_srv);
static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv);
@ -110,11 +109,7 @@ static MONITOR_OBJECT MyObject = {
defaultUser,
diagnostics,
setInterval,
setNetworkTimeout,
defaultId,
replicationHeartbeat,
detectStaleMaster,
NULL
setNetworkTimeout
};
/**
@ -164,10 +159,10 @@ GetModuleObject()
* @return A handle to use when interacting with the monitor
*/
static void *
startMonitor(void *arg)
startMonitor(void *arg, void* opt)
{
MYSQL_MONITOR *handle;
CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
if (arg)
{
handle = arg; /* Must be a restart */
@ -181,7 +176,7 @@ MYSQL_MONITOR *handle;
handle->shutdown = 0;
handle->defaultUser = NULL;
handle->defaultPasswd = NULL;
handle->id = MONITOR_DEFAULT_ID;
handle->id = config_get_gateway_id();
handle->interval = MONITOR_INTERVAL;
handle->replicationHeartbeat = 0;
handle->detectStaleMaster = 0;
@ -191,6 +186,16 @@ MYSQL_MONITOR *handle;
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
spinlock_init(&handle->lock);
}
while(params)
{
if(!strcmp(params->name,"detect_stale_master"))
handle->detectStaleMaster = config_truth_value(params->value);
else if(!strcmp(params->name,"detect_replication_lag"))
handle->replicationHeartbeat = config_truth_value(params->value);
params = params->next;
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
return handle;
}

View File

@ -42,7 +42,7 @@
#include <secrets.h>
#include <dcb.h>
#include <modinfo.h>
#include <maxconfig.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
@ -59,7 +59,7 @@ MODULE_INFO info = {
"A MySQL cluster SQL node monitor"
};
static void *startMonitor(void *);
static void *startMonitor(void *,void*);
static void stopMonitor(void *);
static void registerServer(void *, SERVER *);
static void unregisterServer(void *, SERVER *);
@ -76,11 +76,7 @@ static MONITOR_OBJECT MyObject = {
defaultUsers,
diagnostics,
setInterval,
setNetworkTimeout,
NULL,
NULL,
NULL,
NULL
setNetworkTimeout
};
/**
@ -129,10 +125,10 @@ GetModuleObject()
* @return A handle to use when interacting with the monitor
*/
static void *
startMonitor(void *arg)
startMonitor(void *arg,void* opt)
{
MYSQL_MONITOR *handle;
CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
if (arg != NULL)
{
handle = (MYSQL_MONITOR *)arg;
@ -153,6 +149,7 @@ MYSQL_MONITOR *handle;
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
spinlock_init(&handle->lock);
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
return handle;
}

View File

@ -41,6 +41,7 @@
#include <gw.h>
#include <modinfo.h>
#include <log_manager.h>
#include <resultset.h>
MODULE_INFO info = {
MODULE_API_PROTOCOL,
@ -129,10 +130,10 @@ GetModuleObject()
static int
httpd_read_event(DCB* dcb)
{
//SESSION *session = dcb->session;
//ROUTER_OBJECT *router = session->service->router;
//ROUTER *router_instance = session->service->router_instance;
//void *rsession = session->router_session;
SESSION *session = dcb->session;
ROUTER_OBJECT *router = session->service->router;
ROUTER *router_instance = session->service->router_instance;
void *rsession = session->router_session;
int numchars = 1;
char buf[HTTPD_REQUESTLINE_MAXLEN-1] = "";
@ -143,6 +144,7 @@ int cgi = 0;
size_t i, j;
int headers_read = 0;
HTTPD_session *client_data = NULL;
GWBUF *uri;
client_data = dcb->data;
@ -234,13 +236,11 @@ HTTPD_session *client_data = NULL;
/* send all the basic headers and close with \r\n */
httpd_send_headers(dcb, 1);
#if 0
/**
* ToDO: launch proper content handling based on the requested URI, later REST interface
*
*/
dcb_printf(dcb, "Welcome to HTTPD MaxScale (c) %s\n\n", version_str);
if (strcmp(url, "/show") == 0) {
if (query_string && strlen(query_string)) {
if (strcmp(query_string, "dcb") == 0)
@ -249,6 +249,21 @@ HTTPD_session *client_data = NULL;
dprintAllSessions(dcb);
}
}
if (strcmp(url, "/services") == 0) {
RESULTSET *set, *seviceGetList();
if ((set = serviceGetList()) != NULL)
{
resultset_stream_json(set, dcb);
resultset_free(set);
}
}
#endif
if ((uri = gwbuf_alloc(strlen(url) + 1)) != NULL)
{
strcpy((char *)GWBUF_DATA(uri), url);
gwbuf_set_type(uri, GWBUF_TYPE_HTTP);
SESSION_ROUTE_QUERY(session, uri);
}
/* force the client connecton close */
dcb_close(dcb);
@ -345,6 +360,9 @@ int n_connect = 0;
/* create the session data for HTTPD */
client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session));
client->data = client_data;
client->session =
session_alloc(dcb->session->service, client);
if (poll_add_dcb(client) == -1)
{
@ -354,7 +372,6 @@ int n_connect = 0;
n_connect++;
}
}
close(so);
}
return n_connect;
@ -484,7 +501,7 @@ static void httpd_send_headers(DCB *dcb, int final)
strftime(date, sizeof(date), fmt, localtime(&httpd_current_time));
dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: text/plain\r\n", date, HTTP_SERVER_STRING);
dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: application/json\r\n", date, HTTP_SERVER_STRING);
/* close the headers */
if (final) {

View File

@ -144,6 +144,7 @@ char *password;
if ((n = dcb_read(dcb, &head)) != -1)
{
if (head)
{
unsigned char *ptr = GWBUF_DATA(head);

View File

@ -447,7 +447,7 @@ static int gw_read_backend_event(DCB *dcb) {
/* read available backend data */
rc = dcb_read(dcb, &read_buffer);
if (rc < 0)
{
GWBUF* errbuf;
@ -492,51 +492,37 @@ static int gw_read_backend_event(DCB *dcb) {
{
ss_dassert(read_buffer != NULL || dcb->dcb_readqueue != NULL);
}
if(dcb->dcb_readqueue)
{
read_buffer = gwbuf_append(dcb->dcb_readqueue,read_buffer);
}
nbytes_read = gwbuf_length(read_buffer);
if (nbytes_read < 3)
{
dcb->dcb_readqueue = read_buffer;
rc = 0;
goto return_rc;
}
/** Packet prefix was read earlier */
if (dcb->dcb_readqueue)
{
if (read_buffer != NULL)
{
read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer);
}
else
{
read_buffer = dcb->dcb_readqueue;
}
nbytes_read = gwbuf_length(read_buffer);
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. */
else
{
dcb->dcb_readqueue = NULL;
}
}
/** This may be either short prefix of a packet, or the tail of it. */
else
{
if (nbytes_read < 5)
{
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
rc = 0;
goto return_rc;
}
}
GWBUF *tmp = modutil_get_complete_packets(&read_buffer);
if(tmp == NULL)
{
/** No complete packets */
dcb->dcb_readqueue = read_buffer;
rc = 0;
goto return_rc;
}
dcb->dcb_readqueue = read_buffer;
read_buffer = tmp;
}
/**
* If protocol has session command set, concatenate whole
* response into one buffer.
@ -623,9 +609,14 @@ static int gw_write_backend_event(DCB *dcb) {
if (dcb->writeq != NULL)
{
data = (uint8_t *)GWBUF_DATA(dcb->writeq);
if (!(MYSQL_IS_COM_QUIT(data)))
if(dcb->session->client == NULL)
{
rc = 0;
}
else if (!(MYSQL_IS_COM_QUIT(data)))
{
/*< vraa : errorHandle */
mysql_send_custom_error(
dcb->session->client,
@ -887,7 +878,7 @@ static int gw_error_backend_event(DCB *dcb)
goto retblock;
}
#if defined(SS_DEBUG)
#if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Backend error event handling.")));

View File

@ -488,7 +488,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
* Decode the token and check the password
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
*/
skygw_log_write(LOGFILE_DEBUG,"Receiving connection from '%s' to database '%s'.",username,database);
auth_ret = gw_check_mysql_scramble_data(dcb,
auth_token,
auth_token_len,
@ -576,6 +576,7 @@ int gw_read_client_event(
CHK_PROTOCOL(protocol);
rc = dcb_read(dcb, &read_buffer);
if (rc < 0)
{
dcb_close(dcb);

View File

@ -170,6 +170,9 @@ int gw_read_backend_handshake(
if ((n = dcb_read(dcb, &head)) != -1)
{
dcb->last_read = hkheartbeat;
if (head)
{
payload = GWBUF_DATA(head);
@ -420,6 +423,8 @@ int gw_receive_backend_auth(
n = dcb_read(dcb, &head);
dcb->last_read = hkheartbeat;
/*<
* Read didn't fail and there is enough data for mysql packet.
*/
@ -1434,10 +1439,12 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
LOGIF(LD,
(skygw_log_write_flush(
LOGFILE_DEBUG,
"%lu [MySQL Client Auth], checking user [%s@%s]",
"%lu [MySQL Client Auth], checking user [%s@%s]%s%s",
pthread_self(),
key.user,
dcb->remote)));
dcb->remote,
key.resource != NULL ?" db: " :"",
key.resource != NULL ?key.resource :"")));
/* look for user@current_ipv4 now */
user_password = mysql_users_fetch(service->users, &key);
@ -1450,7 +1457,9 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
* (1) Check for localhost first: 127.0.0.1 (IPv4 only)
*/
if ((key.ipv4.sin_addr.s_addr == 0x0100007F) && !dcb->service->localhost_match_wildcard_host) {
if ((key.ipv4.sin_addr.s_addr == 0x0100007F) &&
!dcb->service->localhost_match_wildcard_host)
{
/* Skip the wildcard check and return 1 */
LOGIF(LE,
(skygw_log_write_flush(

View File

@ -156,6 +156,7 @@ char *password, *t;
if ((n = dcb_read(dcb, &head)) != -1)
{
if (head)
{
unsigned char *ptr = GWBUF_DATA(head);

View File

@ -5,6 +5,14 @@ if(BUILD_TESTS)
install(TARGETS testroute DESTINATION modules)
endif()
add_library(schemarouter SHARED schemarouter/schemarouter.c)
target_link_libraries(schemarouter log_manager utils query_classifier)
install(TARGETS schemarouter DESTINATION modules)
add_library(shardrouter SHARED schemarouter/shardrouter.c)
target_link_libraries(shardrouter log_manager utils query_classifier)
install(TARGETS shardrouter DESTINATION modules)
add_library(readconnroute SHARED readconnroute.c)
target_link_libraries(readconnroute log_manager utils)
install(TARGETS readconnroute DESTINATION modules)
@ -22,7 +30,9 @@ target_link_libraries(cli log_manager utils)
install(TARGETS cli DESTINATION modules)
add_subdirectory(readwritesplit)
add_subdirectory(schemarouter/test)
if(BUILD_BINLOG)
add_subdirectory(binlog)
endif()
add_subdirectory(maxinfo)

View File

@ -432,7 +432,7 @@ unsigned char *defuuid;
* Now start the replication from the master to MaxScale
*/
blr_start_master(inst);
free(name);
return (ROUTER *)inst;
}

View File

@ -71,7 +71,7 @@ static void blr_log_header(logfile_id_t file, char *msg, uint8_t *ptr);
int
blr_file_init(ROUTER_INSTANCE *router)
{
char *ptr, path[1024], filename[1050];
char *ptr, path[PATH_MAX], filename[PATH_MAX];
int file_found, n = 1;
int root_len, i;
DIR *dirp;
@ -79,13 +79,13 @@ struct dirent *dp;
if (router->binlogdir == NULL)
{
strcpy(path, "/usr/local/skysql/MaxScale");
strcpy(path, "/usr/local/mariadb-maxscale");
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
{
strcpy(path, ptr);
strncpy(path, ptr,PATH_MAX);
}
strcat(path, "/");
strcat(path, router->service->name);
strncat(path, "/",PATH_MAX);
strncat(path, router->service->name,PATH_MAX);
if (access(path, R_OK) == -1)
mkdir(path, 0777);
@ -94,7 +94,7 @@ struct dirent *dp;
}
else
{
strncpy(path, router->binlogdir, 1024);
strncpy(path, router->binlogdir, PATH_MAX);
}
if (access(router->binlogdir, R_OK) == -1)
{
@ -128,7 +128,7 @@ struct dirent *dp;
file_found = 0;
do {
sprintf(filename, "%s/" BINLOG_NAMEFMT, path, router->fileroot, n);
snprintf(filename,PATH_MAX, "%s/" BINLOG_NAMEFMT, path, router->fileroot, n);
if (access(filename, R_OK) != -1)
{
file_found = 1;
@ -142,16 +142,16 @@ struct dirent *dp;
if (n == 0) // No binlog files found
{
if (router->initbinlog)
sprintf(filename, BINLOG_NAMEFMT, router->fileroot,
snprintf(filename,PATH_MAX, BINLOG_NAMEFMT, router->fileroot,
router->initbinlog);
else
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, 1);
snprintf(filename,PATH_MAX, BINLOG_NAMEFMT, router->fileroot, 1);
if (! blr_file_create(router, filename))
return 0;
}
else
{
sprintf(filename, BINLOG_NAMEFMT, router->fileroot, n);
snprintf(filename,PATH_MAX, BINLOG_NAMEFMT, router->fileroot, n);
blr_file_append(router, filename);
}
return 1;
@ -196,7 +196,7 @@ unsigned char magic[] = BINLOG_MAGIC;
fsync(fd);
close(router->binlog_fd);
spinlock_acquire(&router->binlog_lock);
strcpy(router->binlog_name, file);
strncpy(router->binlog_name, file,BINLOG_FNAMELEN);
router->binlog_position = 4; /* Initial position after the magic number */
spinlock_release(&router->binlog_lock);
router->binlog_fd = fd;
@ -230,7 +230,7 @@ int fd;
fsync(fd);
close(router->binlog_fd);
spinlock_acquire(&router->binlog_lock);
strcpy(router->binlog_name, file);
strncpy(router->binlog_name, file,BINLOG_FNAMELEN);
router->binlog_position = lseek(fd, 0L, SEEK_END);
spinlock_release(&router->binlog_lock);
router->binlog_fd = fd;
@ -290,7 +290,7 @@ blr_file_flush(ROUTER_INSTANCE *router)
BLFILE *
blr_open_binlog(ROUTER_INSTANCE *router, char *binlog)
{
char path[1024];
char path[1025];
BLFILE *file;
spinlock_acquire(&router->fileslock);
@ -310,14 +310,14 @@ BLFILE *file;
spinlock_release(&router->fileslock);
return NULL;
}
strcpy(file->binlogname, binlog);
strncpy(file->binlogname, binlog,BINLOG_FNAMELEN+1);
file->refcnt = 1;
file->cache = 0;
spinlock_init(&file->lock);
strcpy(path, router->binlogdir);
strcat(path, "/");
strcat(path, binlog);
strncpy(path, router->binlogdir,1024);
strncat(path, "/",1024);
strncat(path, binlog,1024);
if ((file->fd = open(path, O_RDONLY, 0666)) == -1)
{
@ -630,10 +630,10 @@ struct stat statb;
void
blr_cache_response(ROUTER_INSTANCE *router, char *response, GWBUF *buf)
{
char path[4096], *ptr;
char path[4097], *ptr;
int fd;
strcpy(path, "/usr/local/skysql/MaxScale");
strcpy(path, "/usr/local/mariadb-maxscale");
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
{
strncpy(path, ptr, 4096);
@ -668,11 +668,11 @@ GWBUF *
blr_cache_read_response(ROUTER_INSTANCE *router, char *response)
{
struct stat statb;
char path[4096], *ptr;
char path[4097], *ptr;
int fd;
GWBUF *buf;
strcpy(path, "/usr/local/skysql/MaxScale");
strcpy(path, "/usr/local/mariadb-maxscale");
if ((ptr = getenv("MAXSCALE_HOME")) != NULL)
{
strncpy(path, ptr, 4096);

View File

@ -142,6 +142,7 @@ GWBUF *buf;
sprintf(name, "%s Master", router->service->name);
hktask_oneshot(name, blr_start_master, router,
BLR_MASTER_BACKOFF_TIME * router->retry_backoff++);
free(name);
}
if (router->retry_backoff > BLR_MAX_BACKOFF)
router->retry_backoff = BLR_MAX_BACKOFF;
@ -203,11 +204,12 @@ GWBUF *ptr;
router->master_state = BLRM_UNCONNECTED;
if ((name = malloc(strlen(router->service->name)
+ strlen(" Master")+1)) != NULL);
+ strlen(" Master")+1)) != NULL)
{
sprintf(name, "%s Master", router->service->name);
hktask_oneshot(name, blr_start_master, router,
BLR_MASTER_BACKOFF_TIME * router->retry_backoff++);
free(name);
}
if (router->retry_backoff > BLR_MAX_BACKOFF)
router->retry_backoff = BLR_MAX_BACKOFF;
@ -283,10 +285,11 @@ blr_master_delayed_connect(ROUTER_INSTANCE *router)
char *name;
if ((name = malloc(strlen(router->service->name)
+ strlen(" Master Recovery")+1)) != NULL);
+ strlen(" Master Recovery")+1)) != NULL)
{
sprintf(name, "%s Master Recovery", router->service->name);
hktask_oneshot(name, blr_start_master, router, 60);
free(name);
}
}
@ -407,6 +410,7 @@ char query[128];
}
router->master_state = BLRM_HBPERIOD;
router->master->func.write(router->master, buf);
free(val);
break;
}
case BLRM_HBPERIOD:
@ -701,7 +705,7 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
{
uint8_t *msg = NULL, *ptr, *pdata;
REP_HEADER hdr;
unsigned int len, reslen;
unsigned int len = 0, reslen;
unsigned int pkt_length;
int no_residual = 1;
int preslen = -1;
@ -1224,8 +1228,8 @@ MYSQL_session *auth_info;
if ((auth_info = calloc(1, sizeof(MYSQL_session))) == NULL)
return NULL;
strcpy(auth_info->user, username);
strcpy(auth_info->db, database);
strncpy(auth_info->user, username,MYSQL_USER_MAXLEN+1);
strncpy(auth_info->db, database,MYSQL_DATABASE_MAXLEN+1);
gw_sha1_str((const uint8_t *)password, strlen(password), auth_info->client_sha1);
return auth_info;

View File

@ -34,6 +34,8 @@
* Date Who Description
* 14/04/2014 Mark Riddoch Initial implementation
* 18/02/2015 Massimiliano Pinto Addition of DISCONNECT ALL and DISCONNECT SERVER server_id
* 18/03/2015 Markus Makela Better detection of CRC32 | NONE checksum
* 19/03/2015 Massimiliano Pinto Addition of basic MariaDB 10 compatibility support
*
* @endverbatim
*/
@ -80,6 +82,7 @@ static int blr_slave_send_eof(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int
static int blr_slave_send_disconnected_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id, int found);
static int blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id);
static int blr_slave_send_ok(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave);
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
@ -363,13 +366,17 @@ int query_len;
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.heartbeat);
}
else if (strcasecmp(word, "@mariadb_slave_capability") == 0)
{
free(query_text);
return blr_slave_send_ok(router, slave);
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
word = strtok_r(NULL, sep, &brkb);
if (word && (strcasecmp(word, "'none'") == 0))
slave->nocrc = 1;
else
slave->nocrc = 0;
if (word && (strcasecmp(word, "@@global.biglog_checksum'") == 0))
slave->nocrc = !router->master_chksum;
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.chksum1);
}
@ -410,8 +417,6 @@ int query_len;
else if (strcasecmp(word, "ALL") == 0)
{
free(query_text);
spinlock_release(&router->lock);
return blr_slave_disconnect_all(router, slave);
}
else if (strcasecmp(word, "SERVER") == 0)
@ -435,7 +440,7 @@ int query_len;
LOGFILE_ERROR, "Unexpected query from slave server %s", query_text)));
free(query_text);
blr_slave_send_error(router, slave, "Unexpected SQL query received from slave.");
return 0;
return 1;
}
@ -485,9 +490,9 @@ int len;
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
return;
data = GWBUF_DATA(pkt);
len = strlen(msg) + 1;
len = strlen(msg) + 9;
encode_value(&data[0], len, 24); // Payload length
data[3] = 0; // Sequence id
data[3] = 1; // Sequence id
// Payload
data[4] = 0xff; // Error indicator
data[5] = 0; // Error Code
@ -1371,7 +1376,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
GWBUF *head, *record;
REP_HEADER hdr;
int written, rval = 1, burst;
int rotating;
int rotating = 0;
unsigned long burst_size;
uint8_t *ptr;
@ -1887,21 +1892,23 @@ char serverid[40];
uint8_t *ptr;
int len, id_len, seqno = 2;
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router, slave, "server_id", 0x03, 40, seqno++);
blr_slave_send_columndef(router, slave, "state", 0xf, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
sprintf(serverid, "%d", server_id);
id_len = strlen(serverid);
if (found)
strcpy(state, "disconnected");
else
strcpy(state, "not found");
id_len = strlen(serverid);
len = 5 + id_len + strlen(state) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
return 0;
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router, slave, "server_id", 0x03, 40, seqno++);
blr_slave_send_columndef(router, slave, "state", 0xf, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, id_len + 2 + strlen(state), 24); // Add length of data packet
ptr += 3;
@ -1949,9 +1956,12 @@ blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int se
{
/* server_id found */
server_found = 1;
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "DISCONNECT SERVER: closing [%s], server id [%d]",
sptr->dcb->remote, server_id)));
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote,
server_id,
slave->dcb->user,
slave->dcb->remote)));
/* send server_id with disconnect state to client */
n = blr_slave_send_disconnected_server(router, slave, server_id, 1);
@ -1975,7 +1985,15 @@ blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int se
n = blr_slave_send_disconnected_server(router, slave, server_id, 0);
}
return n;
if (n == 0) {
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Error: gwbuf memory allocation in "
"DISCONNECT SERVER server_id [%d]",
sptr->serverid)));
blr_slave_send_error(router, slave, "Memory allocation error for DISCONNECT SERVER");
}
return 1;
}
/**
@ -1996,7 +2014,7 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
uint8_t *ptr;
int len, seqno;
GWBUF *pkt;
int n = 0;
int n = 1;
/* preparing output result */
blr_slave_send_fieldcount(router, slave, 2);
@ -2013,13 +2031,11 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
/* skip servers with state = 0 */
if (sptr->state != 0)
{
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "DISCONNECT ALL: closing [%s], server_id [%d]",
sptr->dcb->remote, sptr->serverid)));
sprintf(server_id, "%d", sptr->serverid);
sprintf(state, "disconnected");
len = 5 + strlen(server_id) + strlen(state) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL) {
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Error: gwbuf memory allocation in "
"DISCONNECT ALL for [%s], server_id [%d]",
@ -2027,9 +2043,15 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
spinlock_release(&router->lock);
return 0;
blr_slave_send_error(router, slave, "Memory allocation error for DISCONNECT ALL");
return 1;
}
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote, sptr->serverid, slave->dcb->user, slave->dcb->remote)));
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
@ -2055,5 +2077,33 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
blr_slave_send_eof(router, slave, seqno);
return n;
return 1;
}
/**
* Send a MySQL OK packet to the DCB
*
* @param dcb The DCB to send the OK packet to
* @return result of a write call, non-zero if write was successful
*/
static int
blr_slave_send_ok(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(11)) == NULL)
return 0;
ptr = GWBUF_DATA(pkt);
*ptr++ = 7; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return slave->dcb->func.write(slave->dcb, pkt);
}

View File

@ -36,12 +36,13 @@
* Date Who Description
* 20/06/13 Mark Riddoch Initial implementation
* 17/07/13 Mark Riddoch Additional commands
* 09/08/2013 Massimiliano Pinto Added enable/disable commands (now only for log)
* 09/08/13 Massimiliano Pinto Added enable/disable commands (now only for log)
* 20/05/14 Mark Riddoch Added ability to give server and service names rather
* than simply addresses
* 23/05/14 Mark Riddoch Added support for developer and user modes
* 29/05/14 Mark Riddoch Add Filter support
* 16/10/14 Mark Riddoch Add show eventq
* 05/03/15 Massimiliano Pinto Added enable/disable feedback
*
* @endverbatim
*/
@ -62,7 +63,7 @@
#include <poll.h>
#include <users.h>
#include <dbusers.h>
#include <config.h>
#include <maxconfig.h>
#include <telnetd.h>
#include <adminusers.h>
#include <monitor.h>
@ -129,6 +130,10 @@ struct subcommand showoptions[] = {
"Show the event statistics",
"Show the event statistics",
{0, 0, 0} },
{ "feedbackreport", 0, moduleShowFeedbackReport,
"Show the report of MaxScale loaded modules, suitable for Notification Service",
"Show the report of MaxScale loaded modules, suitable for Notification Service",
{0, 0, 0} },
{ "filter", 1, dprintFilter,
"Show details of a filter, called with a filter name",
"Show details of a filter, called with the address of a filter",
@ -365,6 +370,8 @@ static void enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor);
static void disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor);
static void enable_service_root(DCB *dcb, SERVICE *service);
static void disable_service_root(DCB *dcb, SERVICE *service);
static void enable_feedback_action();
static void disable_feedback_action();
/**
* * The subcommands of the enable command
@ -406,6 +413,14 @@ struct subcommand enableoptions[] = {
"Enable root access to a service, pass a service name to enable root access",
{ARG_TYPE_SERVICE, 0, 0}
},
{
"feedback",
0,
enable_feedback_action,
"Enable MaxScale modules list sending via http to notification service",
"Enable MaxScale modules list sending via http to notification service",
{0, 0, 0}
},
{
NULL,
0,
@ -458,6 +473,14 @@ struct subcommand disableoptions[] = {
"Disable root access to a service",
{ARG_TYPE_SERVICE, 0, 0}
},
{
"feedback",
0,
disable_feedback_action,
"Disable MaxScale modules list sending via http to notification service",
"Disable MaxScale modules list sending via http to notification service",
{0, 0, 0}
},
{
NULL,
0,
@ -1172,7 +1195,7 @@ shutdown_monitor(DCB *dcb, MONITOR *monitor)
static void
restart_monitor(DCB *dcb, MONITOR *monitor)
{
monitorStart(monitor);
monitorStart(monitor, NULL);
}
/**
@ -1184,7 +1207,14 @@ restart_monitor(DCB *dcb, MONITOR *monitor)
static void
enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor)
{
monitorSetReplicationHeartbeat(monitor, 1);
CONFIG_PARAMETER param;
const char* name = "detect_replication_lag";
const char* value = "1";
param.name = (char*)name;
param.value = (char*)value;
param.next = NULL;
monitorStop(monitor);
monitorStart(monitor,&param);
}
/**
@ -1196,7 +1226,14 @@ enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor)
static void
disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor)
{
monitorSetReplicationHeartbeat(monitor, 0);
CONFIG_PARAMETER param;
const char* name = "detect_replication_lag";
const char* value = "0";
param.name = (char*)name;
param.value = (char*)value;
param.next = NULL;
monitorStop(monitor);
monitorStart(monitor,&param);
}
/**
@ -1381,6 +1418,29 @@ set_nbpoll(DCB *dcb, int nb)
poll_set_nonblocking_polls(nb);
}
/**
* Re-enable sendig MaxScale module list via http
* Proper [feedback] section in MaxSclale.cnf
* is required.
*/
static void
enable_feedback_action(void)
{
config_enable_feedback_task();
return;
}
/**
* Disable sendig MaxScale module list via http
*/
static void
disable_feedback_action(void)
{
config_disable_feedback_task();
return;
}
#if defined(FAKE_CODE)
static void fail_backendfd(void)
{

View File

@ -0,0 +1,4 @@
add_library(maxinfo SHARED maxinfo.c maxinfo_parse.c maxinfo_error.c maxinfo_exec.c)
set_target_properties(maxinfo PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib)
target_link_libraries(maxinfo pthread log_manager)
install(TARGETS maxinfo DESTINATION modules)

View File

@ -0,0 +1,805 @@
/*
* This file is distributed as part of MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2014
*/
/**
* @file maxinfo.c - A "routing module" that in fact merely gives access
* to a MaxScale information schema usign the MySQL protocol
*
* @verbatim
* Revision History
*
* Date Who Description
* 16/02/15 Mark Riddoch Initial implementation
* 27/02/15 Massimiliano Pinto Added maxinfo_add_mysql_user
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <service.h>
#include <session.h>
#include <server.h>
#include <router.h>
#include <modules.h>
#include <modinfo.h>
#include <modutil.h>
#include <monitor.h>
#include <atomic.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <maxinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <resultset.h>
#include <version.h>
#include <resultset.h>
#include <secrets.h>
#include <users.h>
#include <dbusers.h>
MODULE_INFO info = {
MODULE_API_ROUTER,
MODULE_ALPHA_RELEASE,
ROUTER_VERSION,
"The MaxScale Information Schema"
};
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
extern char *create_hex_sha1_sha1_passwd(char *passwd);
static char *version_str = "V1.0.0";
static int maxinfo_statistics(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
static int maxinfo_ping(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
static int maxinfo_execute_query(INFO_INSTANCE *, INFO_SESSION *, char *);
static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *router_session, GWBUF *queue);
static int maxinfo_add_mysql_user(SERVICE *service);
/* The router entry points */
static ROUTER *createInstance(SERVICE *service, char **options);
static void *newSession(ROUTER *instance, SESSION *session);
static void closeSession(ROUTER *instance, void *router_session);
static void freeSession(ROUTER *instance, void *router_session);
static int execute(ROUTER *instance, void *router_session, GWBUF *queue);
static void diagnostics(ROUTER *instance, DCB *dcb);
static uint8_t getCapabilities (ROUTER* inst, void* router_session);
static void handleError(
ROUTER *instance,
void *router_session,
GWBUF *errbuf,
DCB *backend_dcb,
error_action_t action,
bool *succp);
/** The module object definition */
static ROUTER_OBJECT MyObject = {
createInstance,
newSession,
closeSession,
freeSession,
execute,
diagnostics,
NULL,
handleError,
getCapabilities
};
static SPINLOCK instlock;
static INFO_INSTANCE *instances;
/**
* Implementation of the mandatory version entry point
*
* @return version string of the module
*/
char *
version()
{
return version_str;
}
/**
* The module initialisation routine, called when the module
* is first loaded.
*/
void
ModuleInit()
{
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Initialise MaxInfo router module %s.\n",
version_str)));
spinlock_init(&instlock);
instances = NULL;
}
/**
* The module entry point routine. It is this routine that
* must populate the structure that is referred to as the
* "module object", this is a structure with the set of
* external entry points for this module.
*
* @return The module object
*/
ROUTER_OBJECT *
GetModuleObject()
{
return &MyObject;
}
/**
* Create an instance of the router for a particular service
* within the gateway.
*
* @param service The service this router is being create for
* @param options Any array of options for the query router
*
* @return The instance data for this new instance
*/
static ROUTER *
createInstance(SERVICE *service, char **options)
{
INFO_INSTANCE *inst;
int i;
if ((inst = malloc(sizeof(INFO_INSTANCE))) == NULL)
return NULL;
inst->service = service;
spinlock_init(&inst->lock);
if (options)
{
for (i = 0; options[i]; i++)
{
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Unknown option for MaxInfo '%s'\n",
options[i])));
}
}
}
/*
* We have completed the creation of the instance data, so now
* insert this router instance into the linked list of routers
* that have been created with this module.
*/
spinlock_acquire(&instlock);
inst->next = instances;
instances = inst;
spinlock_release(&instlock);
/*
* The following add the service user to service->users via mysql_users_alloc()
* password to be used.
*/
maxinfo_add_mysql_user(service);
return (ROUTER *)inst;
}
/**
* Associate a new session with this instance of the router.
*
* @param instance The router instance data
* @param session The session itself
* @return Session specific data for this session
*/
static void *
newSession(ROUTER *instance, SESSION *session)
{
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
INFO_SESSION *client;
if ((client = (INFO_SESSION *)malloc(sizeof(INFO_SESSION))) == NULL)
{
return NULL;
}
client->session = session;
client->dcb = session->client;
client->queue = NULL;
spinlock_acquire(&inst->lock);
client->next = inst->sessions;
inst->sessions = client;
spinlock_release(&inst->lock);
session->state = SESSION_STATE_READY;
return (void *)client;
}
/**
* Close a session with the router, this is the mechanism
* by which a router may cleanup data structure etc.
*
* @param instance The router instance data
* @param router_session The session being closed
*/
static void
closeSession(ROUTER *instance, void *router_session)
{
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
INFO_SESSION *session = (INFO_SESSION *)router_session;
spinlock_acquire(&inst->lock);
if (inst->sessions == session)
inst->sessions = session->next;
else
{
INFO_SESSION *ptr = inst->sessions;
while (ptr && ptr->next != session)
ptr = ptr->next;
if (ptr)
ptr->next = session->next;
}
spinlock_release(&inst->lock);
/**
* Router session is freed in session.c:session_close, when session who
* owns it, is freed.
*/
}
/**
* Free a maxinfo session
*
* @param router_instance The router session
* @param router_client_session The router session as returned from newSession
*/
static void freeSession(
ROUTER* router_instance,
void* router_client_session)
{
free(router_client_session);
return;
}
/**
* Error Handler routine
*
* The routine will handle errors that occurred in backend writes.
*
* @param instance The router instance
* @param router_session The router session
* @param message The error message to reply
* @param backend_dcb The backend DCB
* @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION
*
*/
static void handleError(
ROUTER *instance,
void *router_session,
GWBUF *errbuf,
DCB *backend_dcb,
error_action_t action,
bool *succp)
{
DCB *client_dcb;
SESSION *session = backend_dcb->session;
session_state_t sesstate;
/** Reset error handle flag from a given DCB */
if (action == ERRACT_RESET)
{
backend_dcb->dcb_errhandle_called = false;
return;
}
/** Don't handle same error twice on same DCB */
if (backend_dcb->dcb_errhandle_called)
{
/** we optimistically assume that previous call succeed */
*succp = true;
return;
}
else
{
backend_dcb->dcb_errhandle_called = true;
}
spinlock_acquire(&session->ses_lock);
sesstate = session->state;
client_dcb = session->client;
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;
}
/**
* We have data from the client, this is a SQL command, or other MySQL
* packet type.
*
* @param instance The router instance
* @param router_session The router session returned from the newSession call
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
execute(ROUTER *rinstance, void *router_session, GWBUF *queue)
{
INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
INFO_SESSION *session = (INFO_SESSION *)router_session;
uint8_t *data;
int length, len, residual;
char *sql;
if (GWBUF_TYPE(queue) == GWBUF_TYPE_HTTP)
{
return handle_url(instance, session, queue);
}
if (session->queue)
{
queue = gwbuf_append(session->queue, queue);
session->queue = NULL;
queue = gwbuf_make_contiguous(queue);
}
data = (uint8_t *)GWBUF_DATA(queue);
length = data[0] + (data[1] << 8) + (data[2] << 16);
if (length + 4 > GWBUF_LENGTH(queue))
{
// Incomplete packet, must be buffered
session->queue = queue;
return 1;
}
// We have a complete request in a signle buffer
if (modutil_MySQL_Query(queue, &sql, &len, &residual))
{
sql = strndup(sql, len);
int rc = maxinfo_execute_query(instance, session, sql);
free(sql);
return rc;
}
else
{
switch (MYSQL_COMMAND(queue))
{
case COM_PING:
return maxinfo_ping(instance, session, queue);
case COM_STATISTICS:
return maxinfo_statistics(instance, session, queue);
default:
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"maxinfo: Unexpected MySQL command 0x%x",
MYSQL_COMMAND(queue))));
}
}
return 1;
}
/**
* Display router diagnostics
*
* @param instance Instance of the router
* @param dcb DCB to send diagnostics to
*/
static void
diagnostics(ROUTER *instance, DCB *dcb)
{
return; /* Nothing to do currently */
}
/**
* Capabilities interface for the rotuer
*
* Not used for the maxinfo router
*/
static uint8_t
getCapabilities(
ROUTER* inst,
void* router_session)
{
return 0;
}
/**
* Return some basic statistics from the router in response to a COM_STATISTICS
* request.
*
* @param router The router instance
* @param session The connection that requested the statistics
* @param queue The statistics request
*
* @return non-zero on sucessful send
*/
static int
maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
{
char result[1000], *ptr;
GWBUF *ret;
int len;
extern int MaxScaleUptime();
snprintf(result, 1000,
"Uptime: %u Threads: %u Sessions: %u ",
MaxScaleUptime(),
config_threadcount(),
serviceSessionCountAll());
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 session->dcb->func.write(session->dcb, ret);
}
/**
* Respond to a COM_PING command
*
* @param router The router instance
* @param session The connection that requested the ping
* @param queue The ping request
*/
static int
maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, 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 session->dcb->func.write(session->dcb, ret);
}
/**
* Populate the version comment with the MaxScale version
*
* @param result The result set
* @param data Pointer to int which is row count
* @return The populated row
*/
static RESULT_ROW *
version_comment(RESULTSET *result, void *data)
{
int *context = (int *)data;
RESULT_ROW *row;
if (*context == 0)
{
(*context)++;
row = resultset_make_row(result);
resultset_row_set(row, 0, MAXSCALE_VERSION);
return row;
}
return NULL;
}
/**
* The hardwired select @@vercom response
*
* @param dcb The DCB of the client
*/
static void
respond_vercom(DCB *dcb)
{
RESULTSET *result;
int context = 0;
if ((result = resultset_create(version_comment, &context)) == NULL)
{
maxinfo_send_error(dcb, 0, "No resources available");
return;
}
resultset_add_column(result, "@@version_comment", 40, COL_TYPE_VARCHAR);
resultset_stream_mysql(result, dcb);
resultset_free(result);
}
/**
* Populate the version comment with the MaxScale version
*
* @param result The result set
* @param data Pointer to int which is row count
* @return The populated row
*/
static RESULT_ROW *
starttime_row(RESULTSET *result, void *data)
{
int *context = (int *)data;
RESULT_ROW *row;
extern time_t MaxScaleStarted;
struct tm tm;
static char buf[40];
if (*context == 0)
{
(*context)++;
row = resultset_make_row(result);
sprintf(buf, "%u", (unsigned int)MaxScaleStarted);
resultset_row_set(row, 0, buf);
return row;
}
return NULL;
}
/**
* The hardwired select ... as starttime response
*
* @param dcb The DCB of the client
*/
static void
respond_starttime(DCB *dcb)
{
RESULTSET *result;
int context = 0;
if ((result = resultset_create(starttime_row, &context)) == NULL)
{
maxinfo_send_error(dcb, 0, "No resources available");
return;
}
resultset_add_column(result, "starttime", 40, COL_TYPE_VARCHAR);
resultset_stream_mysql(result, dcb);
resultset_free(result);
}
/**
* Send a MySQL OK packet to the DCB
*
* @param dcb The DCB to send the OK packet to
* @return result of a write call, non-zero if write was successful
*/
static int
maxinfo_send_ok(DCB *dcb)
{
GWBUF *buf;
uint8_t *ptr;
if ((buf = gwbuf_alloc(11)) == NULL)
return 0;
ptr = GWBUF_DATA(buf);
*ptr++ = 7; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return dcb->func.write(dcb, buf);
}
/**
* Execute a SQL query against the MaxScale Information Schema
*
* @param instance The instance strcture
* @param session The session pointer
* @param sql The SQL to execute
*/
static int
maxinfo_execute_query(INFO_INSTANCE *instance, INFO_SESSION *session, char *sql)
{
MAXINFO_TREE *tree;
PARSE_ERROR err;
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE,
"maxinfo: SQL statement: '%s' for 0x%x.",
sql, session->dcb)));
if (strcmp(sql, "select @@version_comment limit 1") == 0)
{
respond_vercom(session->dcb);
return 1;
}
/* Below is a kludge for MonYog, if we see
* select unix_timestamp... as starttime
* just return the starttime of MaxScale
*/
if (strncasecmp(sql, "select UNIX_TIMESTAMP",
strlen("select UNIX_TIMESTAMP")) == 0
&& (strstr(sql, "as starttime") != NULL || strstr(sql, "AS starttime") != NULL))
{
respond_starttime(session->dcb);
return 1;
}
if (strcasecmp(sql, "set names 'utf8'") == 0)
{
return maxinfo_send_ok(session->dcb);
}
if (strncasecmp(sql, "set session", 11) == 0)
{
return maxinfo_send_ok(session->dcb);
}
if (strncasecmp(sql, "set autocommit", 14) == 0)
{
return maxinfo_send_ok(session->dcb);
}
if (strncasecmp(sql, "SELECT `ENGINES`.`SUPPORT`", 26) == 0)
{
return maxinfo_send_ok(session->dcb);
}
if ((tree = maxinfo_parse(sql, &err)) == NULL)
{
maxinfo_send_parse_error(session->dcb, sql, err);
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Failed to parse SQL statement: '%s'.",
sql)));
}
else
maxinfo_execute(session->dcb, tree);
return 1;
}
/**
* Session all result set
* @return A resultset for all sessions
*/
static RESULTSET *
maxinfoSessionsAll()
{
return sessionGetList(SESSION_LIST_ALL);
}
/**
* Client session result set
* @return A resultset for all sessions
*/
static RESULTSET *
maxinfoClientSessions()
{
return sessionGetList(SESSION_LIST_CONNECTION);
}
typedef RESULTSET *(*RESULTSETFUNC)();
/**
* Table that maps a URI to a function to call to
* to obtain the result set related to that URI
*/
static struct uri_table {
char *uri;
RESULTSETFUNC func;
} supported_uri[] = {
{ "/services", serviceGetList },
{ "/listeners", serviceGetListenerList },
{ "/modules", moduleGetList },
{ "/monitors", monitorGetList },
{ "/sessions", maxinfoSessionsAll },
{ "/clients", maxinfoClientSessions },
{ "/servers", serverGetList },
{ "/variables", maxinfo_variables },
{ "/status", maxinfo_status },
{ "/event/times", eventTimesGetList },
{ NULL, NULL }
};
/**
* We have data from the client, this is a HTTP URL
*
* @param instance The router instance
* @param session The router session returned from the newSession call
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
handle_url(INFO_INSTANCE *instance, INFO_SESSION *session, GWBUF *queue)
{
char *uri;
int i;
RESULTSET *set;
uri = (char *)GWBUF_DATA(queue);
for (i = 0; supported_uri[i].uri; i++)
{
if (strcmp(uri, supported_uri[i].uri) == 0)
{
set = (*supported_uri[i].func)();
resultset_stream_json(set, session->dcb);
resultset_free(set);
}
}
return 1;
}
/**
* Add the service user to the service->users
* via mysql_users_alloc and add_mysql_users_with_host_ipv4
* User is added for '%' and 'localhost' hosts
*
* @param service The service for this router
* @return 0 on success, 1 on failure
*/
static int
maxinfo_add_mysql_user(SERVICE *service) {
char *dpwd = NULL;
char *newpasswd = NULL;
char *service_user = NULL;
char *service_passwd = NULL;
if (serviceGetUser(service, &service_user, &service_passwd) == 0) {
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"maxinfo: failed to get service user details")));
return 1;
}
dpwd = decryptPassword(service->credentials.authdata);
if (!dpwd) {
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"maxinfo: decrypt password failed for service user %s",
service_user)));
return 1;
}
service->users = (void *)mysql_users_alloc();
newpasswd = create_hex_sha1_sha1_passwd(dpwd);
if (!newpasswd) {
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"maxinfo: create hex_sha1_sha1_password failed for service user %s",
service_user)));
users_free(service->users);
return 1;
}
/* add service user for % and localhost */
(void)add_mysql_users_with_host_ipv4(service->users, service->credentials.name, "%", newpasswd, "Y", "");
(void)add_mysql_users_with_host_ipv4(service->users, service->credentials.name, "localhost", newpasswd, "Y", "");
free(newpasswd);
free(dpwd);
return 0;
}

View File

@ -0,0 +1,119 @@
/*
* This file is distributed as part of MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2014
*/
/**
* @file maxinfo_error.c - Handle error reporting for the maxinfo router
*
* @verbatim
* Revision History
*
* Date Who Description
* 17/02/15 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <service.h>
#include <session.h>
#include <router.h>
#include <modules.h>
#include <modinfo.h>
#include <modutil.h>
#include <atomic.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <maxinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
/**
* Process a parse error and send error report to client
*
* @param dcb The DCB to send to error
* @param sql The SQL that had the parse error
* @param err The parse error code
*/
void
maxinfo_send_parse_error(DCB *dcb, char *sql, PARSE_ERROR err)
{
char *desc = "";
char *msg;
int len;
switch (err)
{
case PARSE_NOERROR:
desc = "No error";
break;
case PARSE_MALFORMED_SHOW:
desc = "Expected show <command> [like <pattern>]";
break;
case PARSE_EXPECTED_LIKE:
desc = "Expected LIKE <pattern>";
break;
case PARSE_SYNTAX_ERROR:
desc = "Syntax error";
break;
}
len = strlen(sql) + strlen(desc) + 20;
if ((msg = (char *)malloc(len)) == NULL)
return;
sprintf(msg, "%s in query '%s'", desc, sql);
maxinfo_send_error(dcb, 1149, msg);
free(msg);
}
/**
* Construct an error response
*
* @param dcb The DCB to send the error packet to
* @param msg The slave server instance
*/
void
maxinfo_send_error(DCB *dcb, int errcode, char *msg)
{
GWBUF *pkt;
unsigned char *data;
int len;
len = strlen(msg) + 9;
if ((pkt = gwbuf_alloc(len + 4)) == NULL)
return;
data = GWBUF_DATA(pkt);
data[0] = len & 0xff; // Payload length
data[1] = (len >> 8) & 0xff;
data[2] = (len >> 16) & 0xff;
data[3] = 1; // Sequence id
// Payload
data[4] = 0xff; // Error indicator
data[5] = errcode & 0xff; // Error Code
data[6] = (errcode >> 8) & 0xff; // Error Code
memcpy(&data[7], "#42000", 6);
memcpy(&data[13], msg, strlen(msg)); // Error Message
dcb->func.write(dcb, pkt);
}

View File

@ -0,0 +1,770 @@
/*
* This file is distributed as part of MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2014
*/
/**
* @file maxinfo_parse.c - Parse the limited set of SQL that the MaxScale
* information schema can use
*
* @verbatim
* Revision History
*
* Date Who Description
* 17/02/15 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <service.h>
#include <session.h>
#include <router.h>
#include <modules.h>
#include <monitor.h>
#include <version.h>
#include <modinfo.h>
#include <modutil.h>
#include <atomic.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <maxinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <resultset.h>
#include <maxconfig.h>
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
static void exec_show(DCB *dcb, MAXINFO_TREE *tree);
static void exec_select(DCB *dcb, MAXINFO_TREE *tree);
static void exec_show_variables(DCB *dcb, MAXINFO_TREE *filter);
static void exec_show_status(DCB *dcb, MAXINFO_TREE *filter);
static int maxinfo_pattern_match(char *pattern, char *str);
/**
* Execute a parse tree and return the result set or runtime error
*
* @param dcb The DCB that connects to the client
* @param tree The parse tree for the query
*/
void
maxinfo_execute(DCB *dcb, MAXINFO_TREE *tree)
{
switch (tree->op)
{
case MAXOP_SHOW:
exec_show(dcb, tree);
break;
case MAXOP_SELECT:
exec_select(dcb, tree);
break;
case MAXOP_TABLE:
case MAXOP_COLUMNS:
case MAXOP_LITERAL:
case MAXOP_PREDICATE:
case MAXOP_LIKE:
case MAXOP_EQUAL:
default:
maxinfo_send_error(dcb, 0, "Unexpected operator in parse tree");
}
}
/**
* Fetch the list of services and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_services(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = serviceGetList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of listeners and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_listeners(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = serviceGetListenerList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of sessions and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_sessions(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = sessionGetList(SESSION_LIST_ALL)) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of client sessions and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_clients(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = sessionGetList(SESSION_LIST_CONNECTION)) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of servers and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_servers(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = serverGetList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of modules and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_modules(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = moduleGetList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the list of monitors and stream as a result set
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_monitors(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = monitorGetList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* Fetch the event times data
*
* @param dcb DCB to which to stream result set
* @param tree Potential like clause (currently unused)
*/
static void
exec_show_eventTimes(DCB *dcb, MAXINFO_TREE *tree)
{
RESULTSET *set;
if ((set = eventTimesGetList()) == NULL)
return;
resultset_stream_mysql(set, dcb);
resultset_free(set);
}
/**
* The table of show commands that are supported
*/
static struct {
char *name;
void (*func)(DCB *, MAXINFO_TREE *);
} show_commands[] = {
{ "variables", exec_show_variables },
{ "status", exec_show_status },
{ "services", exec_show_services },
{ "listeners", exec_show_listeners },
{ "sessions", exec_show_sessions },
{ "clients", exec_show_clients },
{ "servers", exec_show_servers },
{ "modules", exec_show_modules },
{ "monitors", exec_show_monitors },
{ "eventTimes", exec_show_eventTimes },
{ NULL, NULL }
};
/**
* Execute a show command parse tree and return the result set or runtime error
*
* @param dcb The DCB that connects to the client
* @param tree The parse tree for the query
*/
static void
exec_show(DCB *dcb, MAXINFO_TREE *tree)
{
int i;
char errmsg[120];
for (i = 0; show_commands[i].name; i++)
{
if (strcasecmp(show_commands[i].name, tree->value) == 0)
{
(*show_commands[i].func)(dcb, tree->right);
return;
}
}
if (strlen(tree->value) > 80) // Prevent buffer overrun
tree->value[80] = 0;
sprintf(errmsg, "Unsupported show command '%s'", tree->value);
maxinfo_send_error(dcb, 0, errmsg);
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, errmsg)));
}
/**
* Return the current MaxScale version
*
* @return The version string for MaxScale
*/
static char *
getVersion()
{
return MAXSCALE_VERSION;
}
static char *versionComment = "MariaDB MaxScale";
/**
* Return the current MaxScale version
*
* @return The version string for MaxScale
*/
static char *
getVersionComment()
{
return versionComment;
}
/**
* Return the current MaxScale Home Directory
*
* @return The version string for MaxScale
*/
static char *
getMaxScaleHome()
{
return getenv("MAXSCALE_HOME");
}
/* The various methods to fetch the variables */
#define VT_STRING 1
#define VT_INT 2
extern int MaxScaleUptime();
typedef void *(*STATSFUNC)();
/**
* Variables that may be sent in a show variables
*/
static struct {
char *name;
int type;
STATSFUNC func;
} variables[] = {
{ "version", VT_STRING, (STATSFUNC)getVersion },
{ "version_comment", VT_STRING, (STATSFUNC)getVersionComment },
{ "basedir", VT_STRING, (STATSFUNC)getMaxScaleHome},
{ "MAXSCALE_VERSION", VT_STRING, (STATSFUNC)getVersion },
{ "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount },
{ "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls },
{ "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep },
{ "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)MaxScaleUptime },
{ "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll },
{ NULL, 0, NULL }
};
typedef struct {
int index;
char *like;
} VARCONTEXT;
/**
* Callback function to populate rows of the show variable
* command
*
* @param data The context point
* @return The next row or NULL if end of rows
*/
static RESULT_ROW *
variable_row(RESULTSET *result, void *data)
{
VARCONTEXT *context = (VARCONTEXT *)data;
RESULT_ROW *row;
char buf[80];
if (variables[context->index].name)
{
if (context->like &&
maxinfo_pattern_match(context->like,
variables[context->index].name))
{
context->index++;
return variable_row(result, data);
}
row = resultset_make_row(result);
resultset_row_set(row, 0, variables[context->index].name);
switch (variables[context->index].type)
{
case VT_STRING:
resultset_row_set(row, 1,
(char *)(*variables[context->index].func)());
break;
case VT_INT:
snprintf(buf, 80, "%ld",
(long)(*variables[context->index].func)());
resultset_row_set(row, 1, buf);
break;
}
context->index++;
return row;
}
return NULL;
}
/**
* Execute a show variables command applying an optional filter
*
* @param dcb The DCB connected to the client
* @param filter A potential like clause or NULL
*/
static void
exec_show_variables(DCB *dcb, MAXINFO_TREE *filter)
{
RESULTSET *result;
VARCONTEXT context;
if (filter)
context.like = filter->value;
else
context.like = NULL;
context.index = 0;
if ((result = resultset_create(variable_row, &context)) == NULL)
{
maxinfo_send_error(dcb, 0, "No resources available");
return;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
resultset_stream_mysql(result, dcb);
resultset_free(result);
}
/**
* Return the show variables output a a result set
*
* @return Variables as a result set
*/
RESULTSET *
maxinfo_variables()
{
RESULTSET *result;
static VARCONTEXT context;
context.like = NULL;
context.index = 0;
if ((result = resultset_create(variable_row, &context)) == NULL)
{
return NULL;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
return result;
}
/**
* Interface to dcb_count_by_usage for all dcbs
*/
static int
maxinfo_all_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_ALL);
}
/**
* Interface to dcb_count_by_usage for client dcbs
*/
static int
maxinfo_client_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_CLIENT);
}
/**
* Interface to dcb_count_by_usage for listener dcbs
*/
static int
maxinfo_listener_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_LISTENER);
}
/**
* Interface to dcb_count_by_usage for backend dcbs
*/
static int
maxinfo_backend_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_BACKEND);
}
/**
* Interface to dcb_count_by_usage for internal dcbs
*/
static int
maxinfo_internal_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_INTERNAL);
}
/**
* Interface to dcb_count_by_usage for zombie dcbs
*/
static int
maxinfo_zombie_dcbs()
{
return dcb_count_by_usage(DCB_USAGE_ZOMBIE);
}
/**
* Interface to poll stats for reads
*/
static int
maxinfo_read_events()
{
return poll_get_stat(POLL_STAT_READ);
}
/**
* Interface to poll stats for writes
*/
static int
maxinfo_write_events()
{
return poll_get_stat(POLL_STAT_WRITE);
}
/**
* Interface to poll stats for errors
*/
static int
maxinfo_error_events()
{
return poll_get_stat(POLL_STAT_ERROR);
}
/**
* Interface to poll stats for hangup
*/
static int
maxinfo_hangup_events()
{
return poll_get_stat(POLL_STAT_HANGUP);
}
/**
* Interface to poll stats for accepts
*/
static int
maxinfo_accept_events()
{
return poll_get_stat(POLL_STAT_ACCEPT);
}
/**
* Interface to poll stats for event queue length
*/
static int
maxinfo_event_queue_length()
{
return poll_get_stat(POLL_STAT_EVQ_LEN);
}
/**
* Interface to poll stats for event pending queue length
*/
static int
maxinfo_event_pending_queue_length()
{
return poll_get_stat(POLL_STAT_EVQ_PENDING);
}
/**
* Interface to poll stats for max event queue length
*/
static int
maxinfo_max_event_queue_length()
{
return poll_get_stat(POLL_STAT_EVQ_MAX);
}
/**
* Interface to poll stats for max queue time
*/
static int
maxinfo_max_event_queue_time()
{
return poll_get_stat(POLL_STAT_MAX_QTIME);
}
/**
* Interface to poll stats for max event execution time
*/
static int
maxinfo_max_event_exec_time()
{
return poll_get_stat(POLL_STAT_MAX_EXECTIME);
}
/**
* Variables that may be sent in a show status
*/
static struct {
char *name;
int type;
STATSFUNC func;
} status[] = {
{ "Uptime", VT_INT, (STATSFUNC)MaxScaleUptime },
{ "Uptime_since_flush_status", VT_INT, (STATSFUNC)MaxScaleUptime },
{ "Threads_created", VT_INT, (STATSFUNC)config_threadcount },
{ "Threads_running", VT_INT, (STATSFUNC)config_threadcount },
{ "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount },
{ "Threads_connected", VT_INT, (STATSFUNC)serviceSessionCountAll },
{ "Connections", VT_INT, (STATSFUNC)maxinfo_all_dcbs },
{ "Client_connections", VT_INT, (STATSFUNC)maxinfo_client_dcbs },
{ "Backend_connections", VT_INT, (STATSFUNC)maxinfo_backend_dcbs },
{ "Listeners", VT_INT, (STATSFUNC)maxinfo_listener_dcbs },
{ "Zombie_connections", VT_INT, (STATSFUNC)maxinfo_zombie_dcbs },
{ "Internal_descriptors", VT_INT, (STATSFUNC)maxinfo_internal_dcbs },
{ "Read_events", VT_INT, (STATSFUNC)maxinfo_read_events },
{ "Write_events", VT_INT, (STATSFUNC)maxinfo_write_events },
{ "Hangup_events", VT_INT, (STATSFUNC)maxinfo_hangup_events },
{ "Error_events", VT_INT, (STATSFUNC)maxinfo_error_events },
{ "Accept_events", VT_INT, (STATSFUNC)maxinfo_accept_events },
{ "Event_queue_length", VT_INT, (STATSFUNC)maxinfo_event_queue_length },
{ "Pending_events", VT_INT, (STATSFUNC)maxinfo_event_pending_queue_length },
{ "Max_event_queue_length", VT_INT, (STATSFUNC)maxinfo_max_event_queue_length },
{ "Max_event_queue_time", VT_INT, (STATSFUNC)maxinfo_max_event_queue_time },
{ "Max_event_execution_time", VT_INT, (STATSFUNC)maxinfo_max_event_exec_time },
{ NULL, 0, NULL }
};
/**
* Callback function to populate rows of the show variable
* command
*
* @param data The context point
* @return The next row or NULL if end of rows
*/
static RESULT_ROW *
status_row(RESULTSET *result, void *data)
{
VARCONTEXT *context = (VARCONTEXT *)data;
RESULT_ROW *row;
char buf[80];
if (status[context->index].name)
{
if (context->like &&
maxinfo_pattern_match(context->like,
status[context->index].name))
{
context->index++;
return status_row(result, data);
}
row = resultset_make_row(result);
resultset_row_set(row, 0, status[context->index].name);
switch (status[context->index].type)
{
case VT_STRING:
resultset_row_set(row, 1,
(char *)(*status[context->index].func)());
break;
case VT_INT:
snprintf(buf, 80, "%ld",
(long)(*status[context->index].func)());
resultset_row_set(row, 1, buf);
break;
}
context->index++;
return row;
}
return NULL;
}
/**
* Execute a show status command applying an optional filter
*
* @param dcb The DCB connected to the client
* @param filter A potential like clause or NULL
*/
static void
exec_show_status(DCB *dcb, MAXINFO_TREE *filter)
{
RESULTSET *result;
VARCONTEXT context;
if (filter)
context.like = filter->value;
else
context.like = NULL;
context.index = 0;
if ((result = resultset_create(status_row, &context)) == NULL)
{
maxinfo_send_error(dcb, 0, "No resources available");
return;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
resultset_stream_mysql(result, dcb);
resultset_free(result);
}
/**
* Return the show status data as a result set
*
* @return The show status data as a result set
*/
RESULTSET *
maxinfo_status()
{
RESULTSET *result;
static VARCONTEXT context;
context.like = NULL;
context.index = 0;
if ((result = resultset_create(status_row, &context)) == NULL)
{
return NULL;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
return result;
}
/**
* Execute a select command parse tree and return the result set
* or runtime error
*
* @param dcb The DCB that connects to the client
* @param tree The parse tree for the query
*/
static void
exec_select(DCB *dcb, MAXINFO_TREE *tree)
{
maxinfo_send_error(dcb, 0, "Select not yet implemented");
}
/**
* Perform a "like" pattern match. Only works for leading and trailing %
*
* @param pattern Pattern to match
* @param str String to match against pattern
* @return Zero on match
*/
static int
maxinfo_pattern_match(char *pattern, char *str)
{
int anchor = 0, len, trailing;
char *fixed;
extern char *strcasestr();
if (*pattern != '%')
{
fixed = pattern;
anchor = 1;
}
else
{
fixed = &pattern[1];
}
len = strlen(fixed);
if (fixed[len - 1] == '%')
trailing = 1;
else
trailing = 0;
if (anchor == 1 && trailing == 0) // No wildcard
return strcasecmp(pattern, str);
else if (anchor == 1)
return strncasecmp(str, pattern, len - trailing);
else
{
char *portion = malloc(len + 1);
int rval;
strncpy(portion, fixed, len - trailing);
portion[len - trailing] = 0;
rval = (strcasestr(str, portion) != NULL ? 0 : 1);
free(portion);
return rval;
}
}

View File

@ -0,0 +1,328 @@
/*
* This file is distributed as part of MaxScale. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright MariaDB Corporation Ab 2014
*/
/**
* @file maxinfo_parse.c - Parse the limited set of SQL that the MaxScale
* information schema can use
*
* @verbatim
* Revision History
*
* Date Who Description
* 16/02/15 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <service.h>
#include <session.h>
#include <router.h>
#include <modules.h>
#include <modinfo.h>
#include <modutil.h>
#include <atomic.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <maxinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
static MAXINFO_TREE *make_tree_node(MAXINFO_OPERATOR, char *, MAXINFO_TREE *, MAXINFO_TREE *);
static void free_tree(MAXINFO_TREE *);
static char *fetch_token(char *, int *, char **);
static MAXINFO_TREE *parse_column_list(char **sql);
static MAXINFO_TREE *parse_table_name(char **sql);
/**
* Parse a SQL subset for the maxinfo plugin and return a parse tree
*
* @param sql The SQL query
* @return Parse tree or NULL on error
*/
MAXINFO_TREE *
maxinfo_parse(char *sql, PARSE_ERROR *parse_error)
{
int token;
char *ptr, *text;
MAXINFO_TREE *tree = NULL;
MAXINFO_TREE *col, *table;
*parse_error = PARSE_NOERROR;
while ((ptr = fetch_token(sql, &token, &text)) != NULL)
{
switch (token)
{
case LT_SHOW:
free(text); // not needed
ptr = fetch_token(ptr, &token, &text);
if (ptr == NULL || token != LT_STRING)
{
// Expected show "name"
*parse_error = PARSE_MALFORMED_SHOW;
return NULL;
}
tree = make_tree_node(MAXOP_SHOW, text, NULL, NULL);
if ((ptr = fetch_token(ptr, &token, &text)) == NULL)
return tree;
else if (token == LT_LIKE)
{
if ((ptr = fetch_token(ptr, &token, &text)) != NULL)
{
tree->right = make_tree_node(MAXOP_LIKE,
text, NULL, NULL);
return tree;
}
else
{
// Expected expression
*parse_error = PARSE_EXPECTED_LIKE;
free_tree(tree);
return NULL;
}
}
// Malformed show
free(text);
free_tree(tree);
*parse_error = PARSE_MALFORMED_SHOW;
return NULL;
#if 0
case LT_SELECT:
free(text); // not needed
col = parse_column_list(&ptr);
table = parse_table_name(&ptr);
return make_tree_node(MAXOP_SELECT, NULL, col, table);
#endif
default:
*parse_error = PARSE_SYNTAX_ERROR;
if (tree)
free_tree(tree);
return NULL;
}
}
*parse_error = PARSE_SYNTAX_ERROR;
if (tree)
free_tree(tree);
return NULL;
}
/**
* Parse a column list, may be a * or a valid list of string name
* separated by a comma
*
* @param sql Pointer to pointer to column list updated to point to the table name
* @return A tree of column names
*/
static MAXINFO_TREE *
parse_column_list(char **ptr)
{
int token, lookahead;
char *text, *text2;
MAXINFO_TREE *tree = NULL;
MAXINFO_TREE * rval = NULL;
*ptr = fetch_token(*ptr, &token, &text);
*ptr = fetch_token(*ptr, &lookahead, &text2);
switch (token)
{
case LT_STRING:
switch (lookahead)
{
case LT_COMMA:
rval = make_tree_node(MAXOP_COLUMNS, text, NULL,
parse_column_list(ptr));
break;
case LT_FROM:
rval = make_tree_node(MAXOP_COLUMNS, text, NULL,
NULL);
break;
default:
break;
}
break;
case LT_STAR:
if (lookahead != LT_FROM)
rval = make_tree_node(MAXOP_ALL_COLUMNS, NULL, NULL,
NULL);
break;
default:
break;
}
free(text);
free(text2);
return rval;
}
/**
* Parse a table name
*
* @param sql Pointer to pointer to column list updated to point to the table name
* @return A tree of table names
*/
static MAXINFO_TREE *
parse_table_name(char **ptr)
{
int token;
char *text;
MAXINFO_TREE *tree = NULL;
*ptr = fetch_token(*ptr, &token, &text);
if (token == LT_STRING)
return make_tree_node(MAXOP_TABLE, text, NULL, NULL);
free(text);
return NULL;
}
/**
* Allocate and populate a parse tree node
*
* @param op The node operator
* @param value The node value
* @param left The left branch of the parse tree
* @param right The right branch of the parse tree
* @return The new parse tree node
*/
static MAXINFO_TREE *
make_tree_node(MAXINFO_OPERATOR op, char *value, MAXINFO_TREE *left, MAXINFO_TREE *right)
{
MAXINFO_TREE *node;
if ((node = (MAXINFO_TREE *)malloc(sizeof(MAXINFO_TREE))) == NULL)
return NULL;
node->op = op;
node->value = value;
node->left = left;
node->right = right;
return node;
}
/**
* Recusrsively free the storage associated with a parse tree
*
* @param tree The parse tree to free
*/
static void
free_tree(MAXINFO_TREE *tree)
{
if (tree->left)
free_tree(tree->left);
if (tree->right)
free_tree(tree->right);
if (tree->value)
free(tree->value);
free(tree);
}
/**
* The set of keywords known to the tokeniser
*/
static struct {
char *text;
int token;
} keywords[] = {
{ "show", LT_SHOW },
{ "select", LT_SELECT },
{ "from", LT_FROM },
{ "like", LT_LIKE },
{ "=", LT_EQUAL },
{ ",", LT_COMMA },
{ "*", LT_STAR },
{ NULL, 0 }
};
/**
* Limited SQL tokeniser. Understands a limited set of key words and
* quoted strings.
*
* @param sql The SQL to tokenise
* @param token The returned token
* @param text The matching text
* @return The next position to tokenise from
*/
static char *
fetch_token(char *sql, int *token, char **text)
{
char *s1, *s2, quote = '\0';
int i;
s1 = sql;
while (*s1 && isspace(*s1))
{
s1++;
}
if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
{
quote = *s1++;
}
if (*s1 == '/' && *(s1 + 1) == '*')
{
s1 += 2;
// Skip the comment
do {
while (*s1 && *s1 != '*')
s1++;
} while (*(s1 + 1) && *(s1 + 1) != '/');
s1 += 2;
while (*s1 && isspace(*s1))
{
s1++;
}
if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
{
quote = *s1++;
}
}
s2 = s1;
while (*s2)
{
if (quote == '\0' && (isspace(*s2)
|| *s2 == ',' || *s2 == '='))
break;
else if (quote == *s2)
{
break;
}
s2++;
}
if (*s1 == '@' && *(s1 + 1) == '@')
{
*text = strndup(s1 + 2, (s2 - s1) - 2);
*token = LT_VARIABLE;
return s2;
}
if (s1 == s2)
return NULL;
*text = strndup(s1, s2 - s1);
for (i = 0; keywords[i].text; i++)
{
if (strcasecmp(keywords[i].text, *text) == 0)
{
*token = keywords[i].token;
return s2;
}
}
*token = LT_STRING;
return s2;
}

View File

@ -311,6 +311,11 @@ char *weightby;
inst->bitmask |= (SERVER_MASTER|SERVER_SLAVE);
inst->bitvalue |= SERVER_SLAVE;
}
else if (!strcasecmp(options[i], "running"))
{
inst->bitmask |= (SERVER_RUNNING);
inst->bitvalue |= SERVER_RUNNING;
}
else if (!strcasecmp(options[i], "synced"))
{
inst->bitmask |= (SERVER_JOINED);
@ -333,7 +338,12 @@ char *weightby;
}
}
}
if(inst->bitmask == 0 && inst->bitvalue == 0)
{
/** No parameters given, use RUNNING as a valid server */
inst->bitmask |= (SERVER_RUNNING);
inst->bitvalue |= SERVER_RUNNING;
}
/*
* We have completed the creation of the instance data, so now
* insert this router instance into the linked list of routers

View File

@ -790,6 +790,7 @@ static void* newSession(
#endif
client_rses->router = router;
client_rses->client_dcb = session->client;
/**
* If service config has been changed, reload config from service to
* router instance first.
@ -3509,8 +3510,6 @@ static bool select_connect_backend_servers(
atomic_add(&backend_ref[i].bref_backend->backend_conn_count, -1);
}
}
master_connected = false;
slaves_connected = 0;
}
return_succp:
@ -3755,10 +3754,11 @@ static GWBUF* sescmd_cursor_process_replies(
bref_clear_state(bref,BREF_IN_USE);
bref_set_state(bref,BREF_CLOSED);
bref_set_state(bref,BREF_SESCMD_FAILED);
dcb_close(bref->bref_dcb);
if(bref->bref_dcb)
dcb_close(bref->bref_dcb);
*reconnect = true;
if(replybuf)
gwbuf_free(replybuf);
gwbuf_consume(replybuf,gwbuf_length(replybuf));
}
}
/** This is a response from the master and it is the "right" one.
@ -3788,7 +3788,8 @@ static GWBUF* sescmd_cursor_process_replies(
bref_clear_state(&ses->rses_backend_ref[i],BREF_IN_USE);
bref_set_state(&ses->rses_backend_ref[i],BREF_CLOSED);
bref_set_state(bref,BREF_SESCMD_FAILED);
dcb_close(ses->rses_backend_ref[i].bref_dcb);
if(ses->rses_backend_ref[i].bref_dcb)
dcb_close(ses->rses_backend_ref[i].bref_dcb);
*reconnect = true;
}
}
@ -4345,6 +4346,21 @@ static bool route_session_write(
goto return_succp;
}
if(router_cli_ses->rses_config.rw_max_sescmd_history_size > 0 &&
router_cli_ses->rses_nsescmd >= router_cli_ses->rses_config.rw_max_sescmd_history_size)
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Router session exceeded session command history limit. "
"Closing router session. <")));
gwbuf_free(querybuf);
rses_end_locked_router_action(router_cli_ses);
router_cli_ses->client_dcb->func.hangup(router_cli_ses->client_dcb);
goto return_succp;
}
/**
* Additional reference is created to querybuf to
* prevent it from being released before properties
@ -4416,6 +4432,9 @@ static bool route_session_write(
}
}
}
atomic_add(&router_cli_ses->rses_nsescmd,1);
/** Unlock router session */
rses_end_locked_router_action(router_cli_ses);
@ -4515,6 +4534,10 @@ static void rwsplit_process_router_options(
router->rwsplit_config.rw_slave_select_criteria = c;
}
}
else if(strcmp(options[i], "max_sescmd_history") == 0)
{
router->rwsplit_config.rw_max_sescmd_history_size = atoi(value);
}
}
} /*< for */
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
if(MYSQLCLIENT_FOUND AND BUILD_TESTS)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/test.cmake @ONLY)
add_executable(testschemarouter testschemarouter.c)
target_link_libraries(testschemarouter ${MYSQLCLIENT_LIBRARIES} ssl crypto dl z m rt pthread)
add_executable(testschemarouter2 testschemarouter2.c)
target_link_libraries(testschemarouter2 ${MYSQLCLIENT_LIBRARIES} ssl crypto dl z m rt pthread)
add_test(NAME TestSchemaRouter COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/test.cmake)
endif()

View File

@ -0,0 +1,9 @@
#!/bin/bash
if [ $# -lt 1 ]
then
echo "Usage $0 <port to block>"
exit 1
fi
sudo iptables -I INPUT 1 -i lo -p tcp --dport $1 -j DROP
sudo iptables -I INPUT 1 -i lo -p tcp --sport $1 -j DROP
echo "Traffic to port $1 blocked."

View File

@ -0,0 +1,19 @@
#!/bin/bash
if [ $# -lt 5 ]
then
printf "Not enough arguments: '"
for i in $@
do
printf "$i "
done
echo "'given, 5 needed."
echo "usage $0 <host> <port> <username> <password> <database name>"
exit 1
fi
HOST=$1
PORT=$2
USER=$3
PW=$4
SHD=$5
mysql -u $USER -p$PW -P $PORT -h $HOST -e "create database $SHD;"
echo "Created database \"$SHD\" at $HOST:$PORT"

View File

@ -0,0 +1,17 @@
set(SCHEMAROUTER_TEST_PORTS 3000 3001 3002 3003)
foreach(VAR ${SCHEMAROUTER_TEST_PORTS})
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/prepare_shard.sh @TEST_HOST@ ${VAR} @TEST_USER@ @TEST_PASSWORD@ "db${VAR}")
endforeach()
execute_process(COMMAND ${CMAKE_CURRENT_BINARY_DIR}/testschemarouter @TEST_HOST@ @TEST_PORT_DB@ @TEST_USER@ @TEST_PASSWORD@ RESULT_VARIABLE RVAL)
if(RVAL EQUAL 0)
message("Test 1 passed.")
else()
message(FATAL_ERROR "Test 1 failed with code ${RVAL}.")
endif()
execute_process(COMMAND ${CMAKE_CURRENT_BINARY_DIR}/testschemarouter2 @TEST_HOST@ @TEST_PORT_DB@ @TEST_USER@ @TEST_PASSWORD@ RESULT_VARIABLE RVAL2)
if(RVAL2 EQUAL 0)
message("Test 2 passed.")
else()
message(FATAL_ERROR "Test 2 failed with code ${RVAL2}.")
endif()

View File

@ -0,0 +1,210 @@
#include <my_config.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysqld_error.h>
int main(int argc, char** argv)
{
/**
* This test sets a session variable, creates tables in each of the shards
* and inserts into them a single value while querying the session variable.
* This will show if the value in the session variable in the shard is set and
* if it is the same in all the shards.
*
* The test fails if any of the session variables is not set or differ from the original value.
*/
MYSQL* server;
MYSQL_RES *result,*shdres;
MYSQL_ROW row;
char *host = NULL,*username = NULL, *password = NULL;
char query[2048];
unsigned int port,errnum;
unsigned long *lengths;
int rval;
if(argc < 5)
{
fprintf(stderr,"Usage: %s <host> <port> <username> <password>\n",argv[0]);
return 1;
}
host = strdup(argv[1]);
port = atoi(argv[2]);
username = strdup(argv[3]);
password = strdup(argv[4]);
rval = 0;
printf("Connecting to %s:%d as %s/%s\n",host,port,username,password);
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
if(mysql_real_connect(server,host,username,password,NULL,port,NULL,0) == NULL){
fprintf(stderr, "Failed to connect to database: %s\n",
mysql_error(server));
rval = 1;
goto report;
}
if(mysql_real_query(server,
"SET @test=123",
strlen("SET @test=123")))
{
fprintf(stderr, "Failed to set session variable: %s.\n",
mysql_error(server));
rval = 1;
goto report;
}
if((result = mysql_list_dbs(server,NULL)) == NULL){
fprintf(stderr, "Failed to query databases: %s\n",
mysql_error(server));
rval = 1;
goto report;
}
if(mysql_field_count(server) != 1)
{
fprintf(stderr, "SHOW DATABASES returned an unexpected result.\n");
rval = 1;
goto report;
}
while((row = mysql_fetch_row(result)))
{
char* dbname = strdup(row[0]);
printf("Testing database %-32s",dbname);
sprintf(query,"DROP TABLE IF EXISTS %s.t1",dbname);
if(mysql_real_query(server,(const char*)query,strlen(query)))
{
errnum = mysql_errno(server);
if(errnum != ER_DBACCESS_DENIED_ERROR &&
errnum != ER_ACCESS_DENIED_ERROR)
{
fprintf(stderr, "DROP TABLE failed in %s: %d: %s.\n",dbname,mysql_errno(server),mysql_error(server));
}
printf("NO PERMISSION\n");
continue;
}
sprintf(query,"CREATE TABLE %s.t1 (id INT)",dbname);
if(mysql_real_query(server,(const char*)query,strlen(query)))
{
errnum = mysql_errno(server);
if( errnum == ER_TABLEACCESS_DENIED_ERROR)
{
sprintf(query,"DROP TABLE IF EXISTS %s.t1",dbname);
mysql_real_query(server,(const char*)query,strlen(query));
printf("NO PERMISSION\n");
continue;
}
fprintf(stderr, "CREATE TABLE failed in %s: %d: %s.\n",
dbname,mysql_errno(server),mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"INSERT INTO %s.t1 VALUES (1);",dbname);
if(mysql_real_query(server,(const char*)query,strlen(query)))
{
fprintf(stderr, "Query to server failed: %d: %s.\n",
mysql_errno(server),mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"SELECT ID FROM %s.t1 UNION SELECT @test",dbname);
if(mysql_real_query(server,(const char*)query,strlen(query)))
{
fprintf(stderr, "Query to server failed: %d: %s.\n",
mysql_errno(server),mysql_error(server));
rval = 1;
goto report;
}
if((shdres = mysql_store_result(server)) == NULL)
{
fprintf(stderr, "Failed to fetch result set: %d: %s\n",
mysql_errno(server),mysql_error(server));
rval = 1;
goto report;
}
if(mysql_field_count(server) != 1)
{
fprintf(stderr, "Returned field count value did not match the expected value.\n");
rval = 1;
goto report;
}
/**Fetch the two rows, the inserted value and the session variable*/
if(mysql_fetch_row(shdres) == NULL ||
(row = mysql_fetch_row(shdres)) == NULL )
{
fprintf(stderr, "Number of returned rows did not match the expected value.\n");
rval = 1;
goto report;
}
if((lengths = mysql_fetch_lengths(shdres)) == NULL)
{
fprintf(stderr, "Failed to retrieve row lengths: %d: %s.\n",
mysql_errno(server),mysql_error(server));
rval = 1;
goto report;
}
if(lengths[0] != 3 || strcmp(row[0],"123"))
{
rval = 1;
printf(" FAILED\n");
printf( "Reason: Session variable was %s instead of \"123\".\n",row[0]);
}
else
{
printf("OK\n");
}
sprintf(query,"DROP TABLE %s.t1;",dbname);
if(mysql_real_query(server,(const char*)query,strlen(query)))
{
fprintf(stderr, "Query to server failed: %s.\n",mysql_error(server));
rval = 1;
goto report;
}
free(dbname);
mysql_free_result(shdres);
}
mysql_free_result(result);
mysql_close(server);
report:
if(rval){
printf("\nTest failed: Errors during test run.\n");
}
free(host);
free(username);
free(password);
return rval;
}

View File

@ -0,0 +1,225 @@
#include <my_config.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysqld_error.h>
int main(int argc, char** argv)
{
/**
* This test sets a session variable, creates tables in each of the shards
* and inserts into them a single value while querying the session variable.
* This will show if the value in the session variable in the shard is set and
* if it is the same in all the shards.
*
* The test fails if any of the session variables is not set or differ from the original value.
*/
const unsigned int ports[4] = {
3000,
3001,
3002,
3003
};
const char* srv_id[4] = {
"3000",
"3001",
"3002",
"3003"
};
const char* databases[4] = {
"db0",
"db1",
"db2",
"db3"
};
MYSQL* server;
MYSQL_RES *result,*shdres;
MYSQL_ROW row;
char *host = NULL,*username = NULL, *password = NULL;
char query[2048];
unsigned int port,errnum,optval;
unsigned long *lengths;
int rval, i, j;
if(argc < 5)
{
fprintf(stderr,"Usage: %s <host> <port> <username> <password>\n",argv[0]);
return 1;
}
host = strdup(argv[1]);
port = atoi(argv[2]);
username = strdup(argv[3]);
password = strdup(argv[4]);
rval = 0;
for(i = 0;i<4;i++)
{
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
optval = 1;
mysql_options(server,MYSQL_OPT_CONNECT_TIMEOUT,&optval);
if(mysql_real_connect(server,host,username,password,NULL,ports[i],NULL,0) == NULL){
fprintf(stderr, "Failed to connect to server on port %d: %s\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"STOP SLAVE");
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to stop slave in %d: %s.\n",
ports[i],
mysql_error(server));
}
for(j = 0;j<4;j++)
{
sprintf(query,"DROP DATABASE IF EXISTS %s",databases[j]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to drop database in %d: %s.\n",
ports[i],
mysql_error(server));
}
}
mysql_close(server);
}
for(i=0;i<4;i++)
{
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
mysql_options(server,MYSQL_OPT_CONNECT_TIMEOUT,&optval);
if(mysql_real_connect(server,host,username,password,NULL,ports[i],NULL,0) == NULL){
fprintf(stderr, "Failed to connect to server on port %d: %s\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"CREATE DATABASE %s",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to create table in %d: %s.\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"DROP TABLE IF EXISTS %s.t1",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to drop table in %d: %s.\n",
ports[i],
mysql_error(server));
}
sprintf(query,"CREATE TABLE %s.t1 (id int)",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to create table in %d: %s.\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"INSERT INTO %s.t1 values (%s)",databases[i],srv_id[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to insert values in %d: %s.\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
mysql_close(server);
}
for(i = 0;i<4;i++)
{
printf("Testing server on port %d through MaxScale.\n",ports[i]);
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
if(mysql_real_connect(server,host,username,password,databases[i],port,NULL,0) == NULL){
fprintf(stderr, "Failed to connect to port %d using database %s: %s\n",
port,
databases[i],
mysql_error(server));
rval = 1;
goto report;
}
if(mysql_real_query(server,"SELECT id FROM t1",strlen("SELECT id FROM t1")))
{
fprintf(stderr, "Failed to execute query in %d: %s.\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
result = mysql_store_result(server);
while((row = mysql_fetch_row(result)))
{
if(strcmp(row[0],srv_id[i]))
{
fprintf(stderr, "Test failed in %d: Was expecting %s but got %s instead.\n",
ports[i],srv_id[i],row[0]);
rval = 1;
}
}
mysql_free_result(result);
if(i > 0 && mysql_real_query(server,"START SLAVE",strlen("START SLAVE")))
{
fprintf(stderr, "Failed to start slave in %d: %s.\n",
ports[i],
mysql_error(server));
}
mysql_close(server);
}
report:
if(rval){
printf("\nTest failed: Errors during test run.\n");
}
free(host);
free(username);
free(password);
return rval;
}

View File

@ -146,12 +146,7 @@ int main(int argc, char** argv)
double test_res = real_test.tv_sec + (real_test.tv_usec / 1000000.0);
result = test_res/base_res;
if(result > ratio){
printf("\nTest failed: Time ratio was %f which exceeded the limit of %f.\n", result, ratio);
rval = 1;
}else{
printf("\nTest passed: Time ratio was %f.\n",result);
}
printf("\nTest passed: Time ratio was %f.\n",result);
}
free(str_baseline);
free(str_test);