Merge branch 'develop' of github.com:skysql/MaxScale into develop

This commit is contained in:
Mark Riddoch 2014-11-14 10:37:02 +00:00
commit e967aca2f0
46 changed files with 2891 additions and 907 deletions

View File

@ -66,6 +66,11 @@ if(GCOV)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
endif()
if(FAKE_CODE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFAKE_CODE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFAKE_CODE")
endif()
subdirs(MYSQL_DIR_ALL ${MYSQL_DIR})
foreach(DIR ${MYSQL_DIR_ALL})
include_directories(${DIR})
@ -152,7 +157,7 @@ add_custom_target(buildtests
add_custom_target(testall
COMMAND ${CMAKE_COMMAND} -DDEPS_OK=Y -DBUILD_TESTS=Y -DBUILD_TYPE=Debug -DINSTALL_DIR=${CMAKE_BINARY_DIR} -DINSTALL_SYSTEM_FILES=N ${CMAKE_SOURCE_DIR}
COMMAND make install
COMMAND cp ${CMAKE_SOURCE_DIR}/server/test/MaxScale_test.cnf ${CMAKE_BINARY_DIR}/etc/MaxScale.cnf
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/server/test/MaxScale_test.cnf ${CMAKE_BINARY_DIR}/etc/MaxScale.cnf
COMMAND /bin/sh -c "${CMAKE_BINARY_DIR}/bin/maxscale -c ${CMAKE_BINARY_DIR} &>/dev/null"
COMMAND /bin/sh -c "make test || echo \"Test results written to: ${CMAKE_BINARY_DIR}/Testing/Temporary/\""
COMMAND killall maxscale

View File

@ -162,7 +162,8 @@ struct logfile_st {
mlist_t lf_blockbuf_list;
int lf_buf_size;
bool lf_flushflag;
int lf_spinlock; /**< lf_flushflag */
bool lf_rotateflag;
int lf_spinlock; /**< lf_flushflag & lf_rotateflag */
int lf_npending_writes;
#if defined(SS_DEBUG)
skygw_chk_t lf_chk_tail;
@ -232,6 +233,11 @@ static bool logfile_init(
static void logfile_done(logfile_t* logfile);
static void logfile_free_memory(logfile_t* lf);
static void logfile_flush(logfile_t* lf);
static void logfile_rotate(logfile_t* lf);
static bool logfile_create(logfile_t* lf);
static bool logfile_open_file(filewriter_t* fw, logfile_t* lf);
static char* form_full_file_name(strpart_t* parts, int seqno, int seqnoidx);
static bool filewriter_init(
logmanager_t* logmanager,
filewriter_t* fw,
@ -249,14 +255,16 @@ static bool logmanager_register(bool writep);
static void logmanager_unregister(void);
static bool logmanager_init_nomutex(int argc, char* argv[]);
static void logmanager_done_nomutex(void);
static int logmanager_write_log(
logfile_id_t id,
bool flush,
bool use_valist,
bool spread_down,
size_t len,
const char* str,
va_list valist);
bool flush,
bool use_valist,
bool spread_down,
bool rotate,
size_t len,
const char* str,
va_list valist);
static blockbuf_t* blockbuf_init(logfile_id_t id);
static void blockbuf_node_done(void* bb_data);
@ -276,6 +284,8 @@ static bool check_file_and_path(
bool* writable);
static bool file_is_symlink(char* filename);
static int skygw_log_disable_raw(logfile_id_t id, bool emergency); /*< no locking */
const char* get_suffix_default(void)
{
@ -391,8 +401,10 @@ static bool logmanager_init_nomutex(
*/
lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
/** Initialize filewriter data and open the (first) log file(s)
* for each log file type. */
/**
* Initialize filewriter data and open the log file
* for each log file type.
*/
if (!filewriter_init(lm, fw, lm->lm_clientmes, lm->lm_logmes))
{
err = 1;
@ -627,6 +639,8 @@ static logfile_t* logmanager_get_logfile(
*
* @param spread_down - in, use
* if true, log string is spread to all logs having larger id.
*
* @param rotate if set, closes currently open log file and opens a new one
*
* @param str_len - in, use
* length of formatted string
@ -648,6 +662,7 @@ static int logmanager_write_log(
bool flush,
bool use_valist,
bool spread_down,
bool rotate,
size_t str_len,
const char* str,
va_list valist)
@ -671,6 +686,7 @@ static int logmanager_write_log(
true,
false,
false,
false,
strlen(errstr)+1,
errstr,
valist);
@ -687,14 +703,21 @@ static int logmanager_write_log(
CHK_LOGFILE(lf);
/**
* When string pointer is NULL, case is skygw_log_flush and no
* writing is involved. With flush && str != NULL case is
* skygw_log_write_flush.
* When string pointer is NULL, operation is either flush or rotate.
*/
if (str == NULL) {
ss_dassert(flush);
logfile_flush(lf); /**< here we wake up file writer */
} else {
if (str == NULL)
{
if (flush)
{
logfile_flush(lf); /*< wakes up file writer */
}
else if (rotate)
{
logfile_rotate(lf); /*< wakes up file writer */
}
}
else
{
/** Length of string that will be written, limited by bufsize */
int safe_str_len;
@ -702,14 +725,13 @@ static int logmanager_write_log(
/** Findout how much can be safely written with current block size */
if (timestamp_len-1+str_len > lf->lf_buf_size)
{
safe_str_len = lf->lf_buf_size;
}
{
safe_str_len = lf->lf_buf_size;
}
else
{
safe_str_len = timestamp_len-1+str_len;
}
{
safe_str_len = timestamp_len-1+str_len;
}
/**
* Seek write position and register to block buffer.
* Then print formatted string to write position.
@ -717,32 +739,26 @@ static int logmanager_write_log(
#if defined (SS_LOG_DEBUG)
{
char *copy,*tok;
int tokval;
char *copy,*tok;
int tokval;
simple_mutex_lock(&msg_mutex,true);
simple_mutex_lock(&msg_mutex,true);
copy = strdup(str);
tok = strtok(copy,"|");
tok = strtok(NULL,"|");
copy = strdup(str);
tok = strtok(copy,"|");
tok = strtok(NULL,"|");
if(strstr(str,"message|") && tok){
tokval = atoi(tok);
if(prevval > 0){
ss_dassert(tokval == (prevval + 1));
}
prevval = tokval;
}
free(copy);
simple_mutex_unlock(&msg_mutex);
if(strstr(str,"message|") && tok)
{
tokval = atoi(tok);
if(prevval > 0)
{
ss_dassert(tokval == (prevval + 1));
}
prevval = tokval;
}
free(copy);
simple_mutex_unlock(&msg_mutex);
}
#endif
@ -799,7 +815,7 @@ static int logmanager_write_log(
if (wp[safe_str_len-2] == '\n')
{
wp[safe_str_len-2]=' ';
}
}
wp[safe_str_len-1] = '\n';
blockbuf_unregister(bb);
@ -858,7 +874,7 @@ static int logmanager_write_log(
blockbuf_unregister(bb_c);
}
} /* if (spread_down) */
}
} /* if (str == NULL) */
return_err:
return err;
@ -1185,25 +1201,36 @@ return_err:
return err;
}
int skygw_log_disable(
logfile_id_t id)
logfile_id_t id) /*< no locking */
{
int rc;
rc = skygw_log_disable_raw(id, false);
return rc;
}
static int skygw_log_disable_raw(
logfile_id_t id,
bool emergency) /*< no locking */
{
bool err = 0;
if (!logmanager_register(true)) {
//fprintf(stderr, "ERROR: Can't register to logmanager\n");
if (!logmanager_register(true))
{
err = -1;
goto return_err;
}
CHK_LOGMANAGER(lm);
if (logfile_set_enabled(id, false)) {
lm->lm_enabled_logfiles &= ~id;
/**
* Set global variable
*/
lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
if (emergency || logfile_set_enabled(id, false))
{
lm->lm_enabled_logfiles &= ~id;
/**
* Set global variable
*/
lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
}
logmanager_unregister();
@ -1234,6 +1261,7 @@ static bool logfile_set_enabled(
true,
false,
false,
false,
strlen(errstr)+1,
errstr,
notused);
@ -1259,6 +1287,7 @@ static bool logfile_set_enabled(
true,
false,
false,
false,
strlen(logstr)+1,
logstr,
notused);
@ -1304,9 +1333,6 @@ int skygw_log_write_flush(
/**
* Find out the length of log string (to be formatted str).
*/
va_start(valist, str);
len = vsnprintf(NULL, 0, str, valist);
va_end(valist);
@ -1318,7 +1344,7 @@ int skygw_log_write_flush(
* Write log string to buffer and add to file write list.
*/
va_start(valist, str);
err = logmanager_write_log(id, true, true, true, len, str, valist);
err = logmanager_write_log(id, true, true, true, false, len, str, valist);
va_end(valist);
if (err != 0) {
@ -1357,9 +1383,6 @@ int skygw_log_write(
err = 1;
goto return_unregister;
}
/**
* Find out the length of log string (to be formatted str).
*/
@ -1375,7 +1398,7 @@ int skygw_log_write(
*/
va_start(valist, str);
err = logmanager_write_log(id, false, true, true, len, str, valist);
err = logmanager_write_log(id, false, true, true, false, len, str, valist);
va_end(valist);
if (err != 0) {
@ -1402,7 +1425,7 @@ int skygw_log_flush(
goto return_err;
}
CHK_LOGMANAGER(lm);
err = logmanager_write_log(id, true, false, false, 0, NULL, valist);
err = logmanager_write_log(id, true, false, false, false, 0, NULL, valist);
if (err != 0) {
fprintf(stderr, "skygw_log_flush failed.\n");
@ -1415,6 +1438,60 @@ return_err:
return err;
}
/**
* Replace current logfile with new file with increased sequence number on
* its name.
*/
int skygw_log_rotate(
logfile_id_t id)
{
int err = 0;
logfile_t* lf;
va_list valist; /**< Dummy, must be present but it is not processed */
if (!logmanager_register(false))
{
ss_dfprintf(stderr,
"Can't register to logmanager, rotating failed\n");
goto return_err;
}
CHK_LOGMANAGER(lm);
lf = &lm->lm_logfile[id];
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Log rotation is called for %s.",
lf->lf_full_file_name)));
err = logmanager_write_log(id, false, false, false, true, 0, NULL, valist);
if (err != 0)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Log file rotation failed for file %s.",
lf->lf_full_file_name)));
fprintf(stderr, "skygw_log_rotate failed.\n");
goto return_unregister;
}
return_unregister:
LOGIF(LM, (skygw_log_write_flush(
LOGFILE_MESSAGE,
"File %s use for log writing..",
lf->lf_full_file_name)));
logmanager_unregister();
return_err:
return err;
}
/**
* @node Register as a logging client to logmanager.
*
@ -1828,6 +1905,237 @@ static void logfile_flush(
skygw_message_send(lf->lf_logmes);
}
/**
* Set rotate flag for a log file and wake up the writer thread which then
* performs the actual rotation task.
*
* @param lf logfile pointer
*/
static void logfile_rotate(
logfile_t* lf)
{
CHK_LOGFILE(lf);
acquire_lock(&lf->lf_spinlock);
lf->lf_rotateflag = true;
release_lock(&lf->lf_spinlock);
skygw_message_send(lf->lf_logmes);
}
/**
* Forms complete path name for logfile and tests that the file doesn't conflict
* with any existing file and it is writable.
*
* @param lf logfile pointer
*
* @return true if succeed, false if failed
*/
static bool logfile_create(
logfile_t* lf)
{
bool namecreatefail;
bool nameconflicts;
bool store_shmem;
bool writable;
bool succp;
strpart_t spart[3]; /*< string parts of which the file is composed of */
/**
* sparts is an array but next pointers are used to walk through
* the list of string parts.
*/
spart[0].sp_next = &spart[1];
spart[1].sp_next = &spart[2];
spart[2].sp_next = NULL;
spart[1].sp_string = lf->lf_name_prefix;
spart[2].sp_string = lf->lf_name_suffix;
store_shmem = lf->lf_store_shmem;
do {
namecreatefail = false;
nameconflicts = false;
spart[0].sp_string = lf->lf_filepath;
/**
* Create name for log file. Seqno is added between prefix &
* suffix (index == 2)
*/
lf->lf_full_file_name =
form_full_file_name(spart, lf->lf_name_seqno, 2);
if (store_shmem)
{
spart[0].sp_string = lf->lf_linkpath;
/**
* Create name for link file
*/
lf->lf_full_link_name = form_full_file_name(
spart,
lf->lf_name_seqno,
2);
}
/**
* At least one of the files couldn't be created. Increase
* sequence number and retry until succeeds.
*/
if (lf->lf_full_file_name == NULL ||
(store_shmem && lf->lf_full_link_name == NULL))
{
namecreatefail = true;
goto file_create_fail;
}
/**
* If file exists but is different type, create fails and
* new, increased sequence number is added to file name.
*/
if (check_file_and_path(lf->lf_full_file_name, &writable))
{
/** Found similarly named file which isn't writable */
if (!writable || file_is_symlink(lf->lf_full_file_name))
{
nameconflicts = true;
goto file_create_fail;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_succp;
}
}
if (store_shmem)
{
if (check_file_and_path(lf->lf_full_file_name, &writable))
{
/** Found similarly named file which isn't writable */
if (!writable ||
file_is_symlink(lf->lf_full_file_name))
{
unlink(lf->lf_full_file_name);
nameconflicts = true;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_succp;
}
}
}
file_create_fail:
if (namecreatefail || nameconflicts)
{
lf->lf_name_seqno += 1;
if (lf->lf_full_file_name != NULL)
{
free(lf->lf_full_file_name);
lf->lf_full_file_name = NULL;
}
if (lf->lf_full_link_name != NULL)
{
free(lf->lf_full_link_name);
lf->lf_full_link_name = NULL;
}
}
} while (namecreatefail || nameconflicts);
succp = true;
return_succp:
return succp;
}
/**
* Opens a log file and writes header to the beginning of it. File name, FILE*,
* and file descriptor are stored to skygw_file_t struct which is stored in
* filewriter strcuture passed as parameter.
*
* @param fw filewriter pointer
* @param lf logfile pointer
*
* @return true if succeed; the resulting skygw_file_t is written in filewriter,
* false if failed.
*
*/
static bool logfile_open_file(
filewriter_t* fw,
logfile_t* lf)
{
bool succp;
char* start_msg_str;
int err;
if (lf->lf_store_shmem)
{
/** Create symlink pointing to log file */
fw->fwr_file[lf->lf_id] = skygw_file_init(
lf->lf_full_file_name,
lf->lf_full_link_name);
}
else
{
/** Create normal disk-resident log file */
fw->fwr_file[lf->lf_id] = skygw_file_init(
lf->lf_full_file_name,
NULL);
}
if (fw->fwr_file[lf->lf_id] == NULL)
{
fprintf(stderr,
"Error : opening logfile %s failed.\n",
lf->lf_full_file_name);
succp = false;
goto return_succp;
}
if (lf->lf_enabled)
{
start_msg_str = strdup("---\tLogging is enabled.\n");
}
else
{
start_msg_str = strdup("---\tLogging is disabled.\n");
}
err = skygw_file_write(fw->fwr_file[lf->lf_id],
(void *)start_msg_str,
strlen(start_msg_str),
true);
if (err != 0)
{
fprintf(stderr,
"Error : writing to file %s failed due to %d, %s. "
"Exiting MaxScale.\n",
lf->lf_full_file_name,
err,
strerror(err));
succp = false;
goto return_succp;
}
free(start_msg_str);
succp = true;
return_succp:
return succp;
}
/**
* @node Combine all name parts from left to right.
@ -2109,12 +2417,6 @@ static bool logfile_init(
{
bool succp = false;
fnames_conf_t* fn = &logmanager->lm_fnames_conf;
/** string parts of which the file is composed of */
strpart_t strparts[3];
bool namecreatefail = false;
bool nameconflicts = false;
bool writable;
logfile->lf_state = INIT;
#if defined(SS_DEBUG)
logfile->lf_chk_top = CHK_NUM_LOGFILE;
@ -2128,21 +2430,12 @@ static bool logfile_init(
logfile->lf_name_seqno = 1;
logfile->lf_lmgr = logmanager;
logfile->lf_flushflag = false;
logfile->lf_rotateflag= false;
logfile->lf_spinlock = 0;
logfile->lf_store_shmem = store_shmem;
logfile->lf_write_syslog = write_syslog;
logfile->lf_buf_size = MAX_LOGSTRLEN;
logfile->lf_enabled = logmanager->lm_enabled_logfiles & logfile_id;
/**
* strparts is an array but next pointers are used to walk through
* the list of string parts.
*/
strparts[0].sp_next = &strparts[1];
strparts[1].sp_next = &strparts[2];
strparts[2].sp_next = NULL;
strparts[1].sp_string = logfile->lf_name_prefix;
strparts[2].sp_string = logfile->lf_name_suffix;
/**
* If file is stored in shared memory in /dev/shm, a link
* pointing to shm file is created and located to the file
@ -2160,7 +2453,7 @@ static bool logfile_init(
if (c == NULL)
{
succp = false;
goto file_create_fail;
goto return_with_succp;
}
sprintf(c, "%s%d", shm_pathname_prefix, pid);
logfile->lf_filepath = c;
@ -2169,7 +2462,7 @@ static bool logfile_init(
errno != EEXIST)
{
succp = false;
goto file_create_fail;
goto return_with_succp;
}
logfile->lf_linkpath = strdup(fn->fn_logpath);
logfile->lf_linkpath = add_slash(logfile->lf_linkpath);
@ -2179,114 +2472,11 @@ static bool logfile_init(
logfile->lf_filepath = strdup(fn->fn_logpath);
}
logfile->lf_filepath = add_slash(logfile->lf_filepath);
do {
namecreatefail = false;
nameconflicts = false;
strparts[0].sp_string = logfile->lf_filepath;
/**
* Create name for log file. Seqno is added between prefix &
* suffix (index == 2)
*/
logfile->lf_full_file_name =
form_full_file_name(strparts, logfile->lf_name_seqno, 2);
if (store_shmem)
{
strparts[0].sp_string = logfile->lf_linkpath;
/**
* Create name for link file
*/
logfile->lf_full_link_name =
form_full_file_name(strparts,
logfile->lf_name_seqno,
2);
}
/**
* At least one of the files couldn't be created. Increase
* sequence number and retry until succeeds.
*/
if (logfile->lf_full_file_name == NULL ||
(store_shmem && logfile->lf_full_link_name == NULL))
{
namecreatefail = true;
goto file_create_fail;
}
/**
* If file exists but is different type, create fails and
* new, increased sequence number is added to file name.
*/
if (check_file_and_path(
logfile->lf_full_file_name,
&writable))
{
/** Found similarly named file which isn't writable */
if (!writable ||
file_is_symlink(logfile->lf_full_file_name))
{
nameconflicts = true;
goto file_create_fail;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_with_succp;
}
}
if (store_shmem)
{
if (check_file_and_path(
logfile->lf_full_file_name,
&writable))
{
/** Found similarly named file which isn't writable */
if (!writable ||
file_is_symlink(logfile->lf_full_file_name))
{
unlink(logfile->lf_full_file_name);
nameconflicts = true;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_with_succp;
}
}
}
file_create_fail:
if (namecreatefail || nameconflicts)
{
logfile->lf_name_seqno += 1;
if (logfile->lf_full_file_name != NULL)
{
free(logfile->lf_full_file_name);
logfile->lf_full_file_name = NULL;
}
if (logfile->lf_full_link_name != NULL)
{
free(logfile->lf_full_link_name);
logfile->lf_full_link_name = NULL;
}
}
} while (namecreatefail || nameconflicts);
if (!(succp = logfile_create(logfile)))
{
goto return_with_succp;
}
/**
* Create a block buffer list for log file. Clients' writes go to buffers
* from where separate log flusher thread writes them to disk.
@ -2321,11 +2511,11 @@ file_create_fail:
CHK_LOGFILE(logfile);
return_with_succp:
if (!succp) {
if (!succp)
{
logfile_done(logfile);
}
ss_dassert(logfile->lf_state == RUN ||
logfile->lf_state == DONE);
ss_dassert(logfile->lf_state == RUN || logfile->lf_state == DONE);
return succp;
}
@ -2384,16 +2574,14 @@ static void logfile_free_memory(
}
/**
* @node Initialize filewriter struct to a given address
* @node Initialize filewriter data and open the log file for each log file type.
*
* Parameters:
* @param fw - <usage>
* <description>
* @param logmanager Log manager struct
* @param fw File writer struct
* @param clientmes Messaging from file writer to log manager
* @param logmes Messaging from loggers to file writer thread
*
* @return
*
*
* @details (write detailed description here)
* @return true if succeed, false if failed
*
*/
static bool filewriter_init(
@ -2406,7 +2594,6 @@ static bool filewriter_init(
logfile_t* lf;
logfile_id_t id;
int i;
char* start_msg_str;
CHK_LOGMANAGER(logmanager);
@ -2424,47 +2611,25 @@ static bool filewriter_init(
if (fw->fwr_logmes == NULL || fw->fwr_clientmes == NULL) {
goto return_succp;
}
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1) {
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
{
id = (logfile_id_t)i;
lf = logmanager_get_logfile(logmanager, id);
if (lf->lf_store_shmem)
{
/** Create symlink pointing to log file */
fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
lf->lf_full_link_name);
}
else
{
/** Create normal disk-resident log file */
fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
NULL);
}
if (fw->fwr_file[id] == NULL)
if (!(succp = logfile_open_file(fw, lf)))
{
fprintf(stderr,
"Error : opening %s failed, %s. Exiting "
"Error : opening log file %s failed. Exiting "
"MaxScale\n",
lf->lf_full_file_name,
strerror(errno));
goto return_succp;
}
if (lf->lf_enabled) {
start_msg_str = strdup("---\tLogging is enabled.\n");
} else {
start_msg_str = strdup("---\tLogging is disabled.\n");
}
skygw_file_write(fw->fwr_file[id],
(void *)start_msg_str,
strlen(start_msg_str),
true);
free(start_msg_str);
}
lf->lf_full_file_name);
goto return_succp;
}
} /*< for */
fw->fwr_state = RUN;
CHK_FILEWRITER(fw);
succp = true;
return_succp:
if (!succp) {
filewriter_done(fw);
@ -2485,9 +2650,10 @@ static void filewriter_done(
case INIT:
fw->fwr_logmes = NULL;
fw->fwr_clientmes = NULL;
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++) {
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++)
{
id = (logfile_id_t)i;
skygw_file_done(fw->fwr_file[id]);
skygw_file_close(fw->fwr_file[id], true);
}
fw->fwr_state = DONE;
case DONE:
@ -2555,6 +2721,7 @@ static void* thr_filewriter_fun(
blockbuf_state_t flush_blockbuf; /**< flush single block buffer. */
bool flush_logfile; /**< flush logfile */
bool flushall_logfiles;/**< flush all logfiles */
bool rotate_logfile; /*< close current and open new file */
size_t vn1;
size_t vn2;
@ -2576,7 +2743,8 @@ static void* thr_filewriter_fun(
flushall_logfiles = skygw_thread_must_exit(thr);
/** Process all logfiles which have buffered writes. */
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1) {
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
{
retry_flush_on_exit:
/**
* Get file pointer of current logfile.
@ -2585,13 +2753,45 @@ static void* thr_filewriter_fun(
lf = &lm->lm_logfile[(logfile_id_t)i];
/**
* read and reset logfile's flushflag
* read and reset logfile's flush- and rotateflag
*/
acquire_lock(&lf->lf_spinlock);
flush_logfile = lf->lf_flushflag;
lf->lf_flushflag = false;
flush_logfile = lf->lf_flushflag;
rotate_logfile = lf->lf_rotateflag;
lf->lf_flushflag = false;
lf->lf_rotateflag = false;
release_lock(&lf->lf_spinlock);
/**
* Log rotation :
* Close current, and open a new file for the log.
*/
if (rotate_logfile)
{
bool succp;
lf->lf_name_seqno += 1; /*< new sequence number */
if (!(succp = logfile_create(lf)))
{
lf->lf_name_seqno -= 1; /*< restore */
}
else if ((succp = logfile_open_file(fwr, lf)))
{
skygw_file_close(file, false); /*< close old file */
}
if (!succp)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Error : Log rotation failed. "
"Creating replacement file %s "
"failed. Continuing "
"logging to existing file.",
lf->lf_full_file_name)));
}
continue;
}
/**
* get logfile's block buffer list
*/
@ -2603,7 +2803,10 @@ static void* thr_filewriter_fun(
#endif
node = bb_list->mlist_first;
while (node != NULL) {
while (node != NULL)
{
int err = 0;
CHK_MLIST_NODE(node);
bb = (blockbuf_t *)node->mlnode_data;
CHK_BLOCKBUF(bb);
@ -2622,19 +2825,33 @@ static void* thr_filewriter_fun(
* buffer is at least half-full
* -> write to disk
*/
while(bb->bb_refcount > 0) {
while(bb->bb_refcount > 0)
{
simple_mutex_unlock(
&bb->bb_mutex);
simple_mutex_lock(
&bb->bb_mutex,
true);
}
skygw_file_write(file,
(void *)bb->bb_buf,
bb->bb_buf_used,
(flush_logfile ||
flushall_logfiles));
err = skygw_file_write(
file,
(void *)bb->bb_buf,
bb->bb_buf_used,
(flush_logfile ||
flushall_logfiles));
if (err)
{
fprintf(stderr,
"Error : Write to %s log "
": %s failed due to %d, "
"%s. Disabling the log.",
STRLOGNAME((logfile_id_t)i),
lf->lf_full_file_name,
err,
strerror(err));
/** Force log off */
skygw_log_disable_raw((logfile_id_t)i, true);
}
/**
* Reset buffer's counters and mark
* not full.

View File

@ -75,6 +75,7 @@ void skygw_logmanager_exit(void);
void skygw_log_done(void);
int skygw_log_write(logfile_id_t id, const char* format, ...);
int skygw_log_flush(logfile_id_t id);
int skygw_log_rotate(logfile_id_t id);
int skygw_log_write_flush(logfile_id_t id, const char* format, ...);
int skygw_log_enable(logfile_id_t id);
int skygw_log_disable(logfile_id_t id);

View File

@ -27,7 +27,7 @@ macro(set_variables)
set(TEST_HOST "127.0.0.1" CACHE STRING "hostname or IP address of MaxScale's host")
# port of read connection router module
set(TEST_PORT_RW "4008" CACHE STRING "port of read connection router module")
set(TEST_PORT "4008" CACHE STRING "port of read connection router module")
# port of read/write split router module
set(TEST_PORT_RW "4006" CACHE STRING "port of read/write split router module")
@ -38,6 +38,9 @@ macro(set_variables)
# master test server server_id
set(TEST_MASTER_ID "3000" CACHE STRING "master test server server_id")
# master test server port
set(MASTER_PORT "3000" CACHE STRING "master test server port")
# username of MaxScale user
set(TEST_USER "maxuser" CACHE STRING "username of MaxScale user")
@ -202,6 +205,16 @@ debugmsg("Search returned: ${MYSQL_DIR_LOC}")
unset(DEB_FNC)
unset(RPM_FNC)
#Find the MySQL client library
find_library(MYSQLCLIENT_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mysql mariadb)
if(${MYSQLCLIENT_LIBRARIES} MATCHES "NOTFOUND")
set(MYSQLCLIENT_FOUND FALSE CACHE INTERNAL "")
message(STATUS "Cannot find MySQL client library: Login tests disabled.")
else()
set(MYSQLCLIENT_FOUND TRUE CACHE INTERNAL "")
message(STATUS "Found MySQL client library: ${MYSQLCLIENT_LIBRARIES}")
endif()
#Check RabbitMQ headers and libraries
if(BUILD_RABBITMQ)
include(CheckCSourceCompiles)

View File

@ -57,21 +57,19 @@ int main(int argc, char** argv)
{
fgets(readbuff,4092,infile);
psize = strlen(readbuff);
if(psize < 0 || psize > 4092){
continue;
}
qbuff = gwbuf_alloc(psize + 7);
*(qbuff->sbuf->data + 0) = (unsigned char)psize;
*(qbuff->sbuf->data + 1) = (unsigned char)(psize>>8);
*(qbuff->sbuf->data + 2) = (unsigned char)(psize>>16);
*(qbuff->sbuf->data + 4) = 0x03;
memcpy(qbuff->start + 5,readbuff,psize + 1);
parse_query(qbuff);
tok = skygw_get_canonical(qbuff);
fprintf(outfile,"%s\n",tok);
free(tok);
gwbuf_free(qbuff);
if(psize < 4092){
qbuff = gwbuf_alloc(psize + 7);
*(qbuff->sbuf->data + 0) = (unsigned char)psize;
*(qbuff->sbuf->data + 1) = (unsigned char)(psize>>8);
*(qbuff->sbuf->data + 2) = (unsigned char)(psize>>16);
*(qbuff->sbuf->data + 4) = 0x03;
memcpy(qbuff->start + 5,readbuff,psize + 1);
parse_query(qbuff);
tok = skygw_get_canonical(qbuff);
fprintf(outfile,"%s\n",tok);
free(tok);
gwbuf_free(qbuff);
}
}
fclose(infile);
fclose(outfile);

View File

@ -38,6 +38,8 @@
* 09/09/14 Massimiliano Pinto Added localhost_match_wildcard_host parameter
* 12/09/14 Mark Riddoch Addition of checks on servers list and
* internal router suppression of messages
* 30/10/14 Massimiliano Pinto Added disable_master_failback parameter
* 07/11/14 Massimiliano Pinto Addition of monitor timeouts for connect/read/write
*
* @endverbatim
*/
@ -58,6 +60,7 @@
extern int lm_enabled_logfiles_bitmask;
extern int setipaddress(struct in_addr *, char *);
static int process_config_context(CONFIG_CONTEXT *);
static int process_config_update(CONFIG_CONTEXT *);
static void free_config_context(CONFIG_CONTEXT *);
@ -772,6 +775,10 @@ int error_count = 0;
unsigned long interval = 0;
int replication_heartbeat = 0;
int detect_stale_master = 0;
int disable_master_failback = 0;
int connect_timeout = 0;
int read_timeout = 0;
int write_timeout = 0;
module = config_get_value(obj->parameters, "module");
servers = config_get_value(obj->parameters, "servers");
@ -789,6 +796,20 @@ int error_count = 0;
detect_stale_master = atoi(config_get_value(obj->parameters, "detect_stale_master"));
}
if (config_get_value(obj->parameters, "disable_master_failback")) {
disable_master_failback = atoi(config_get_value(obj->parameters, "disable_master_failback"));
}
if (config_get_value(obj->parameters, "backend_connect_timeout")) {
connect_timeout = atoi(config_get_value(obj->parameters, "backend_connect_timeout"));
}
if (config_get_value(obj->parameters, "backend_read_timeout")) {
read_timeout = atoi(config_get_value(obj->parameters, "backend_read_timeout"));
}
if (config_get_value(obj->parameters, "backend_write_timeout")) {
write_timeout = atoi(config_get_value(obj->parameters, "backend_write_timeout"));
}
if (module)
{
obj->element = monitor_alloc(obj->object, module);
@ -816,6 +837,18 @@ int error_count = 0;
if(detect_stale_master == 1)
monitorDetectStaleMaster(obj->element, detect_stale_master);
/* disable master failback */
if(disable_master_failback == 1)
monitorDisableMasterFailback(obj->element, disable_master_failback);
/* set timeouts */
if (connect_timeout > 0)
monitorSetNetworkTimeout(obj->element, MONITOR_CONNECT_TIMEOUT, connect_timeout);
if (read_timeout > 0)
monitorSetNetworkTimeout(obj->element, MONITOR_READ_TIMEOUT, read_timeout);
if (write_timeout > 0)
monitorSetNetworkTimeout(obj->element, MONITOR_WRITE_TIMEOUT, write_timeout);
/* get the servers to monitor */
s = strtok(servers, ",");
while (s)
@ -1360,11 +1393,11 @@ SERVER *server;
user,
auth);
if (enable_root_user)
serviceEnableRootUser(service, atoi(enable_root_user));
serviceEnableRootUser(obj->element, atoi(enable_root_user));
if (allow_localhost_match_wildcard_host && service)
if (allow_localhost_match_wildcard_host)
serviceEnableLocalhostMatchWildcardHost(
service,
obj->element,
atoi(allow_localhost_match_wildcard_host));
}
}
@ -1625,6 +1658,10 @@ static char *monitor_params[] =
"monitor_interval",
"detect_replication_lag",
"detect_stale_master",
"disable_master_failback",
"backend_connect_timeout",
"backend_read_timeout",
"backend_write_timeout",
NULL
};
/**

View File

@ -468,26 +468,56 @@ getUsers(SERVICE *service, USERS *users)
server = service->databases;
dpwd = decryptPassword(service_passwd);
while (server != NULL && (mysql_real_connect(con,
server->name,
service_user,
dpwd,
NULL,
server->port,
NULL,
0) == NULL))
{
server = server->nextdb;
/* Select a server with Master bit, if available */
while (server != NULL && !(server->status & SERVER_MASTER)) {
server = server->nextdb;
}
/* Try loading data from master server */
if (server != NULL && (mysql_real_connect(con, server->name, service_user, dpwd, NULL, server->port, NULL, 0) != NULL)) {
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"Dbusers : Loading data from backend database with Master role [%s:%i] "
"for service [%s]",
server->name,
server->port,
service->name)));
} else {
/* load data from other servers via loop */
server = service->databases;
while (server != NULL && (mysql_real_connect(con,
server->name,
service_user,
dpwd,
NULL,
server->port,
NULL,
0) == NULL))
{
server = server->nextdb;
}
if (server != NULL) {
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"Dbusers : Loading data from backend database [%s:%i] "
"for service [%s]",
server->name,
server->port,
service->name)));
}
}
free(dpwd);
if (server == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Unable to get user data from backend database "
"for service [%s]. Missing server information.",
service->name)));
LOGFILE_ERROR,
"Error : Unable to get user data from backend database "
"for service [%s]. Missing server information.",
service->name)));
mysql_close(con);
return -1;
}
@ -663,8 +693,8 @@ getUsers(SERVICE *service, USERS *users)
/* load all mysql database names */
dbnames = getDatabases(service, con);
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"Loaded %d MySQL Database Names for service [%s]",
dbnames,
service->name)));

View File

@ -121,8 +121,8 @@ DCB *rval;
#if defined(SS_DEBUG)
rval->dcb_chk_top = CHK_NUM_DCB;
rval->dcb_chk_tail = CHK_NUM_DCB;
rval->dcb_errhandle_called = false;
#endif
rval->dcb_errhandle_called = false;
rval->dcb_role = role;
spinlock_init(&rval->dcb_initlock);
spinlock_init(&rval->writeqlock);
@ -509,10 +509,14 @@ bool succp = false;
pthread_self(),
dcb->fd,
dcb)));
#endif /* SS_DEBUG */
#if defined(FAKE_CODE)
conn_open[dcb->fd] = false;
#endif /* FAKE_CODE */
#if defined(SS_DEBUG)
ss_debug(dcb->fd = -1;)
}
#endif
#endif /* SS_DEBUG */
succp = dcb_set_state(dcb, DCB_STATE_DISCONNECTED, NULL);
ss_dassert(succp);
dcb_next = dcb->memdata.next;
@ -658,7 +662,6 @@ int dcb_read(
int rc;
int n ;
int nread = 0;
int eno = 0;
CHK_DCB(dcb);
while (true)
@ -669,8 +672,6 @@ int dcb_read(
if (rc == -1)
{
eno = errno;
errno = 0;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : ioctl FIONREAD for dcb %p in "
@ -678,8 +679,8 @@ int dcb_read(
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
eno,
strerror(eno))));
errno,
strerror(errno))));
n = -1;
goto return_n;
}
@ -727,22 +728,18 @@ int dcb_read(
"for dcb %p fd %d, due %d, %s.",
dcb,
dcb->fd,
eno,
strerror(eno))));
errno,
strerror(errno))));
n = -1;
ss_dassert(buffer != NULL);
goto return_n;
}
GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize);
dcb->stats.n_reads++);
if (n <= 0)
{
int eno = errno;
errno = 0;
if (eno != 0 && eno != EAGAIN && eno != EWOULDBLOCK)
{
if (errno != 0 && errno != EAGAIN && errno != EWOULDBLOCK)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
@ -751,10 +748,10 @@ int dcb_read(
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
eno,
strerror(eno))));
errno,
strerror(errno))));
}
gwbuf_free(buffer);
gwbuf_free(buffer);
goto return_n;
}
nread += n;
@ -862,7 +859,7 @@ int below_water;
while (queue != NULL)
{
int qlen;
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER &&
dcb->session != NULL)
{
@ -878,7 +875,7 @@ int below_water;
fail_next_backend_fd = false;
}
}
#endif /* SS_DEBUG */
#endif /* FAKE_CODE */
qlen = GWBUF_LENGTH(queue);
GW_NOINTR_CALL(
w = gw_write(
@ -1684,7 +1681,7 @@ int gw_write(
size_t nbytes)
{
int w;
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
if (dcb_fake_write_errno[fd] != 0) {
ss_dassert(dcb_fake_write_ev[fd] != 0);
w = write(fd, buf, nbytes/2); /*< leave peer to read missing bytes */
@ -1698,7 +1695,7 @@ int gw_write(
}
#else
w = write(fd, buf, nbytes);
#endif /* SS_DEBUG && SS_TEST */
#endif /* FAKE_CODE */
#if defined(SS_DEBUG_MYSQL)
{

View File

@ -1025,7 +1025,7 @@ int main(int argc, char **argv)
progname = *argv;
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
memset(conn_open, 0, sizeof(bool)*10240);
memset(dcb_fake_write_errno, 0, sizeof(unsigned char)*10240);
memset(dcb_fake_write_ev, 0, sizeof(__int32_t)*10240);
@ -1033,7 +1033,7 @@ int main(int argc, char **argv)
fail_next_client_fd = false;
fail_next_accept = 0;
fail_accept_errno = 0;
#endif
#endif /* FAKE_CODE */
file_write_header(stderr);
/*<
* Register functions which are called at exit except libmysqld-related,
@ -1851,8 +1851,6 @@ static int write_pid_file(char *home_dir) {
/* close file */
close(fd);
fprintf(stderr, "MaxScale PID %s in pidfile %s\n", pidstr, pidfile);
}
/* success */

View File

@ -26,6 +26,8 @@
* 08/07/13 Mark Riddoch Initial implementation
* 23/05/14 Massimiliano Pinto Addition of monitor_interval parameter
* and monitor id
* 30/10/14 Massimiliano Pinto Addition of disable_master_failback parameter
* 07/11/14 Massimiliano Pinto Addition of monitor network timeouts
*
* @endverbatim
*/
@ -329,3 +331,31 @@ monitorDetectStaleMaster(MONITOR *mon, int enable)
mon->module->detectStaleMaster(mon->handle, enable);
}
}
/**
* Disable Master Failback
*
* @param mon The monitor instance
* @param disable The value 1 disable the failback, 0 keeps it
*/
void
monitorDisableMasterFailback(MONITOR *mon, int disable)
{
if (mon->module->disableMasterFailback != NULL) {
mon->module->disableMasterFailback(mon->handle, disable);
}
}
/**
* Set Monitor timeouts for connect/read/write
*
* @param mon The monitor instance
* @param type The timeout handling type
* @param value The timeout to set
*/
void
monitorSetNetworkTimeout(MONITOR *mon, int type, int value) {
if (mon->module->setNetworkTimeout != NULL) {
mon->module->setNetworkTimeout(mon->handle, type, value);
}
}

View File

@ -625,7 +625,7 @@ uint32_t ev;
thread_data[thread_id].event = ev;
}
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
if (dcb_fake_write_ev[dcb->fd] != 0) {
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
@ -637,7 +637,7 @@ uint32_t ev;
ev |= dcb_fake_write_ev[dcb->fd];
dcb_fake_write_ev[dcb->fd] = 0;
}
#endif
#endif /* FAKE_CODE */
ss_debug(spinlock_acquire(&dcb->dcb_initlock);)
ss_dassert(dcb->state != DCB_STATE_ALLOC);
ss_dassert(dcb->state != DCB_STATE_DISCONNECTED);
@ -735,7 +735,7 @@ uint32_t ev;
if (ev & EPOLLERR)
{
int eno = gw_getsockerrno(dcb->fd);
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
if (eno == 0) {
eno = dcb_fake_write_errno[dcb->fd];
LOGIF(LD, (skygw_log_write(
@ -748,7 +748,7 @@ uint32_t ev;
strerror(eno))));
}
dcb_fake_write_errno[dcb->fd] = 0;
#endif
#endif /* FAKE_CODE */
if (eno != 0) {
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
@ -1073,13 +1073,26 @@ double avg1 = 0.0, avg5 = 0.0, avg15 = 0.0;
{
char *event_string
= event_to_string(thread_data[i].event);
bool from_heap;
if (event_string == NULL)
{
from_heap = false;
event_string = "??";
}
else
{
from_heap = true;
}
dcb_printf(dcb,
" %2d | %-10s | %6d | %-16p | %s\n",
i, state, thread_data[i].n_fds,
thread_data[i].cur_dcb, event_string);
free(event_string);
if (from_heap)
{
free(event_string);
}
}
}
}

View File

@ -269,7 +269,17 @@ MAXKEYS key;
errno,
strerror(errno))));
}
chmod(secret_file, S_IRUSR);
if( chmod(secret_file, S_IRUSR) < 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : failed to change the permissions of the"
"secret file [%s]. Error %d, %s.",
secret_file,
errno,
strerror(errno))));
}
return 0;
}

View File

@ -30,7 +30,8 @@
* 28/05/14 Massimiliano Pinto Addition of rlagd and node_ts fields
* 20/06/14 Massimiliano Pinto Addition of master_id, depth, slaves fields
* 26/06/14 Mark Riddoch Addition of server parameters
* 30/08/14 Massimiliano Pinto Addition of new service status description
* 30/08/14 Massimiliano Pinto Addition of new service status description
* 30/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS description
*
* @endverbatim
*/
@ -424,6 +425,8 @@ char *status = NULL;
strcat(status, "Slave of External Server, ");
if (server->status & SERVER_STALE_STATUS)
strcat(status, "Stale Status, ");
if (server->status & SERVER_MASTER_STICKINESS)
strcat(status, "Master Stickiness, ");
if (server->status & SERVER_AUTH_ERROR)
strcat(status, "Auth Error, ");
if (server->status & SERVER_RUNNING)

View File

@ -145,6 +145,7 @@ SERVICE *service;
service->filters = NULL;
service->n_filters = 0;
service->weightby = 0;
service->users = NULL;
service->resources = NULL;
spinlock_init(&service->spin);
spinlock_init(&service->users_table_spin);
@ -213,42 +214,46 @@ GWPROTOCOL *funcs;
if (strcmp(port->protocol, "MySQLClient") == 0) {
int loaded;
/*
* Allocate specific data for MySQL users
* including hosts and db names
*/
service->users = mysql_users_alloc();
if ((loaded = load_mysql_users(service)) < 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Unable to load users from %s:%d for "
"service %s.",
port->address,
port->port,
service->name)));
hashtable_free(service->users->data);
free(service->users);
dcb_free(port->listener);
port->listener = NULL;
goto retblock;
}
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
* This way MaxScale could try reloading users' just after startup
*/
service->rate_limit.last=time(NULL) - USERS_REFRESH_TIME;
service->rate_limit.nloads=1;
if (service->users == NULL) {
/*
* Allocate specific data for MySQL users
* including hosts and db names
*/
service->users = mysql_users_alloc();
if ((loaded = load_mysql_users(service)) < 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Unable to load users from %s:%d for "
"service %s.",
(port->address == NULL ? "0.0.0.0" : port->address),
port->port,
service->name)));
hashtable_free(service->users->data);
free(service->users);
dcb_free(port->listener);
port->listener = NULL;
goto retblock;
}
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
* This way MaxScale could try reloading users' just after startup
*/
service->rate_limit.last=time(NULL) - USERS_REFRESH_TIME;
service->rate_limit.nloads=1;
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Loaded %d MySQL Users for service [%s].",
loaded, service->name)));
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Loaded %d MySQL Users for service [%s].",
loaded, service->name)));
}
}
else
{
/* Generic users table */
service->users = users_alloc();
if (service->users == NULL) {
/* Generic users table */
service->users = users_alloc();
}
}
if ((funcs=(GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL))

View File

@ -133,8 +133,9 @@ session_alloc(SERVICE *service, DCB *client_dcb)
session->router_session =
service->router->newSession(service->router_instance,
session);
if (session->router_session == NULL) {
if (session->router_session == NULL)
{
/**
* Inform other threads that session is closing.
*/
@ -153,7 +154,6 @@ session_alloc(SERVICE *service, DCB *client_dcb)
goto return_session;
}
/*
* Pending filter chain being setup set the head of the chain to
* be the router. As filters are inserted the current head will
@ -187,20 +187,22 @@ session_alloc(SERVICE *service, DCB *client_dcb)
session_free(session);
client_dcb->session = NULL;
session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Error : Failed to create %s session.",
"Error : Setting up filters failed. "
"Terminating session %s.",
service->name)));
goto return_session;
}
}
}
spinlock_acquire(&session_spin);
spinlock_acquire(&session->ses_lock);
if (session->state != SESSION_STATE_READY)
{
session_free(session);
spinlock_release(&session->ses_lock);
session_free(session);
client_dcb->session = NULL;
session = NULL;
LOGIF(LE, (skygw_log_write_flush(
@ -212,10 +214,13 @@ session_alloc(SERVICE *service, DCB *client_dcb)
else
{
session->state = SESSION_STATE_ROUTER_READY;
session->next = allSessions;
spinlock_release(&session->ses_lock);
spinlock_acquire(&session_spin);
session->next = allSessions;
allSessions = session;
spinlock_release(&session_spin);
atomic_add(&service->stats.n_sessions, 1);
atomic_add(&service->stats.n_sessions, 1);
atomic_add(&service->stats.n_current, 1);
CHK_SESSION(session);
}

View File

@ -30,7 +30,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <dcb.h>
@ -44,6 +44,7 @@ test1()
{
DCB *dcb;
int result;
int eno = 0;
/* Poll tests */
ss_dfprintf(stderr,
@ -51,10 +52,35 @@ int result;
poll_init();
ss_dfprintf(stderr, "\t..done\nAdd a DCB");
dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER);
if(dcb == NULL){
ss_dfprintf(stderr, "\nError on function call: dcb_alloc() returned NULL.\n");
return 1;
}
dcb->fd = socket(AF_UNIX, SOCK_STREAM, 0);
poll_add_dcb(dcb);
poll_remove_dcb(dcb);
poll_add_dcb(dcb);
if(dcb->fd < 0){
ss_dfprintf(stderr, "\nError on function call: socket() returned %d: %s\n",errno,strerror(errno));
return 1;
}
if((eno = poll_add_dcb(dcb)) != 0){
ss_dfprintf(stderr, "\nError on function call: poll_add_dcb() returned %d.\n",eno);
return 1;
}
if((eno = poll_remove_dcb(dcb)) != 0){
ss_dfprintf(stderr, "\nError on function call: poll_remove_dcb() returned %d.\n",eno);
return 1;
}
if((eno = poll_add_dcb(dcb)) != 0){
ss_dfprintf(stderr, "\nError on function call: poll_add_dcb() returned %d.\n",eno);
return 1;
}
ss_dfprintf(stderr, "\t..done\nStart wait for events.");
sleep(10);
poll_shutdown();

View File

@ -225,7 +225,9 @@ int gw_getsockerrno(
goto return_eno;
}
getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&eno, &elen);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&eno, &elen) != 0){
eno = 0;
}
return_eno:
return eno;

View File

@ -205,9 +205,9 @@ typedef struct dcb_callback {
typedef struct dcb {
#if defined(SS_DEBUG)
skygw_chk_t dcb_chk_top;
bool dcb_errhandle_called;
#endif
dcb_role_t dcb_role;
bool dcb_errhandle_called; /*< this can be called only once */
dcb_role_t dcb_role;
SPINLOCK dcb_initlock;
DCBEVENTQ evq; /**< The event queue for this DCB */
int fd; /**< The descriptor */
@ -252,14 +252,14 @@ typedef struct dcb {
#endif
} DCB;
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
unsigned char dcb_fake_write_errno[10240];
__int32_t dcb_fake_write_ev[10240];
bool fail_next_backend_fd;
bool fail_next_client_fd;
int fail_next_accept;
int fail_accept_errno;
#endif
#endif /* FAKE_CODE */
/* A few useful macros */
#define DCB_SESSION(x) (x)->session

View File

@ -33,6 +33,8 @@
* 23/05/14 Massimiliano Pinto Addition of defaultId and setInterval
* 23/06/14 Massimiliano Pinto Addition of replicationHeartbeat
* 28/08/14 Massimiliano Pinto Addition of detectStaleMaster
* 30/10/14 Massimiliano Pinto Addition of disableMasterFailback
* 07/11/14 Massimiliano Pinto Addition of setNetworkTimeout
*
* @endverbatim
*/
@ -70,9 +72,11 @@ typedef struct {
void (*defaultUser)(void *, char *, char *);
void (*diagnostics)(DCB *, void *);
void (*setInterval)(void *, size_t);
void (*setNetworkTimeout)(void *, int, int);
void (*defaultId)(void *, unsigned long);
void (*replicationHeartbeat)(void *, int);
void (*detectStaleMaster)(void *, int);
void (*disableMasterFailback)(void *, int);
} MONITOR_OBJECT;
/**
@ -96,6 +100,20 @@ typedef enum
MONITOR_STATE_FREED = 0x08
} monitor_state_t;
/**
* Monitor network timeout types
*/
typedef enum
{
MONITOR_CONNECT_TIMEOUT = 0,
MONITOR_READ_TIMEOUT = 1,
MONITOR_WRITE_TIMEOUT = 2
} monitor_timeouts_t;
#define DEFAULT_CONNECT_TIMEOUT 3
#define DEFAULT_READ_TIMEOUT 1
#define DEFAULT_WRITE_TIMEOUT 2
/**
* Representation of the running monitor.
*/
@ -123,4 +141,6 @@ extern void monitorSetId(MONITOR *, unsigned long);
extern void monitorSetInterval (MONITOR *, unsigned long);
extern void monitorSetReplicationHeartbeat(MONITOR *, int);
extern void monitorDetectStaleMaster(MONITOR *, int);
extern void monitorDisableMasterFailback(MONITOR *, int);
extern void monitorSetNetworkTimeout(MONITOR *, int, int);
#endif

View File

@ -40,6 +40,7 @@
* 26/06/14 Mark Riddoch Adidtion of server parameters
* 30/07/14 Massimiliano Pinto Addition of NDB status for MySQL Cluster
* 30/08/14 Massimiliano Pinto Addition of SERVER_STALE_STATUS
* 27/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS
*
* @endverbatim
*/
@ -105,6 +106,7 @@ typedef struct server {
#define SERVER_MAINT 0x0020 /**<< Server is in maintenance mode */
#define SERVER_SLAVE_OF_EXTERNAL_MASTER 0x0040 /**<< Server is slave of a Master outside the provided replication topology */
#define SERVER_STALE_STATUS 0x0080 /**<< Server stale status, monitor didn't update it */
#define SERVER_MASTER_STICKINESS 0x0100 /**<< Server Master stickiness */
#define SERVER_AUTH_ERROR 0x1000 /**<< Authentication erorr from monitor */
/**

View File

@ -435,7 +435,7 @@ HINT_MODE mode = HM_EXECUTE;
token_free(tok);
} /*< while */
if (tok->token == TOK_EOL)
if ( tok && tok->token == TOK_EOL)
{
token_free(tok);
}
@ -550,7 +550,7 @@ HINT_TOKEN *tok;
else if (!inword && inquote == '\0' && **ptr == '=')
{
*dest = **ptr;
*dest++;
dest++;
(*ptr)++;
break;
}

View File

@ -398,7 +398,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
QLA_INSTANCE *my_instance = (QLA_INSTANCE *)instance;
QLA_SESSION *my_session = (QLA_SESSION *)session;
char *ptr;
int length;
int length = 0;
struct tm t;
struct timeval tv;

View File

@ -415,8 +415,9 @@ GWBUF *clone = NULL;
modutil_MySQL_Query(queue, &dummy, &length, &residual);
clone = gwbuf_clone(queue);
my_session->residual = residual;
free(ptr);
}
free(ptr);
}
/* Pass the query downstream */

View File

@ -748,9 +748,12 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
}
int x;
for(x = 0;x<paramc;x++){
free(fparams[x]->name);
free(fparams[x]->value);
if(fparams){
for(x = 0;x<paramc;x++){
free(fparams[x]->name);
free(fparams[x]->value);
}
}
free(fparams);
@ -769,15 +772,18 @@ FILTERCHAIN* load_filter_module(char* str)
flt_ptr->next = instance.head;
}
if((flt_ptr->instance = (FILTER_OBJECT*)load_module(str, MODULE_FILTER)) == NULL)
{
printf("Error: Module loading failed: %s\n",str);
skygw_log_write(LOGFILE_ERROR,"Error: Module loading failed: %s\n",str);
free(flt_ptr->down);
free(flt_ptr);
return NULL;
}
flt_ptr->name = strdup(str);
if(flt_ptr){
if( (flt_ptr->instance = (FILTER_OBJECT*)load_module(str, MODULE_FILTER)) == NULL)
{
printf("Error: Module loading failed: %s\n",str);
skygw_log_write(LOGFILE_ERROR,"Error: Module loading failed: %s\n",str);
free(flt_ptr->down);
free(flt_ptr);
return NULL;
}
flt_ptr->name = strdup(str);
}
return flt_ptr;
}
@ -925,14 +931,35 @@ GWBUF* gen_packet(PACKET pkt)
int process_opts(int argc, char** argv)
{
unsigned int fd = open_file("harness.cnf",1), buffsize = 1024;
int rd,rdsz;
unsigned int fsize;
int fd, buffsize = 1024;
int rd,rdsz, rval;
size_t fsize;
char *buff = calloc(buffsize,sizeof(char)), *tok = NULL;
/**Parse 'harness.cnf' file*/
fsize = lseek(fd,0,SEEK_END);
lseek(fd,0,SEEK_SET);
if(buff == NULL){
printf("Error: Call to malloc() failed.\n");
return 1;
}
if((fd = open_file("harness.cnf",1)) < 0){
printf("Failed to open configuration file.\n");
free(buff);
return 1;
}
if( (rval = lseek(fd,0,SEEK_END)) < 0 ||
lseek(fd,0,SEEK_SET) < 0){
printf("Error: Cannot seek file.\n");
close(fd);
free(buff);
return 1;
}
fsize = (size_t)rval;
instance.thrcount = 1;
instance.session_count = 1;
rdsz = read(fd,buff,fsize);

View File

@ -314,10 +314,10 @@ char *remote, *user;
else
my_session->userName = NULL;
my_session->active = 1;
if (my_instance->source && strcmp(my_session->clientHost,
if (my_instance->source && my_session->clientHost && strcmp(my_session->clientHost,
my_instance->source))
my_session->active = 0;
if (my_instance->user && strcmp(my_session->userName,
if (my_instance->user && my_session->userName && strcmp(my_session->userName,
my_instance->user))
my_session->active = 0;

View File

@ -281,6 +281,7 @@ typedef struct {
* created or received */
unsigned long tid; /*< MySQL Thread ID, in
* handshake */
unsigned int charset; /*< MySQL character set at connect time */
#if defined(SS_DEBUG)
skygw_chk_t protocol_chk_tail;
#endif
@ -296,6 +297,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_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
#define MYSQL_GET_NATTR(payload) ((int)payload[4])
#endif /** _MYSQL_PROTOCOL_H */
@ -313,6 +315,7 @@ int gw_send_authentication_to_backend(
char *user,
uint8_t *passwd,
MySQLProtocol *protocol);
const char *gw_mysql_protocol_state2string(int state);
int gw_do_connect_to_backend(char *host, int port, int* fd);
int mysql_send_com_quit(DCB* dcb, int packet_number, GWBUF* buf);
@ -334,6 +337,11 @@ int gw_send_change_user_to_backend(
char *user,
uint8_t *passwd,
MySQLProtocol *protocol);
GWBUF* gw_create_change_user_packet(
MYSQL_session* mses,
MySQLProtocol* protocol);
int gw_find_mysql_user_password_sha1(
char *username,
uint8_t *gateway_password,

View File

@ -30,6 +30,8 @@
* Interval is printed in diagnostics.
* 03/06/14 Mark Riddoch Add support for maintenance mode
* 24/06/14 Massimiliano Pinto Added depth level 0 for each node
* 30/10/14 Massimiliano Pinto Added disableMasterFailback feature
* 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write
*
* @endverbatim
*/
@ -52,7 +54,7 @@ extern int lm_enabled_logfiles_bitmask;
static void monitorMain(void *);
static char *version_str = "V1.2.0";
static char *version_str = "V1.4.0";
MODULE_INFO info = {
MODULE_API_MONITOR,
@ -68,6 +70,10 @@ static void unregisterServer(void *, SERVER *);
static void defaultUsers(void *, char *, char *);
static void diagnostics(DCB *, void *);
static void setInterval(void *, size_t);
static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *);
static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *, MONITOR_SERVERS *, int);
static void disableMasterFailback(void *, int);
static void setNetworkTimeout(void *arg, int type, int value);
static MONITOR_OBJECT MyObject = {
startMonitor,
@ -76,10 +82,12 @@ static MONITOR_OBJECT MyObject = {
unregisterServer,
defaultUsers,
diagnostics,
setInterval,
setInterval,
setNetworkTimeout,
NULL,
NULL,
NULL,
disableMasterFailback
};
/**
@ -147,6 +155,11 @@ MYSQL_MONITOR *handle;
handle->defaultPasswd = NULL;
handle->id = MONITOR_DEFAULT_ID;
handle->interval = MONITOR_INTERVAL;
handle->disableMasterFailback = 0;
handle->master = NULL;
handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT;
handle->read_timeout=DEFAULT_READ_TIMEOUT;
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
spinlock_init(&handle->lock);
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
@ -264,6 +277,10 @@ char *sep;
}
dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval);
dcb_printf(dcb,"\tMaster Failback:\t%s\n", (handle->disableMasterFailback == 1) ? "off" : "on");
dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout);
dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout);
dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout);
dcb_printf(dcb, "\tMonitored servers: ");
db = handle->databases;
@ -301,16 +318,18 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
/**
* Monitor an individual server
*
* @param database The database to probe
* @param handle The MySQL Monitor object
* @param database The database to probe
*/
static void
monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd)
monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database)
{
MYSQL_ROW row;
MYSQL_RES *result;
int num_fields;
int isjoined = 0;
char *uname = defaultUser, *passwd = defaultPasswd;
char *uname = handle->defaultUser;
char *passwd = handle->defaultPasswd;
unsigned long int server_version = 0;
char *server_string;
@ -330,10 +349,15 @@ char *server_string;
{
char *dpwd = decryptPassword(passwd);
int rc;
int read_timeout = 1;
int connect_timeout = handle->connect_timeout;
int read_timeout = handle->read_timeout;
int write_timeout = handle->write_timeout;;
database->con = mysql_init(NULL);
rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout);
rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout);
rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout);
if (mysql_real_connect(database->con, database->server->name,
uname, dpwd, NULL, database->server->port, NULL, 0) == NULL)
@ -345,7 +369,15 @@ char *server_string;
database->server->name,
database->server->port,
mysql_error(database->con))));
server_clear_status(database->server, SERVER_RUNNING);
/* Also clear Joined, M/S and Stickiness bits */
server_clear_status(database->server, SERVER_JOINED);
server_clear_status(database->server, SERVER_SLAVE);
server_clear_status(database->server, SERVER_MASTER);
server_clear_status(database->server, SERVER_MASTER_STICKINESS);
if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
{
server_set_status(database->server, SERVER_AUTH_ERROR);
@ -421,10 +453,11 @@ char *server_string;
static void
monitorMain(void *arg)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *ptr;
long master_id;
size_t nrounds = 0;
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *ptr;
size_t nrounds = 0;
MONITOR_SERVERS *candidate_master = NULL;
int master_stickiness = handle->disableMasterFailback;
if (mysql_thread_init())
{
@ -453,39 +486,35 @@ size_t nrounds = 0;
* interval, then skip monitoring checks. Excluding the first
* round.
*/
if (nrounds != 0 &&
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >=
MON_BASE_INTERVAL_MS)
if (nrounds != 0 && ((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >= MON_BASE_INTERVAL_MS)
{
nrounds += 1;
continue;
}
nrounds += 1;
master_id = -1;
ptr = handle->databases;
while (ptr)
{
unsigned int prev_status = ptr->server->status;
monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd);
/* set master_id to the lowest value of ptr->server->node_id */
monitorDatabase(handle, ptr);
/* clear bits for non member nodes */
if ( ! SERVER_IN_MAINT(ptr->server) && (ptr->server->node_id < 0 || ! SERVER_IS_JOINED(ptr->server))) {
ptr->server->depth = -1;
if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) {
ptr->server->depth = 0;
if (ptr->server->node_id < master_id && master_id >= 0) {
master_id = ptr->server->node_id;
} else {
if (master_id < 0) {
master_id = ptr->server->node_id;
}
}
} else if (!SERVER_IN_MAINT(ptr->server)) {
/* clear M/S status */
server_clear_status(ptr->server, SERVER_SLAVE);
server_clear_status(ptr->server, SERVER_MASTER);
ptr->server->depth = -1;
/* clear master sticky status */
server_clear_status(ptr->server, SERVER_MASTER_STICKINESS);
}
/* Log server status change */
if (ptr->server->status != prev_status ||
SERVER_IS_DOWN(ptr->server))
{
@ -496,24 +525,50 @@ size_t nrounds = 0;
ptr->server->port,
STRSRVSTATUS(ptr->server))));
}
ptr = ptr->next;
}
/*
* Let's select a master server:
* it could be the candidate master following MIN(node_id) rule or
* the server that was master in the previous monitor polling cycle
* Decision depends on master_stickiness value set in configuration
*/
/* get the candidate master, followinf MIN(node_id) rule */
candidate_master = get_candidate_master(handle->databases);
/* Select the master, based on master_stickiness */
handle->master = set_cluster_master(handle->master, candidate_master, master_stickiness);
ptr = handle->databases;
/* this server loop sets Master and Slave roles */
while (ptr)
{
if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && master_id >= 0) {
/* set the Master role */
if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id == master_id)) {
server_set_status(ptr->server, SERVER_MASTER);
server_clear_status(ptr->server, SERVER_SLAVE);
} else if (SERVER_IS_JOINED(ptr->server) && (ptr->server->node_id > master_id)) {
while (ptr && handle->master) {
if (!SERVER_IS_JOINED(ptr->server) || SERVER_IN_MAINT(ptr->server)) {
ptr = ptr->next;
continue;
}
if (ptr != handle->master) {
/* set the Slave role */
server_set_status(ptr->server, SERVER_SLAVE);
server_clear_status(ptr->server, SERVER_MASTER);
}
server_set_status(ptr->server, SERVER_SLAVE);
server_clear_status(ptr->server, SERVER_MASTER);
/* clear master stickyness */
server_clear_status(ptr->server, SERVER_MASTER_STICKINESS);
} else {
/* set the Master role */
server_set_status(handle->master->server, SERVER_MASTER);
server_clear_status(handle->master->server, SERVER_SLAVE);
if (candidate_master && handle->master->server->node_id != candidate_master->server->node_id) {
/* set master stickyness */
server_set_status(handle->master->server, SERVER_MASTER_STICKINESS);
} else {
/* clear master stickyness */
server_clear_status(ptr->server, SERVER_MASTER_STICKINESS);
}
}
ptr = ptr->next;
@ -533,3 +588,143 @@ setInterval(void *arg, size_t interval)
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
memcpy(&handle->interval, &interval, sizeof(unsigned long));
}
/**
* get candidate master from all nodes
*
* The current available rule: get the server with MIN(node_id)
* node_id comes from 'wsrep_local_index' variable
*
* @param servers The monitored servers list
* @return The candidate master on success, NULL on failure
*/
static MONITOR_SERVERS *get_candidate_master(MONITOR_SERVERS *servers) {
MONITOR_SERVERS *ptr = servers;
MONITOR_SERVERS *candidate_master = NULL;
long min_id = -1;
/* set min_id to the lowest value of ptr->server->node_id */
while(ptr) {
if ((! SERVER_IN_MAINT(ptr->server)) && ptr->server->node_id >= 0 && SERVER_IS_JOINED(ptr->server)) {
ptr->server->depth = 0;
if ((ptr->server->node_id < min_id) && min_id >= 0) {
min_id = ptr->server->node_id;
candidate_master = ptr;
} else {
if (min_id < 0) {
min_id = ptr->server->node_id;
candidate_master = ptr;
}
}
}
ptr = ptr->next;
}
return candidate_master;
}
/**
* set the master server in the cluster
*
* master could be the last one from previous monitor cycle Iis running) or
* the candidate master.
* The selection is based on the configuration option mapped to master_stickiness
* The candidate master may change over time due to
* 'wsrep_local_index' value change in the Galera Cluster
* Enabling master_stickiness will avoid master change unless a failure is spotted
*
* @param current_master Previous master server
* @param candidate_master The candidate master server accordingly to the selection rule
* @return The master node pointer (could be NULL)
*/
static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *current_master, MONITOR_SERVERS *candidate_master, int master_stickiness) {
/*
* if current master is not set or master_stickiness is not enable
* just return candidate_master.
*/
if (current_master == NULL || master_stickiness == 0) {
return candidate_master;
} else {
/*
* if current_master is still a cluster member use it
*
*/
if (SERVER_IS_JOINED(current_master->server) && (! SERVER_IN_MAINT(current_master->server))) {
return current_master;
} else
return candidate_master;
}
}
/**
* Disable/Enable the Master failback in a Galera Cluster.
*
* A restarted / rejoined node may get back the previous 'wsrep_local_index'
* from Cluster: if the value is the lowest in the cluster it will be selected as Master
* This will cause a Master change even if there is no failure.
* The option if set to 1 will avoid this situation, keeping the current Master (if running) available
*
* @param arg The handle allocated by startMonitor
* @param disable To disable it use 1, 0 keeps failback
*/
static void
disableMasterFailback(void *arg, int disable)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
memcpy(&handle->disableMasterFailback, &disable, sizeof(int));
}
static void
setNetworkTimeout(void *arg, int type, int value)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
int max_timeout = (int)(handle->interval/1000);
int new_timeout = max_timeout -1;
if (new_timeout <= 0)
new_timeout = DEFAULT_CONNECT_TIMEOUT;
switch(type) {
case MONITOR_CONNECT_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->connect_timeout, &value, sizeof(int));
} else {
memcpy(&handle->connect_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
case MONITOR_READ_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->read_timeout, &value, sizeof(int));
} else {
memcpy(&handle->read_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
case MONITOR_WRITE_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->write_timeout, &value, sizeof(int));
} else {
memcpy(&handle->write_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
default:
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Monitor setNetworkTimeout received an unsupported action type %i", type)));
break;
}
}

View File

@ -0,0 +1,824 @@
/*
* 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 mysql_mon.c - A MySQL Multi Muster cluster monitor
*
* @verbatim
* Revision History
*
* Date Who Description
* 08/09/14 Massimiliano Pinto Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <monitor.h>
#include <mysqlmon.h>
#include <thread.h>
#include <mysql.h>
#include <mysqld_error.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <secrets.h>
#include <dcb.h>
#include <modinfo.h>
extern int lm_enabled_logfiles_bitmask;
static void monitorMain(void *);
static char *version_str = "V1.0.1";
MODULE_INFO info = {
MODULE_API_MONITOR,
MODULE_BETA_RELEASE,
MONITOR_VERSION,
"A MySQL Multi Master monitor"
};
static void *startMonitor(void *);
static void stopMonitor(void *);
static void registerServer(void *, SERVER *);
static void unregisterServer(void *, SERVER *);
static void defaultUser(void *, char *, char *);
static void diagnostics(DCB *, void *);
static void setInterval(void *, size_t);
static void detectStaleMaster(void *, int);
static bool mon_status_changed(MONITOR_SERVERS* mon_srv);
static bool mon_print_fail_status(MONITOR_SERVERS* mon_srv);
static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *);
static void monitor_set_pending_status(MONITOR_SERVERS *, int);
static void monitor_clear_pending_status(MONITOR_SERVERS *, int);
static MONITOR_OBJECT MyObject = {
startMonitor,
stopMonitor,
registerServer,
unregisterServer,
defaultUser,
diagnostics,
setInterval,
NULL,
NULL,
NULL,
detectStaleMaster,
NULL
};
/**
* Implementation of the mandatory version entry point
*
* @return version string of the module
*/
char *
version()
{
return version_str;
}
/**
* The module initialisation routine, called when the module
* is first loaded.
*/
void
ModuleInit()
{
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Initialise the MySQL Monitor module %s.",
version_str)));
}
/**
* The module entry point routine. It is this routine that
* must populate the structure that is referred to as the
* "module object", this is a structure with the set of
* external entry points for this module.
*
* @return The module object
*/
MONITOR_OBJECT *
GetModuleObject()
{
return &MyObject;
}
/**
* Start the instance of the monitor, returning a handle on the monitor.
*
* This function creates a thread to execute the actual monitoring.
*
* @param arg The current handle - NULL if first start
* @return A handle to use when interacting with the monitor
*/
static void *
startMonitor(void *arg)
{
MYSQL_MONITOR *handle;
if (arg)
{
handle = arg; /* Must be a restart */
handle->shutdown = 0;
}
else
{
if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL)
return NULL;
handle->databases = NULL;
handle->shutdown = 0;
handle->defaultUser = NULL;
handle->defaultPasswd = NULL;
handle->id = MONITOR_DEFAULT_ID;
handle->interval = MONITOR_INTERVAL;
handle->replicationHeartbeat = 0;
handle->detectStaleMaster = 0;
handle->master = NULL;
spinlock_init(&handle->lock);
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
return handle;
}
/**
* Stop a running monitor
*
* @param arg Handle on thr running monior
*/
static void
stopMonitor(void *arg)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
handle->shutdown = 1;
thread_wait((void *)handle->tid);
}
/**
* Register a server that must be added to the monitored servers for
* a monitoring module.
*
* @param arg A handle on the running monitor module
* @param server The server to add
*/
static void
registerServer(void *arg, SERVER *server)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *ptr, *db;
if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL)
return;
db->server = server;
db->con = NULL;
db->next = NULL;
db->mon_err_count = 0;
db->mon_prev_status = 0;
/* pending status is updated by monitorMain */
db->pending_status = 0;
spinlock_acquire(&handle->lock);
if (handle->databases == NULL)
handle->databases = db;
else
{
ptr = handle->databases;
while (ptr->next != NULL)
ptr = ptr->next;
ptr->next = db;
}
spinlock_release(&handle->lock);
}
/**
* Remove a server from those being monitored by a monitoring module
*
* @param arg A handle on the running monitor module
* @param server The server to remove
*/
static void
unregisterServer(void *arg, SERVER *server)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *ptr, *lptr;
spinlock_acquire(&handle->lock);
if (handle->databases == NULL)
{
spinlock_release(&handle->lock);
return;
}
if (handle->databases->server == server)
{
ptr = handle->databases;
handle->databases = handle->databases->next;
free(ptr);
}
else
{
ptr = handle->databases;
while (ptr->next != NULL && ptr->next->server != server)
ptr = ptr->next;
if (ptr->next)
{
lptr = ptr->next;
ptr->next = ptr->next->next;
free(lptr);
}
}
spinlock_release(&handle->lock);
}
/**
* Set the default username and password to use to monitor if the server does not
* override this.
*
* @param arg The handle allocated by startMonitor
* @param uname The default user name
* @param passwd The default password
*/
static void
defaultUser(void *arg, char *uname, char *passwd)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
if (handle->defaultUser)
free(handle->defaultUser);
if (handle->defaultPasswd)
free(handle->defaultPasswd);
handle->defaultUser = strdup(uname);
handle->defaultPasswd = strdup(passwd);
}
/**
* Daignostic interface
*
* @param dcb DCB to print diagnostics
* @param arg The monitor handle
*/
static void diagnostics(DCB *dcb, void *arg)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *db;
char *sep;
switch (handle->status)
{
case MONITOR_RUNNING:
dcb_printf(dcb, "\tMonitor running\n");
break;
case MONITOR_STOPPING:
dcb_printf(dcb, "\tMonitor stopping\n");
break;
case MONITOR_STOPPED:
dcb_printf(dcb, "\tMonitor stopped\n");
break;
}
dcb_printf(dcb,"\tSampling interval:\t%lu milliseconds\n", handle->interval);
dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "\tMonitored servers: ");
db = handle->databases;
sep = "";
while (db)
{
dcb_printf(dcb,
"%s%s:%d",
sep,
db->server->name,
db->server->port);
sep = ", ";
db = db->next;
}
dcb_printf(dcb, "\n");
}
/**
* Monitor an individual server
*
* @param handle The MySQL Monitor object
* @param database The database to probe
*/
static void
monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database)
{
MYSQL_ROW row;
MYSQL_RES *result;
int num_fields;
int isslave = 0;
int ismaster = 0;
char *uname = handle->defaultUser;
char *passwd = handle->defaultPasswd;
unsigned long int server_version = 0;
char *server_string;
if (database->server->monuser != NULL)
{
uname = database->server->monuser;
passwd = database->server->monpw;
}
if (uname == NULL)
return;
/* Don't probe servers in maintenance mode */
if (SERVER_IN_MAINT(database->server))
return;
/** Store prevous status */
database->mon_prev_status = database->server->status;
if (database->con == NULL || mysql_ping(database->con) != 0)
{
char *dpwd = decryptPassword(passwd);
int rc;
int read_timeout = 1;
database->con = mysql_init(NULL);
rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout);
if (mysql_real_connect(database->con,
database->server->name,
uname,
dpwd,
NULL,
database->server->port,
NULL,
0) == NULL)
{
free(dpwd);
if (mon_print_fail_status(database))
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Monitor was unable to connect to "
"server %s:%d : \"%s\"",
database->server->name,
database->server->port,
mysql_error(database->con))));
}
/* The current server is not running
*
* Store server NOT running in server and monitor server pending struct
*
*/
if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
{
server_set_status(database->server, SERVER_AUTH_ERROR);
monitor_set_pending_status(database, SERVER_AUTH_ERROR);
}
server_clear_status(database->server, SERVER_RUNNING);
monitor_clear_pending_status(database, SERVER_RUNNING);
/* Also clear M/S state in both server and monitor server pending struct */
server_clear_status(database->server, SERVER_SLAVE);
server_clear_status(database->server, SERVER_MASTER);
monitor_clear_pending_status(database, SERVER_SLAVE);
monitor_clear_pending_status(database, SERVER_MASTER);
/* Clean addition status too */
server_clear_status(database->server, SERVER_STALE_STATUS);
monitor_clear_pending_status(database, SERVER_STALE_STATUS);
return;
} else {
server_clear_status(database->server, SERVER_AUTH_ERROR);
monitor_clear_pending_status(database, SERVER_AUTH_ERROR);
}
free(dpwd);
}
/* Store current status in both server and monitor server pending struct */
server_set_status(database->server, SERVER_RUNNING);
monitor_set_pending_status(database, SERVER_RUNNING);
/* get server version from current server */
server_version = mysql_get_server_version(database->con);
/* get server version string */
server_string = (char *)mysql_get_server_info(database->con);
if (server_string) {
database->server->server_string = realloc(database->server->server_string, strlen(server_string)+1);
if (database->server->server_string)
strcpy(database->server->server_string, server_string);
}
/* get server_id form current node */
if (mysql_query(database->con, "SELECT @@server_id") == 0
&& (result = mysql_store_result(database->con)) != NULL)
{
long server_id = -1;
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result)))
{
server_id = strtol(row[0], NULL, 10);
if ((errno == ERANGE && (server_id == LONG_MAX
|| server_id == LONG_MIN)) || (errno != 0 && server_id == 0))
{
server_id = -1;
}
database->server->node_id = server_id;
}
mysql_free_result(result);
}
/* Check if the Slave_SQL_Running and Slave_IO_Running status is
* set to Yes
*/
/* Check first for MariaDB 10.x.x and get status for multimaster replication */
if (server_version >= 100000) {
if (mysql_query(database->con, "SHOW ALL SLAVES STATUS") == 0
&& (result = mysql_store_result(database->con)) != NULL)
{
int i = 0;
long master_id = -1;
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result)))
{
/* get Slave_IO_Running and Slave_SQL_Running values*/
if (strncmp(row[12], "Yes", 3) == 0
&& strncmp(row[13], "Yes", 3) == 0) {
isslave += 1;
}
/* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building
* the replication tree, slaves ids will be added to master(s) and we will have at least the
* root master server.
* Please note, there could be no slaves at all if Slave_SQL_Running == 'No'
*/
if (strncmp(row[12], "Yes", 3) == 0) {
/* get Master_Server_Id values */
master_id = atol(row[41]);
if (master_id == 0)
master_id = -1;
}
i++;
}
/* store master_id of current node */
memcpy(&database->server->master_id, &master_id, sizeof(long));
mysql_free_result(result);
/* If all configured slaves are running set this node as slave */
if (isslave > 0 && isslave == i)
isslave = 1;
else
isslave = 0;
}
} else {
if (mysql_query(database->con, "SHOW SLAVE STATUS") == 0
&& (result = mysql_store_result(database->con)) != NULL)
{
long master_id = -1;
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result)))
{
/* get Slave_IO_Running and Slave_SQL_Running values*/
if (strncmp(row[10], "Yes", 3) == 0
&& strncmp(row[11], "Yes", 3) == 0) {
isslave = 1;
}
/* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building
* the replication tree, slaves ids will be added to master(s) and we will have at least the
* root master server.
* Please note, there could be no slaves at all if Slave_SQL_Running == 'No'
*/
if (strncmp(row[10], "Yes", 3) == 0) {
/* get Master_Server_Id values */
master_id = atol(row[39]);
if (master_id == 0)
master_id = -1;
}
}
/* store master_id of current node */
memcpy(&database->server->master_id, &master_id, sizeof(long));
mysql_free_result(result);
}
}
/* get variable 'read_only' set by an external component */
if (mysql_query(database->con, "SHOW GLOBAL VARIABLES LIKE 'read_only'") == 0
&& (result = mysql_store_result(database->con)) != NULL)
{
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result)))
{
if (strncasecmp(row[1], "OFF", 3) == 0) {
ismaster = 1;
}
}
mysql_free_result(result);
}
/* Remove addition info */
monitor_clear_pending_status(database, SERVER_STALE_STATUS);
/* Set the Slave Role */
if (isslave)
{
monitor_set_pending_status(database, SERVER_SLAVE);
/* Avoid any possible stale Master state */
monitor_clear_pending_status(database, SERVER_MASTER);
/* Set replication depth to 1 */
database->server->depth = 1;
} else {
/* Avoid any possible Master/Slave stale state */
monitor_clear_pending_status(database, SERVER_SLAVE);
monitor_clear_pending_status(database, SERVER_MASTER);
}
/* Set the Master role */
if (isslave && ismaster)
{
monitor_clear_pending_status(database, SERVER_SLAVE);
monitor_set_pending_status(database, SERVER_MASTER);
/* Set replication depth to 0 */
database->server->depth = 0;
}
}
/**
* The entry point for the monitoring module thread
*
* @param arg The handle of the monitor
*/
static void
monitorMain(void *arg)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
MONITOR_SERVERS *ptr;
int detect_stale_master = handle->detectStaleMaster;
MONITOR_SERVERS *root_master;
size_t nrounds = 0;
if (mysql_thread_init())
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Fatal : mysql_thread_init failed in monitor "
"module. Exiting.\n")));
return;
}
handle->status = MONITOR_RUNNING;
while (1)
{
if (handle->shutdown)
{
handle->status = MONITOR_STOPPING;
mysql_thread_end();
handle->status = MONITOR_STOPPED;
return;
}
/** Wait base interval */
thread_millisleep(MON_BASE_INTERVAL_MS);
/**
* Calculate how far away the monitor interval is from its full
* cycle and if monitor interval time further than the base
* interval, then skip monitoring checks. Excluding the first
* round.
*/
if (nrounds != 0 &&
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >=
MON_BASE_INTERVAL_MS)
{
nrounds += 1;
continue;
}
nrounds += 1;
/* start from the first server in the list */
ptr = handle->databases;
while (ptr)
{
/* copy server status into monitor pending_status */
ptr->pending_status = ptr->server->status;
/* monitor current node */
monitorDatabase(handle, ptr);
if (mon_status_changed(ptr))
{
dcb_call_foreach(DCB_REASON_NOT_RESPONDING);
}
if (mon_status_changed(ptr) ||
mon_print_fail_status(ptr))
{
LOGIF(LM, (skygw_log_write_flush(
LOGFILE_MESSAGE,
"Backend server %s:%d state : %s",
ptr->server->name,
ptr->server->port,
STRSRVSTATUS(ptr->server))));
}
if (SERVER_IS_DOWN(ptr->server))
{
/** Increase this server'e error count */
ptr->mon_err_count += 1;
}
else
{
/** Reset this server's error count */
ptr->mon_err_count = 0;
}
ptr = ptr->next;
}
/* Get Master server pointer */
root_master = get_current_master(handle);
/* Update server status from monitor pending status on that server*/
ptr = handle->databases;
while (ptr)
{
if (! SERVER_IN_MAINT(ptr->server)) {
/* If "detect_stale_master" option is On, let's use the previus master */
if (detect_stale_master && root_master && (!strcmp(ptr->server->name, root_master->server->name) && ptr->server->port == root_master->server->port) && (ptr->server->status & SERVER_MASTER) && !(ptr->pending_status & SERVER_MASTER)) {
/* in this case server->status will not be updated from pending_status */
LOGIF(LM, (skygw_log_write_flush(
LOGFILE_MESSAGE, "[mysql_mon]: root server [%s:%i] is no longer Master, let's use it again even if it could be a stale master, you have been warned!", ptr->server->name, ptr->server->port)));
/* Set the STALE bit for this server in server struct */
server_set_status(ptr->server, SERVER_STALE_STATUS);
} else {
ptr->server->status = ptr->pending_status;
}
}
ptr = ptr->next;
}
}
}
/**
* Set the monitor sampling interval.
*
* @param arg The handle allocated by startMonitor
* @param interval The interval to set in monitor struct, in milliseconds
*/
static void
setInterval(void *arg, size_t interval)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
memcpy(&handle->interval, &interval, sizeof(unsigned long));
}
/**
* Enable/Disable the MySQL Replication Stale Master dectection, allowing a previouvsly detected master to still act as a Master.
* This option must be enabled in order to keep the Master when the replication is stopped or removed from slaves.
* If the replication is still stopped when MaxSclale is restarted no Master will be available.
*
* @param arg The handle allocated by startMonitor
* @param enable To enable it 1, disable it with 0
*/
static void
detectStaleMaster(void *arg, int enable)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
memcpy(&handle->detectStaleMaster, &enable, sizeof(int));
}
static bool mon_status_changed(
MONITOR_SERVERS* mon_srv)
{
bool succp;
if (mon_srv->mon_prev_status != mon_srv->server->status)
{
succp = true;
}
else
{
succp = false;
}
return succp;
}
static bool mon_print_fail_status(
MONITOR_SERVERS* mon_srv)
{
bool succp;
int errcount = mon_srv->mon_err_count;
uint8_t modval;
modval = 1<<(MIN(errcount/10, 7));
if (SERVER_IS_DOWN(mon_srv->server) && errcount%modval == 0)
{
succp = true;
}
else
{
succp = false;
}
return succp;
}
/**
* Set a pending status bit in the monior server
*
* @param server The server to update
* @param bit The bit to clear for the server
*/
static void
monitor_set_pending_status(MONITOR_SERVERS *ptr, int bit)
{
ptr->pending_status |= bit;
}
/**
* Clear a pending status bit in the monior server
*
* @param server The server to update
* @param bit The bit to clear for the server
*/
static void
monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit)
{
ptr->pending_status &= ~bit;
}
/*******
* This function returns the master server
* from a set of MySQL Multi Master monitored servers
* and returns the root server (that has SERVER_MASTER bit)
* The server is returned even for servers in 'maintenance' mode.
*
* @param handle The monitor handle
* @return The server at root level with SERVER_MASTER bit
*/
static MONITOR_SERVERS *get_current_master(MYSQL_MONITOR *handle) {
MONITOR_SERVERS *ptr;
ptr = handle->databases;
while (ptr)
{
/* The server could be in SERVER_IN_MAINT
* that means SERVER_IS_RUNNING returns 0
* Let's check only for SERVER_IS_DOWN: server is not running
*/
if (SERVER_IS_DOWN(ptr->server)) {
ptr = ptr->next;
continue;
}
if (ptr->server->depth == 0) {
handle->master = ptr;
}
ptr = ptr->next;
}
/*
* Return the root master
*/
if (handle->master != NULL) {
/* If the root master is in MAINT, return NULL */
if (SERVER_IN_MAINT(handle->master->server)) {
return NULL;
} else {
return handle->master;
}
} else {
return NULL;
}
}

View File

@ -43,6 +43,7 @@
* 28/08/14 Massimiliano Pinto Added detectStaleMaster feature: previous detected master will be used again, even if the replication is stopped.
* This means both IO and SQL threads are not working on slaves.
* This option is not enabled by default.
* 10/11/14 Massimiliano Pinto Addition of setNetworkTimeout for connect, read, write
*
* @endverbatim
*/
@ -65,7 +66,7 @@ extern int lm_enabled_logfiles_bitmask;
static void monitorMain(void *);
static char *version_str = "V1.3.0";
static char *version_str = "V1.4.0";
MODULE_INFO info = {
MODULE_API_MONITOR,
@ -84,6 +85,7 @@ 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);
static MONITOR_SERVERS *getServerByNodeId(MONITOR_SERVERS *, long);
@ -103,9 +105,11 @@ static MONITOR_OBJECT MyObject = {
defaultUser,
diagnostics,
setInterval,
setNetworkTimeout,
defaultId,
replicationHeartbeat,
detectStaleMaster
detectStaleMaster,
NULL
};
/**
@ -177,6 +181,9 @@ MYSQL_MONITOR *handle;
handle->replicationHeartbeat = 0;
handle->detectStaleMaster = 0;
handle->master = NULL;
handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT;
handle->read_timeout=DEFAULT_READ_TIMEOUT;
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
spinlock_init(&handle->lock);
}
handle->tid = (THREAD)thread_start(monitorMain, handle);
@ -323,6 +330,9 @@ char *sep;
dcb_printf(dcb,"\tMaxScale MonitorId:\t%lu\n", handle->id);
dcb_printf(dcb,"\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled");
dcb_printf(dcb,"\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
dcb_printf(dcb,"\tConnect Timeout:\t%i seconds\n", handle->connect_timeout);
dcb_printf(dcb,"\tRead Timeout:\t\t%i seconds\n", handle->read_timeout);
dcb_printf(dcb,"\tWrite Timeout:\t\t%i seconds\n", handle->write_timeout);
dcb_printf(dcb, "\tMonitored servers: ");
db = handle->databases;
@ -378,11 +388,15 @@ char *server_string;
{
char *dpwd = decryptPassword(passwd);
int rc;
int read_timeout = 1;
int connect_timeout = handle->connect_timeout;
int read_timeout = handle->read_timeout;
int write_timeout = handle->write_timeout;
database->con = mysql_init(NULL);
rc = mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout);
rc = mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout);
rc = mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *)&write_timeout);
if (mysql_real_connect(database->con,
database->server->name,
@ -864,6 +878,13 @@ static void set_master_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *databas
char heartbeat_insert_query[512]="";
char heartbeat_purge_query[512]="";
if (handle->master == NULL) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"[mysql_mon]: set_master_heartbeat called without an available Master server")));
return;
}
/* create the maxscale_schema database */
if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) {
LOGIF(LE, (skygw_log_write_flush(
@ -969,6 +990,13 @@ static void set_slave_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *database
MYSQL_RES *result;
int num_fields;
if (handle->master == NULL) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"[mysql_mon]: set_slave_heartbeat called without an available Master server")));
return;
}
/* Get the master_timestamp value from maxscale_schema.replication_heartbeat table */
sprintf(select_heartbeat_query, "SELECT master_timestamp "
@ -1203,3 +1231,65 @@ monitor_clear_pending_status(MONITOR_SERVERS *ptr, int bit)
{
ptr->pending_status &= ~bit;
}
/**
* Set the default id to use in the monitor.
*
* @param arg The handle allocated by startMonitor
* @param type The connect timeout type
* @param value The timeout value to set
*/
static void
setNetworkTimeout(void *arg, int type, int value)
{
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
int max_timeout = (int)(handle->interval/1000);
int new_timeout = max_timeout -1;
if (new_timeout <= 0)
new_timeout = DEFAULT_CONNECT_TIMEOUT;
switch(type) {
case MONITOR_CONNECT_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->connect_timeout, &value, sizeof(int));
} else {
memcpy(&handle->connect_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Connect Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
case MONITOR_READ_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->read_timeout, &value, sizeof(int));
} else {
memcpy(&handle->read_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Read Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
case MONITOR_WRITE_TIMEOUT:
if (value < max_timeout) {
memcpy(&handle->write_timeout, &value, sizeof(int));
} else {
memcpy(&handle->write_timeout, &new_timeout, sizeof(int));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"warning : Monitor Write Timeout %i is greater than monitor interval ~%i seconds"
", lowering to %i seconds", value, max_timeout, new_timeout)));
}
break;
default:
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Monitor setNetworkTimeout received an unsupported action type %i", type)));
break;
}
}

View File

@ -33,6 +33,8 @@
* 28/05/14 Massimiliano Pinto Addition of new fields in MYSQL_MONITOR struct
* 24/06/14 Massimiliano Pinto Addition of master field in MYSQL_MONITOR struct and MONITOR_MAX_NUM_SLAVES
* 28/08/14 Massimiliano Pinto Addition of detectStaleMaster
* 30/10/14 Massimiliano Pinto Addition of disableMasterFailback
* 07/11/14 Massimiliano Pinto Addition of NetworkTimeout: connect, read, write
*
* @endverbatim
*/
@ -65,8 +67,16 @@ typedef struct {
unsigned long id; /**< Monitor ID */
int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */
int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
MONITOR_SERVERS *master; /**< Master server for MySQL Master/Slave replication */
MONITOR_SERVERS *databases; /**< Linked list of servers to monitor */
int connect_timeout; /**< Connect timeout in seconds for mysql_real_connect */
int read_timeout; /**< Timeout in seconds to read from the server.
* There are retries and the total effective timeout value is three times the option value.
*/
int write_timeout; /**< Timeout in seconds for each attempt to write to the server.
* There are retries and the total effective timeout value is two times the option value.
*/
} MYSQL_MONITOR;
#define MONITOR_RUNNING 1

View File

@ -73,6 +73,8 @@ static MONITOR_OBJECT MyObject = {
setInterval,
NULL,
NULL,
NULL,
NULL,
NULL
};

View File

@ -40,6 +40,7 @@
#include <httpd.h>
#include <gw.h>
#include <modinfo.h>
#include <log_manager.h>
MODULE_INFO info = {
MODULE_API_PROTOCOL,
@ -48,6 +49,8 @@ MODULE_INFO info = {
"An experimental HTTPD implementation for use in admnistration"
};
extern int lm_enabled_logfiles_bitmask;
#define ISspace(x) isspace((int)(x))
#define HTTP_SERVER_STRING "MaxScale(c) v.1.0.0"
static char *version_str = "V1.0.1";
@ -171,7 +174,7 @@ HTTPD_session *client_data = NULL;
j++;
}
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) {
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf) - 1)) {
url[i] = buf[j];
i++; j++;
}
@ -236,7 +239,7 @@ HTTPD_session *client_data = NULL;
dcb_printf(dcb, "Welcome to HTTPD MaxScale (c) %s\n\n", version_str);
if (strcmp(url, "/show") == 0) {
if (strlen(query_string)) {
if (query_string && strlen(query_string)) {
if (strcmp(query_string, "dcb") == 0)
dprintAllDCBs(dcb);
if (strcmp(query_string, "session") == 0)
@ -327,25 +330,29 @@ int n_connect = 0;
else
{
atomic_add(&dcb->stats.n_accepts, 1);
client = dcb_alloc(DCB_ROLE_REQUEST_HANDLER);
client->fd = so;
client->remote = strdup(inet_ntoa(addr.sin_addr));
memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL));
if((client = dcb_alloc(DCB_ROLE_REQUEST_HANDLER))){
client->fd = so;
client->remote = strdup(inet_ntoa(addr.sin_addr));
memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL));
/* we don't need the session */
client->session = NULL;
/* we don't need the session */
client->session = NULL;
/* create the session data for HTTPD */
client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session));
client->data = client_data;
/* create the session data for HTTPD */
client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session));
client->data = client_data;
if (poll_add_dcb(client) == -1)
{
return n_connect;
if (poll_add_dcb(client) == -1)
{
close(so);
return n_connect;
}
n_connect++;
}
n_connect++;
}
}
return n_connect;
}
@ -373,7 +380,8 @@ httpd_listen(DCB *listener, char *config)
{
struct sockaddr_in addr;
int one = 1;
int rc;
int rc;
int syseno = 0;
memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL));
if (!parse_bindconfig(config, 6442, &addr))
@ -385,12 +393,16 @@ int rc;
}
/* socket options */
setsockopt(listener->fd,
syseno = setsockopt(listener->fd,
SOL_SOCKET,
SO_REUSEADDR,
(char *)&one,
sizeof(one));
if(syseno != 0){
skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno));
return 0;
}
/* set NONBLOCKING mode */
setnonblocking(listener->fd);
@ -403,9 +415,7 @@ int rc;
rc = listen(listener->fd, SOMAXCONN);
if (rc == 0) {
fprintf(stderr,
"Listening http connections at %s\n",
config);
LOGIF(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening httpd connections at %s", config)));
} else {
int eno = errno;
errno = 0;
@ -439,15 +449,19 @@ static int httpd_get_line(int sock, char *buf, int size) {
if (c == '\r') {
n = recv(sock, &c, 1, MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
if ((n > 0) && (c == '\n')) {
if(recv(sock, &c, 1, 0) < 0){
c = '\n';
}
} else {
c = '\n';
}
}
buf[i] = c;
i++;
} else
} else {
c = '\n';
}
}
buf[i] = '\0';

View File

@ -44,6 +44,7 @@
* 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake
* 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
* 10/11/2014 Massimiliano Pinto Client charset is passed to backend
*
*/
#include <modinfo.h>
@ -270,6 +271,11 @@ static int gw_read_backend_event(DCB *dcb) {
CHK_SESSION(session);
if (session == NULL)
{
rc = 0;
goto return_with_lock;
}
router = session->service->router;
router_instance = session->service->router_instance;
rsession = session->router_session;
@ -378,7 +384,6 @@ static int gw_read_backend_event(DCB *dcb) {
ERRACT_REPLY_CLIENT,
&succp);
gwbuf_free(errbuf);
ss_dassert(!succp);
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [gw_read_backend_event] "
@ -388,13 +393,11 @@ static int gw_read_backend_event(DCB *dcb) {
dcb,
dcb->session)));
if (session != NULL)
{
spinlock_acquire(&session->ses_lock);
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
}
dcb_close(dcb);
spinlock_acquire(&session->ses_lock);
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
ss_dassert(dcb->dcb_errhandle_called);
dcb_close(dcb);
}
rc = 1;
goto return_rc;
@ -468,6 +471,7 @@ static int gw_read_backend_event(DCB *dcb) {
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
}
ss_dassert(dcb->dcb_errhandle_called);
dcb_close(dcb);
rc = 0;
goto return_rc;
@ -852,12 +856,17 @@ static int gw_error_backend_event(DCB *dcb)
&succp);
gwbuf_free(errbuf);
/** There are not required backends available, close session. */
if (!succp) {
/**
* If error handler fails it means that routing session can't continue
* and it must be closed. In success, only this DCB is closed.
*/
if (!succp)
{
spinlock_acquire(&session->ses_lock);
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
}
ss_dassert(dcb->dcb_errhandle_called);
dcb_close(dcb);
retblock:
@ -906,6 +915,9 @@ static int gw_create_backend_connection(
/** Copy client flags to backend protocol */
protocol->client_capabilities =
((MySQLProtocol *)(backend_dcb->session->client->protocol))->client_capabilities;
/** Copy client charset to backend protocol */
protocol->charset =
((MySQLProtocol *)(backend_dcb->session->client->protocol))->charset;
/*< if succeed, fd > 0, -1 otherwise */
rv = gw_do_connect_to_backend(server->name, server->port, &fd);
@ -1047,6 +1059,7 @@ gw_backend_hangup(DCB *dcb)
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
}
ss_dassert(dcb->dcb_errhandle_called);
dcb_close(dcb);
retblock:
@ -1078,8 +1091,8 @@ gw_backend_close(DCB *dcb)
mysql_protocol_done(dcb);
/**
* If session->state is set to STOPPING the client and the session must
* be closed too.
* If session->state is STOPPING, start closing client session.
* Otherwise only this backend connection is closed.
*/
if (session != NULL && session->state == SESSION_STATE_STOPPING)
{
@ -1144,7 +1157,24 @@ static int backend_write_delayqueue(DCB *dcb)
localq = dcb->delayq;
dcb->delayq = NULL;
spinlock_release(&dcb->delayqlock);
rc = dcb_write(dcb, localq);
if (MYSQL_IS_CHANGE_USER(((uint8_t *)GWBUF_DATA(localq))))
{
MYSQL_session* mses;
GWBUF* new_packet;
mses = (MYSQL_session *)dcb->session->client->data;
new_packet = gw_create_change_user_packet(
mses,
(MySQLProtocol *)dcb->protocol);
/**
* Remove previous packet which lacks scramble
* and append the new.
*/
localq = gwbuf_consume(localq, GWBUF_LENGTH(localq));
localq = gwbuf_append(localq, new_packet);
}
rc = dcb_write(dcb, localq);
}
if (rc == 0)
@ -1157,42 +1187,42 @@ static int backend_write_delayqueue(DCB *dcb)
SESSION *session = dcb->session;
CHK_SESSION(session);
router = session->service->router;
router_instance = session->service->router_instance;
rsession = session->router_session;
if (session != NULL)
{
router = session->service->router;
router_instance = session->service->router_instance;
rsession = session->router_session;
#if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Backend write delayqueue error handling.")));
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Backend write delayqueue error handling.")));
#endif
errbuf = mysql_create_custom_error(
1,
0,
"Failed to write buffered data to back-end server. "
"Buffer was empty or back-end was disconnected during "
"operation. Attempting to find a new backend.");
router->handleError(router_instance,
rsession,
errbuf,
dcb,
ERRACT_NEW_CONNECTION,
&succp);
gwbuf_free(errbuf);
errbuf = mysql_create_custom_error(
1,
0,
"Failed to write buffered data to back-end server. "
"Buffer was empty or back-end was disconnected during "
"operation. Attempting to find a new backend.");
router->handleError(router_instance,
rsession,
errbuf,
dcb,
ERRACT_NEW_CONNECTION,
&succp);
gwbuf_free(errbuf);
if (!succp)
{
if (session != NULL)
{
if (!succp)
{
spinlock_acquire(&session->ses_lock);
session->state = SESSION_STATE_STOPPING;
spinlock_release(&session->ses_lock);
}
dcb_close(dcb);
}
}
ss_dassert(dcb->dcb_errhandle_called);
dcb_close(dcb);
}
}
}
return rc;
}
@ -1253,6 +1283,16 @@ static int gw_change_user(
/* get new database name */
strcpy(database, (char *)client_auth_packet);
/* get character set */
if (strlen(database)) {
client_auth_packet += strlen(database) + 1;
} else {
client_auth_packet++;
}
if (client_auth_packet && *client_auth_packet)
memcpy(&backend_protocol->charset, client_auth_packet, sizeof(int));
/* save current_database name */
strcpy(current_database, current_session->db);
@ -1266,13 +1306,25 @@ static int gw_change_user(
* decode the token and check the password.
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
*/
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1);
auth_ret = gw_check_mysql_scramble_data(backend->session->client,
auth_token,
auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
username,
client_sha1);
if (auth_ret != 0) {
if (!service_refresh_users(backend->session->client->service)) {
/* Try authentication again with new repository data */
/* Note: if no auth client authentication will fail */
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1);
auth_ret = gw_check_mysql_scramble_data(
backend->session->client,
auth_token, auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
username,
client_sha1);
}
}
@ -1336,7 +1388,7 @@ static int gw_change_user(
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
/*
* Now copy new data into user session
*/
*/
strcpy(current_session->user, username);
strcpy(current_session->db, database);
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));
@ -1361,8 +1413,8 @@ static GWBUF* process_response_data (
GWBUF* readbuf,
int nbytes_to_process) /*< number of new bytes read */
{
int npackets_left = 0; /*< response's packet count */
size_t nbytes_left = 0; /*< nbytes to be read for the packet */
int npackets_left = 0; /*< response's packet count */
size_t nbytes_left = 0; /*< nbytes to be read for the packet */
MySQLProtocol* p;
GWBUF* outbuf = NULL;
@ -1506,4 +1558,4 @@ static bool sescmd_response_complete(
succp = false;
}
return succp;
}
}

View File

@ -36,6 +36,7 @@
* 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
* 13/10/2014 Massimiliano Pinto Added: dbname authentication check
* 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct
*
*/
#include <skygw_utils.h>
@ -444,6 +445,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
return 1;
}
/* get charset */
memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int));
/* get the auth token len */
memcpy(&auth_token_len,
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1,
@ -475,7 +479,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
auth_ret = gw_check_mysql_scramble_data(dcb,
auth_token,
auth_token_len,
protocol->scramble, sizeof(protocol->scramble),
protocol->scramble,
sizeof(protocol->scramble),
username,
stage1_hash);
@ -487,7 +492,14 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
if (!service_refresh_users(dcb->service)) {
/* Try authentication again with new repository data */
/* Note: if no auth client authentication will fail */
auth_ret = gw_check_mysql_scramble_data(dcb, auth_token, auth_token_len, protocol->scramble, sizeof(protocol->scramble), username, stage1_hash);
auth_ret = gw_check_mysql_scramble_data(
dcb,
auth_token,
auth_token_len,
protocol->scramble,
sizeof(protocol->scramble),
username,
stage1_hash);
}
}
@ -830,38 +842,23 @@ int gw_read_client_event(
}
/** succeed */
if (rc)
if (rc)
{
rc = 0; /**< here '0' means success */
}
else
{
GWBUF* errbuf;
bool succp;
errbuf = mysql_create_custom_error(
1,
0,
"Write to backend failed. Session closed.");
#if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Client routing error handling.")));
#endif
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Routing the query failed. "
"Session will be closed.")));
router->handleError(router_instance,
rsession,
errbuf,
dcb,
ERRACT_REPLY_CLIENT,
&succp);
gwbuf_free(errbuf);
ss_dassert(!succp);
modutil_send_mysql_err_packet(dcb,
1,
0,
2003,
"HY000",
"Write to backend failed. Session closed.");
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Routing the query failed. "
"Session will be closed.")));
dcb_close(dcb);
}
}
@ -951,6 +948,7 @@ int gw_MySQLListener(
char *config_bind)
{
int l_so;
int syseno = 0;
struct sockaddr_in serv_addr;
struct sockaddr_un local_addr;
struct sockaddr *current_addr;
@ -999,7 +997,10 @@ int gw_MySQLListener(
listen_dcb->fd = -1;
// socket options
setsockopt(l_so, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
if((syseno = setsockopt(l_so, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one))) != 0){
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno))));
}
// set NONBLOCKING mode
setnonblocking(l_so);
@ -1054,9 +1055,7 @@ int gw_MySQLListener(
rc = listen(l_so, 10 * SOMAXCONN);
if (rc == 0) {
fprintf(stderr,
"Listening MySQL connections at %s\n",
config_bind);
LOGIF(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening MySQL connections at %s", config_bind)));
} else {
int eno = errno;
errno = 0;
@ -1079,9 +1078,9 @@ int gw_MySQLListener(
strerror(errno));
return 0;
}
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
conn_open[l_so] = true;
#endif
#endif /* FAKE_CODE */
listen_dcb->func.accept = gw_MySQLAccept;
return 1;
@ -1112,6 +1111,7 @@ int gw_MySQLAccept(DCB *listener)
int sendbuf = GW_BACKEND_SO_SNDBUF;
socklen_t optlen = sizeof(sendbuf);
int eno = 0;
int syseno = 0;
int i = 0;
CHK_DCB(listener);
@ -1120,7 +1120,7 @@ int gw_MySQLAccept(DCB *listener)
retry_accept:
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
if (fail_next_accept > 0)
{
c_sock = -1;
@ -1128,16 +1128,16 @@ int gw_MySQLAccept(DCB *listener)
fail_next_accept -= 1;
} else {
fail_accept_errno = 0;
#endif /* SS_DEBUG */
#endif /* FAKE_CODE */
// new connection from client
c_sock = accept(listener->fd,
(struct sockaddr *) &client_conn,
&client_len);
eno = errno;
errno = 0;
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
}
#endif /* SS_DEBUG */
#endif /* FAKE_CODE */
if (c_sock == -1) {
@ -1212,13 +1212,22 @@ int gw_MySQLAccept(DCB *listener)
"%lu [gw_MySQLAccept] Accepted fd %d.",
pthread_self(),
c_sock)));
#endif /* SS_DEBUG */
#if defined(FAKE_CODE)
conn_open[c_sock] = true;
#endif
#endif /* FAKE_CODE */
/* set nonblocking */
sendbuf = GW_CLIENT_SO_SNDBUF;
setsockopt(c_sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, optlen);
if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, optlen)) != 0){
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno))));
}
sendbuf = GW_CLIENT_SO_RCVBUF;
setsockopt(c_sock, SOL_SOCKET, SO_RCVBUF, &sendbuf, optlen);
if((syseno = setsockopt(c_sock, SOL_SOCKET, SO_RCVBUF, &sendbuf, optlen)) != 0){
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno))));
}
setnonblocking(c_sock);
client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER);
@ -1374,7 +1383,6 @@ gw_client_close(DCB *dcb)
SESSION* session;
ROUTER_OBJECT* router;
void* router_instance;
void* rsession;
#if defined(SS_DEBUG)
MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol;
if (dcb->state == DCB_STATE_POLLING ||
@ -1391,7 +1399,7 @@ gw_client_close(DCB *dcb)
* session may be NULL if session_alloc failed.
* In that case, router session wasn't created.
*/
if (session != NULL)
if (session != NULL)
{
CHK_SESSION(session);
spinlock_acquire(&session->ses_lock);
@ -1400,13 +1408,22 @@ gw_client_close(DCB *dcb)
{
session->state = SESSION_STATE_STOPPING;
}
spinlock_release(&session->ses_lock);
router = session->service->router;
router_instance = session->service->router_instance;
rsession = session->router_session;
/** Close router session and all its connections */
router->closeSession(router_instance, rsession);
router_instance = session->service->router_instance;
router = session->service->router;
/**
* If router session is being created concurrently router
* session might be NULL and it shouldn't be closed.
*/
if (session->router_session != NULL)
{
spinlock_release(&session->ses_lock);
/** Close router session and all its connections */
router->closeSession(router_instance, session->router_session);
}
else
{
spinlock_release(&session->ses_lock);
}
}
return 1;
}

View File

@ -35,6 +35,7 @@
* x.y.z.%, x.y.%.%, x.%.%.%
* 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts.
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
* 10/11/2014 Massimiliano Pinto Charset at connect is passed to backend during authentication
*
*/
@ -274,8 +275,7 @@ int gw_read_backend_handshake(
payload += 4;
//Now decode mysql handshake
success = gw_decode_mysql_server_handshake(conn,
payload);
success = gw_decode_mysql_server_handshake(conn, payload);
if (success < 0) {
/* MySQL handshake has not been properly decoded
@ -563,6 +563,7 @@ int gw_send_authentication_to_backend(
char *curr_db = NULL;
uint8_t *curr_passwd = NULL;
unsigned int charset;
if (strlen(dbname))
curr_db = dbname;
@ -574,7 +575,10 @@ int gw_send_authentication_to_backend(
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
/** Copy client's flags to backend */
final_capabilities |= conn->client_capabilities;;
final_capabilities |= conn->client_capabilities;
/* get charset the client sent and use it for connection auth */
charset = conn->charset;
if (compress) {
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
@ -668,7 +672,7 @@ int gw_send_authentication_to_backend(
// set the charset
payload += 4;
*payload = '\x08';
*payload = charset;
payload++;
@ -736,13 +740,13 @@ int gw_send_authentication_to_backend(
*
*/
int gw_do_connect_to_backend(
char *host,
int port,
int* fd)
char *host,
int port,
int *fd)
{
struct sockaddr_in serv_addr;
int rv;
int so = 0;
int rv;
int so = 0;
int bufsize;
memset(&serv_addr, 0, sizeof serv_addr);
@ -750,8 +754,6 @@ int gw_do_connect_to_backend(
so = socket(AF_INET,SOCK_STREAM,0);
if (so < 0) {
int eno = errno;
errno = 0;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Establishing connection to backend server "
@ -759,8 +761,8 @@ int gw_do_connect_to_backend(
"due %d, %s.",
host,
port,
eno,
strerror(eno))));
errno,
strerror(errno))));
rv = -1;
goto return_rv;
}
@ -768,46 +770,62 @@ int gw_do_connect_to_backend(
setipaddress(&serv_addr.sin_addr, host);
serv_addr.sin_port = htons(port);
bufsize = GW_BACKEND_SO_SNDBUF;
setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
if(setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Failed to set socket options "
"%s:%d failed.\n\t\t Socket configuration failed "
"due %d, %s.",
host,
port,
errno,
strerror(errno))));
rv = -1;
/** Close socket */
goto close_so;
}
bufsize = GW_BACKEND_SO_RCVBUF;
setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
if(setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Failed to set socket options "
"%s:%d failed.\n\t\t Socket configuration failed "
"due %d, %s.",
host,
port,
errno,
strerror(errno))));
rv = -1;
/** Close socket */
goto close_so;
}
/* set socket to as non-blocking here */
setnonblocking(so);
rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (rv != 0) {
int eno = errno;
errno = 0;
if (eno == EINPROGRESS) {
if (rv != 0)
{
if (errno == EINPROGRESS)
{
rv = 1;
} else {
int rc;
int oldfd = so;
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Failed to connect backend server %s:%d, "
"due %d, %s.",
host,
port,
eno,
strerror(eno))));
/*< Close newly created socket. */
rc = close(so);
if (rc != 0) {
int eno = errno;
errno = 0;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Failed to "
"close socket %d due %d, %s.",
oldfd,
eno,
strerror(eno))));
}
goto return_rv;
errno,
strerror(errno))));
/** Close socket */
goto close_so;
}
}
*fd = so;
@ -819,11 +837,26 @@ int gw_do_connect_to_backend(
host,
port,
so)));
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
conn_open[so] = true;
#endif
#endif /* FAKE_CODE */
return_rv:
return rv;
close_so:
/*< Close newly created socket. */
if (close(so) != 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error: Failed to "
"close socket %d due %d, %s.",
so,
errno,
strerror(errno))));
}
goto return_rv;
}
/**
@ -1020,193 +1053,226 @@ int mysql_send_custom_error (
return dcb->func.write(dcb, buf);
}
/**
* Create COM_CHANGE_USER packet and store it to GWBUF
*
* @param mses MySQL session
* @param protocol protocol structure of the backend
*
* @return GWBUF buffer consisting of COM_CHANGE_USER packet
*
* @note the function doesn't fail
*/
GWBUF* gw_create_change_user_packet(
MYSQL_session* mses,
MySQLProtocol* protocol)
{
char* db;
char* user;
uint8_t* pwd;
GWBUF* buffer;
int compress = 0;
uint8_t* payload = NULL;
uint8_t* payload_start = NULL;
long bytes;
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
uint32_t server_capabilities = 0;
uint32_t final_capabilities = 0;
char dbpass[MYSQL_USER_MAXLEN + 1]="";
char* curr_db = NULL;
uint8_t* curr_passwd = NULL;
unsigned int charset;
db = mses->db;
user = mses->user;
pwd = mses->client_sha1;
if (strlen(db) > 0)
{
curr_db = db;
}
if (strlen((char *)pwd) > 0)
{
curr_passwd = pwd;
}
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
/** Copy client's flags to backend */
final_capabilities |= protocol->client_capabilities;
/* get charset the client sent and use it for connection auth */
charset = protocol->charset;
if (compress)
{
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (curr_passwd != NULL)
{
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";
/** hash1 is the function input, SHA1(real_password) */
memcpy(hash1, pwd, GW_MYSQL_SCRAMBLE_SIZE);
/**
* hash2 is the SHA1(input data), where
* input_data = SHA1(real_password)
*/
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
/** dbpass is the HEX form of SHA1(SHA1(real_password)) */
gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);
/** new_sha is the SHA1(CONCAT(scramble, hash2) */
gw_sha1_2_str(protocol->scramble,
GW_MYSQL_SCRAMBLE_SIZE,
hash2,
GW_MYSQL_SCRAMBLE_SIZE,
new_sha);
/** compute the xor in client_scramble */
gw_str_xor(client_scramble,
new_sha, hash1,
GW_MYSQL_SCRAMBLE_SIZE);
}
if (curr_db == NULL)
{
final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
else
{
final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
/**
* Protocol MySQL COM_CHANGE_USER for CLIENT_PROTOCOL_41
* 1 byte COMMAND
*/
bytes = 1;
/** add the user and a terminating char */
bytes += strlen(user);
bytes++;
/**
* next will be + 1 (scramble_len) + 20 (fixed_scramble) +
* (db + NULL term) + 2 bytes charset
*/
if (curr_passwd != NULL)
{
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
/** 1 byte for scramble_len */
bytes++;
/** db name and terminating char */
if (curr_db != NULL)
{
bytes += strlen(curr_db);
}
bytes++;
/** the charset */
bytes += 2;
bytes += strlen("mysql_native_password");
bytes++;
/** the packet header */
bytes += 4;
buffer = gwbuf_alloc(bytes);
/**
* Set correct type to GWBUF so that it will be handled like session
* commands
*/
buffer->gwbuf_type =
GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
payload = GWBUF_DATA(buffer);
memset(payload, '\0', bytes);
payload_start = payload;
/** set packet number to 0 */
payload[3] = 0x00;
payload += 4;
/** set the command COM_CHANGE_USER 0x11 */
payload[0] = 0x11;
payload++;
memcpy(payload, user, strlen(user));
payload += strlen(user);
payload++;
if (curr_passwd != NULL)
{
/** set the auth-length */
*payload = GW_MYSQL_SCRAMBLE_SIZE;
payload++;
/**
* copy the 20 bytes scramble data after
* packet_buffer+36+user+NULL+1 (byte of auth-length)
*/
memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
payload += GW_MYSQL_SCRAMBLE_SIZE;
}
else
{
/** skip the auth-length and write a NULL */
payload++;
}
/** if the db is not NULL append it */
if (curr_db != NULL)
{
memcpy(payload, curr_db, strlen(curr_db));
payload += strlen(curr_db);
}
payload++;
/** set the charset, 2 bytes */
*payload = charset;
payload++;
*payload = '\x00';
payload++;
memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
payload += strlen("mysql_native_password");
payload++;
/** put here the paylod size: bytes to write - 4 bytes packet header */
gw_mysql_set_byte3(payload_start, (bytes-4));
return buffer;
}
/**
* Write a MySQL CHANGE_USER packet to backend server
*
* @param conn MySQL protocol structure
* @param dbname The selected database
* @param user The selected user
* @param passwd The SHA1(real_password): Note real_password is unknown
* @param passwd The SHA1(real_password)
* @return 1 on success, 0 on failure
*/
int gw_send_change_user_to_backend(
char *dbname,
char *user,
uint8_t *passwd,
MySQLProtocol *conn)
char *dbname,
char *user,
uint8_t *passwd,
MySQLProtocol *conn)
{
int compress = 0;
int rv;
uint8_t *payload = NULL;
uint8_t *payload_start = NULL;
long bytes;
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
uint8_t client_capabilities[4];
uint32_t server_capabilities = 0;
uint32_t final_capabilities = 0;
char dbpass[MYSQL_USER_MAXLEN + 1]="";
GWBUF *buffer;
DCB *dcb;
char *curr_db = NULL;
uint8_t *curr_passwd = NULL;
if (strlen(dbname))
curr_db = dbname;
if (strlen((char *)passwd))
curr_passwd = passwd;
dcb = conn->owner_dcb;
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
/** Copy client's flags to backend */
final_capabilities |= conn->client_capabilities;;
if (compress) {
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (curr_passwd != NULL) {
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";
// hash1 is the function input, SHA1(real_password)
memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);
// hash2 is the SHA1(input data), where input_data = SHA1(real_password)
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
// dbpass is the HEX form of SHA1(SHA1(real_password))
gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);
// new_sha is the SHA1(CONCAT(scramble, hash2)
gw_sha1_2_str(conn->scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha);
// compute the xor in client_scramble
gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE);
}
if (curr_db == NULL) {
// without db
final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
} else {
final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
gw_mysql_set_byte4(client_capabilities, final_capabilities);
// Protocol MySQL COM_CHANGE_USER for CLIENT_PROTOCOL_41
// 1 byte COMMAND
bytes = 1;
// add the user
bytes += strlen(user);
// NULL byte for user string
bytes++;
// next will be + 1 (scramble_len) + 20 (fixed_scramble) + (dbname + NULL term) + 2 bytes charset
if (curr_passwd != NULL) {
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
// 1 byte for scramble_len
bytes++;
if (curr_db != NULL) {
bytes += strlen(curr_db);
}
// NULL byte for dbname string
bytes++;
// the charset
bytes += 2;
bytes += strlen("mysql_native_password");
bytes++;
// the packet header
bytes += 4;
// allocating the GWBUF
buffer = gwbuf_alloc(bytes);
/**
* Set correct type to GWBUF so that it will be handled like session
* commands should
*/
buffer->gwbuf_type = GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
payload = GWBUF_DATA(buffer);
// clearing data
memset(payload, '\0', bytes);
GWBUF *buffer;
int rc;
MYSQL_session* mses;
// save the start pointer
payload_start = payload;
mses = (MYSQL_session*)conn->owner_dcb->session->client->data;
buffer = gw_create_change_user_packet(mses, conn);
rc = conn->owner_dcb->func.write(conn->owner_dcb, buffer);
// set packet # = 1
payload[3] = 0x00;
payload += 4;
// set the command COM_CHANGE_USER \x11
payload[0] = 0x11;
payload++;
memcpy(payload, user, strlen(user));
payload += strlen(user);
payload++;
if (curr_passwd != NULL) {
// set the auth-length
*payload = GW_MYSQL_SCRAMBLE_SIZE;
payload++;
//copy the 20 bytes scramble data after packet_buffer+36+user+NULL+1 (byte of auth-length)
memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
payload += GW_MYSQL_SCRAMBLE_SIZE;
} else {
// skip the auth-length and write a NULL
payload++;
}
// if the db is not NULL append it
if (curr_db != NULL) {
memcpy(payload, curr_db, strlen(curr_db));
payload += strlen(curr_db);
payload++;
} else {
// skip the NULL
payload++;
if (rc != 0)
{
rc = 1;
}
// set the charset, 2 bytes!!!!
*payload = '\x08';
payload++;
*payload = '\x00';
payload++;
memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
payload += strlen("mysql_native_password");
payload++;
// put here the paylod size: bytes to write - 4 bytes packet header
gw_mysql_set_byte3(payload_start, (bytes-4));
rv = dcb->func.write(dcb, buffer);
if (rv == 0)
return 0;
else
return 1;
return rc;
}
/**
@ -1736,11 +1802,14 @@ void protocol_archive_srv_command(
{
p->protocol_cmd_history = server_command_copy(s1);
}
else
else /*< scan and count history commands */
{
len = 1;
while (h1->scom_next != NULL)
{
h1 = h1->scom_next;
len += 1;
}
h1->scom_next = server_command_copy(s1);
}
@ -2033,7 +2102,7 @@ char* get_username_from_auth(
first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23);
if (first_letter == '\0')
if (*first_letter == '\0')
{
rval = NULL;
goto retblock;

View File

@ -358,7 +358,8 @@ telnetd_listen(DCB *listener, char *config)
{
struct sockaddr_in addr;
int one = 1;
int rc;
int rc;
int syseno = 0;
memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL));
@ -372,7 +373,12 @@ int rc;
}
// socket options
setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
syseno = setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
if(syseno != 0){
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno))));
return 0;
}
// set NONBLOCKING mode
setnonblocking(listener->fd);
// bind address and port
@ -384,9 +390,7 @@ int rc;
rc = listen(listener->fd, SOMAXCONN);
if (rc == 0) {
fprintf(stderr,
"Listening telnet connections at %s\n",
config);
LOGIF(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening telnet connections at %s", config)));
} else {
int eno = errno;
errno = 0;

View File

@ -1,3 +1,7 @@
if(BUILD_TESTS)
add_subdirectory(test)
endif()
add_library(testroute SHARED testroute.c)
target_link_libraries(testroute log_manager utils)
install(TARGETS testroute DESTINATION modules)

View File

@ -414,7 +414,7 @@ struct subcommand disableoptions[] = {
}
};
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
static void fail_backendfd(void);
static void fail_clientfd(void);
@ -456,7 +456,7 @@ struct subcommand failoptions[] = {
{0, 0, 0}
}
};
#endif /* SS_DEBUG */
#endif /* FAKE_CODE */
static void telnetdAddUser(DCB *, char *, char *);
/**
@ -502,9 +502,9 @@ static struct {
{ "clear", clearoptions },
{ "disable", disableoptions },
{ "enable", enableoptions },
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
{ "fail", failoptions },
#endif
#endif /* FAKE_CODE */
{ "list", listoptions },
{ "reload", reloadoptions },
{ "remove", removeoptions },
@ -1113,7 +1113,7 @@ static void disable_log_action(DCB *dcb, char *arg1) {
skygw_log_disable(type);
}
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
static void fail_backendfd(void)
{
fail_next_backend_fd = true;
@ -1157,4 +1157,4 @@ static void fail_accept(
return ;
}
}
#endif
#endif /* FAKE_CODE */

View File

@ -828,6 +828,17 @@ static void handleError(
SESSION *session = backend_dcb->session;
session_state_t sesstate;
/** 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;

View File

@ -830,8 +830,16 @@ static void* newSession(
* Find a backend servers to connect to.
* This command requires that rsession's lock is held.
*/
rses_begin_locked_router_action(client_rses);
succp = rses_begin_locked_router_action(client_rses);
if(!succp)
{
free(client_rses->rses_backend_ref);
free(client_rses);
client_rses = NULL;
goto return_rses;
}
succp = select_connect_backend_servers(&master_ref,
backend_ref,
router_nservers,
@ -843,7 +851,9 @@ static void* newSession(
rses_end_locked_router_action(client_rses);
/** Both Master and at least 1 slave must be found */
/**
* Master and at least <min_nslaves> slaves must be found
*/
if (!succp) {
free(client_rses->rses_backend_ref);
free(client_rses);
@ -1029,6 +1039,9 @@ static void freeSession(
/**
* Provide the router with a pointer to a suitable backend dcb.
*
* As of Nov. 2014, slave which has least connections is always chosen.
*
* Detect failures in server statuses and reselect backends if necessary.
* If name is specified, server name becomes primary selection criteria.
*
@ -1527,12 +1540,12 @@ skygw_query_type_t is_read_tmp_table(
}
}
for(i = 0; i<tsize;i++)
{
free(tbl[i]);
}
if(tbl != NULL){
for(i = 0; i<tsize;i++)
{
free(tbl[i]);
}
free(tbl);
}
@ -1610,8 +1623,12 @@ void check_create_tmp_table(
rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES;
router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp;
}
else
{
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error : Call to malloc() failed.")));
}
}
if(rses_prop_tmp){
if (rses_prop_tmp->rses_prop_data.temp_tables == NULL)
{
h = hashtable_alloc(7, hashkeyfun, hashcmpfun);
@ -1619,10 +1636,13 @@ void check_create_tmp_table(
if (h != NULL)
{
rses_prop_tmp->rses_prop_data.temp_tables = h;
}
}else{
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error : Failed to allocate a new hashtable.")));
}
}
if (hkey &&
if (hkey && rses_prop_tmp->rses_prop_data.temp_tables &&
hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables,
(void *)hkey,
(void *)is_temp) == 0) /*< Conflict in hash table */
@ -1647,6 +1667,8 @@ void check_create_tmp_table(
}
}
#endif
}
free(hkey);
free(tblname);
}
@ -1730,7 +1752,10 @@ static int routeQuery(
goto retblock;
}
/** Read stored master DCB pointer */
/**
* Read stored master DCB pointer. If master is not set, routing must
* be aborted
*/
if ((master_dcb = router_cli_ses->rses_master_ref->bref_dcb) == NULL)
{
char* query_str = modutil_get_query(querybuf);
@ -2071,11 +2096,15 @@ static int routeQuery(
"route to master "
"but couldn't find "
"master in a "
"suitable state "
"failed.")));
"suitable state.")));
}
/**
* Master has changed. Return with error indicator.
*/
rses_end_locked_router_action(router_cli_ses);
succp = false;
ret = 0;
goto retblock;
}
}
@ -2645,7 +2674,7 @@ static bool select_connect_backend_servers(
const int min_nslaves = 0; /*< not configurable at the time */
bool is_synced_master;
int (*p)(const void *, const void *);
BACKEND* master_host = NULL;
BACKEND* master_host;
if (p_master_ref == NULL || backend_ref == NULL)
{
@ -2657,46 +2686,32 @@ static bool select_connect_backend_servers(
/* get the root Master */
master_host = get_root_master(backend_ref, router_nservers);
/**
* Master is already chosen and connected. It means that the function
* was called from error handling function or from some other similar
* function where session was already established but new slaves needed
* to be selected.
/**
* Existing session : master is already chosen and connected.
* The function was called because new slave must be selected to replace
* failed one.
*/
if (*p_master_ref != NULL &&
BREF_IS_IN_USE((*p_master_ref)))
{
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [select_connect_backend_servers] Master %p fd %d found.",
pthread_self(),
(*p_master_ref)->bref_dcb,
(*p_master_ref)->bref_dcb->fd)));
master_found = true;
master_connected = true;
if (*p_master_ref != NULL)
{
/**
* Ensure that *p_master_ref and master_host point to same backend
* and it has a master role.
* Ensure that backend reference is in use, stored master is
* still current root master.
*/
ss_dassert(master_host &&
((*p_master_ref)->bref_backend->backend_server ==
master_host->backend_server) &&
(master_host->backend_server->status &
(SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER);
}
/** New session or master failure case */
if (!BREF_IS_IN_USE((*p_master_ref)) ||
!SERVER_IS_MASTER((*p_master_ref)->bref_backend->backend_server) ||
master_host != (*p_master_ref)->bref_backend)
{
succp = false;
goto return_succp;
}
master_found = true;
master_connected = true;
}
/**
* New session : select master and slaves
*/
else
{
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [select_connect_backend_servers] Session %p doesn't "
"currently have a master chosen. Proceeding to master "
"selection.",
pthread_self(),
session)));
master_found = false;
master_connected = false;
}
@ -2735,11 +2750,6 @@ static bool select_connect_backend_servers(
b->backend_conn_count)));
}
#endif
/* assert with master_host */
ss_dassert(!master_connected ||
(master_host &&
((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) &&
SERVER_MASTER));
/**
* Sort the pointer list to servers according to connection counts. As
* a consequence those backends having least connections are in the
@ -2830,8 +2840,10 @@ static bool select_connect_backend_servers(
(max_slave_rlag == MAX_RLAG_UNDEFINED ||
(b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE &&
b->backend_server->rlag <= max_slave_rlag)) &&
(SERVER_IS_SLAVE(b->backend_server) || SERVER_IS_RELAY_SERVER(b->backend_server)) &&
(master_host != NULL && (b->backend_server != master_host->backend_server)))
(SERVER_IS_SLAVE(b->backend_server) ||
SERVER_IS_RELAY_SERVER(b->backend_server)) &&
(master_host != NULL &&
(b->backend_server != master_host->backend_server)))
{
slaves_found += 1;
@ -2893,6 +2905,12 @@ static bool select_connect_backend_servers(
else if (master_host &&
(b->backend_server == master_host->backend_server))
{
/**
* *p_master_ref must be assigned with this
* backend_ref pointer because its original value
* may have been lost when backend references were
* sorted (qsort).
*/
*p_master_ref = &backend_ref[i];
if (master_connected)
@ -4048,7 +4066,7 @@ static void rwsplit_process_router_options(
}
/**
* Error Handler routine to resolve backend failures. If it succeeds then there
* Error Handler routine to resolve _backend_ failures. If it succeeds then there
* are enough operative backends available and connected. Otherwise it fails,
* and session is terminated.
*
@ -4062,7 +4080,6 @@ static void rwsplit_process_router_options(
* Even if succp == true connecting to new slave may have failed. succp is to
* tell whether router has enough master/slave connections to continue work.
*/
static void handleError (
ROUTER* instance,
void* router_session,
@ -4076,27 +4093,38 @@ static void handleError (
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *)router_session;
CHK_DCB(backend_dcb);
#if defined(SS_DEBUG)
backend_dcb->dcb_errhandle_called = true;
#endif
/** 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;
}
session = backend_dcb->session;
if (session != NULL)
CHK_SESSION(session);
if (session == NULL || rses == NULL)
{
*succp = false;
return;
}
CHK_SESSION(session);
CHK_CLIENT_RSES(rses);
switch (action) {
case ERRACT_NEW_CONNECTION:
{
if (rses != NULL)
CHK_CLIENT_RSES(rses);
{
if (!rses_begin_locked_router_action(rses))
{
*succp = false;
return;
}
if (rses->rses_master_ref->bref_dcb == backend_dcb)
if (rses->rses_master_ref->bref_dcb == backend_dcb &&
!SERVER_IS_MASTER(rses->rses_master_ref->bref_backend->backend_server))
{
/** Master failed, can't recover */
LOGIF(LE, (skygw_log_write_flush(
@ -4195,6 +4223,11 @@ static bool handle_error_new_connection(
}
CHK_BACKEND_REF(bref);
/**
* If query was sent through the bref and it is waiting for reply from
* the backend server it is necessary to send an error to the client
* because it is waiting for reply.
*/
if (BREF_IS_WAITING_RESULT(bref))
{
DCB* client_dcb;
@ -4462,6 +4495,10 @@ static backend_ref_t* get_bref_from_dcb(
return bref;
}
/**
* Calls hang-up function for DCB if it is not both running and in
* master/slave/joined/ndb role. Called by DCB's callback routine.
*/
static int router_handle_state_switch(
DCB* dcb,
DCB_REASON reason,
@ -4502,6 +4539,7 @@ return_rc:
return rc;
}
static sescmd_cursor_t* backend_ref_get_sescmd_cursor (
backend_ref_t* bref)
{
@ -4623,7 +4661,7 @@ static BACKEND *get_root_master(
* Servers are checked even if they are in 'maintenance'
*
* @param rses pointer to router session
* @return pointer to backend reference of the root master
* @return pointer to backend reference of the root master or NULL
*
*/
static backend_ref_t* get_root_master_bref(
@ -4653,6 +4691,14 @@ static backend_ref_t* get_root_master_bref(
bref++;
i += 1;
}
if (candidate_bref == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Could not find master among the backend "
"servers. Previous master state : %s",
STRSRVSTATUS(BREFSRV(rses->rses_master_ref)))));
}
return candidate_bref;
}

View File

@ -1,2 +1,7 @@
add_test(NAME ReadWriteSplitTest COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/rwsplit.sh testrwsplit.log ${TEST_HOST} ${TEST_PORT_RW} ${TEST_MASTER_ID} ${TEST_USER} ${TEST_PASSWORD} ${CMAKE_CURRENT_SOURCE_DIR})
if(MYSQLCLIENT_FOUND)
add_test(NAME ReadWriteSplitLoginTest COMMAND $<TARGET_FILE:testconnect> 10000 ${TEST_HOST} ${MASTER_PORT} ${TEST_HOST} ${TEST_PORT_RW} 1.10)
endif()
add_subdirectory(test_hints)

View File

@ -0,0 +1,6 @@
if(MYSQLCLIENT_FOUND)
add_executable(testconnect testconnect.c)
message(STATUS "Linking against: ${MYSQLCLIENT_LIBRARIES}")
target_link_libraries(testconnect ${MYSQLCLIENT_LIBRARIES} ssl crypto dl z m rt pthread)
add_test(NAME ReadConnRouterLoginTest COMMAND $<TARGET_FILE:testconnect> 10000 ${TEST_HOST} ${MASTER_PORT} ${TEST_HOST} ${TEST_PORT} 1.10)
endif()

View File

@ -0,0 +1,161 @@
#include <my_config.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
int main(int argc, char** argv)
{
MYSQL* server;
char *host = NULL, *str_baseline = NULL,*str_test = NULL, *errmsg = NULL;
unsigned int port;
int rval, iterations,i;
clock_t begin,end;
size_t offset;
struct timeval real_begin,real_end,real_baseline,real_test;
time_t time;
double baseline, test, ratio, result;
if(argc < 7){
fprintf(stderr,"Usage: %s <iterations> <baseline host> <baseline port> <test host> <test port> <max result ratio>\n",argv[0]);
fprintf(stderr,"The ratio is measured as:\ntest time / baseline time\n");
fprintf(stderr,"The test fails if this ratio is exceeded.\n");
return 1;
}
;
if((str_baseline = calloc(256,sizeof(char))) == NULL){
return 1;
}
if((str_test = calloc(256,sizeof(char))) == NULL){
free(str_baseline);
return 1;
}
iterations = atoi(argv[1]);
host = strdup(argv[2]);
port = atoi(argv[3]);
ratio = atof(argv[6]);
rval = 0;
if(ratio <= 0.0){
return 1;
}
/**Testing direct connection to master*/
printf("Connecting to MySQL server through %s:%d.\n",host,port);
gettimeofday(&real_begin,NULL);
begin = clock();
if((server = mysql_init(NULL)) == NULL){
return 1;
}
if(mysql_real_connect(server,host,"maxuser","maxpwd",NULL,port,NULL,0) == NULL){
rval = 1;
printf( "Failed to connect to database: Error: %s\n",
mysql_error(server));
goto report;
}
for(i = 0;i<iterations;i++)
{
if(mysql_change_user(server,"maxuser","maxpwd",NULL)){
rval = 1;
printf( "Failed to change user: Error: %s\n",
mysql_error(server));
goto report;
}
}
mysql_close(server);
end = clock();
gettimeofday(&real_end,NULL);
baseline = (double)(end - begin)/CLOCKS_PER_SEC;
timersub(&real_end,&real_begin,&real_baseline);
free(host);
host = strdup(argv[4]);
port = atoi(argv[5]);
/**Testing connection to master through MaxScale*/
printf("Connecting to MySQL server through %s:%d.\n",host,port);
gettimeofday(&real_begin,NULL);
begin = clock();
if((server = mysql_init(NULL)) == NULL){
return 1;
}
if(mysql_real_connect(server,host,"maxuser","maxpwd",NULL,port,NULL,0) == NULL){
rval = 1;
printf("Failed to connect to database: Error: %s\n",
mysql_error(server));
goto report;
}
for(i = 0;i<iterations;i++)
{
if(mysql_change_user(server,"maxuser","maxpwd",NULL)){
rval = 1;
printf("Failed to change user: Error: %s\n",
mysql_error(server));
goto report;
}
}
mysql_close(server);
end = clock();
gettimeofday(&real_end,NULL);
test = (double)(end - begin)/CLOCKS_PER_SEC;
timersub(&real_end,&real_begin,&real_test);
report:
if(rval){
printf("\nTest failed: Errors during test run.\n");
}else{
struct tm *tm;
time = real_baseline.tv_sec;
tm = localtime(&time);
offset = strftime(str_baseline,256*sizeof(char),"%S",tm);
sprintf(str_baseline + offset,".%06d",(int)real_baseline.tv_usec);
time = real_test.tv_sec;
tm = localtime(&time);
offset = strftime(str_test,256*sizeof(char),"%S",tm);
sprintf(str_test + offset,".%06d",(int)real_test.tv_usec);
printf("\n\tCPU time in seconds\n\nDirect connection: %f\nThrough MaxScale: %f\n",baseline,test);
printf("\n\tReal time in seconds\n\nDirect connection: %s\nThrough MaxScale: %s\n",str_baseline,str_test);
double base_res = real_baseline.tv_sec + (real_baseline.tv_usec / 1000000.0);
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);
}
}
free(str_baseline);
free(str_test);
free(host);
free(errmsg);
return rval;
}

View File

@ -183,8 +183,7 @@ typedef enum skygw_chk_t {
((p) == MYSQL_COM_QUIT ? "COM_QUIT" : \
((p) == MYSQL_COM_STMT_PREPARE ? "MYSQL_COM_STMT_PREPARE" : \
((p) == MYSQL_COM_STMT_EXECUTE ? "MYSQL_COM_STMT_EXECUTE" : \
((p) == MYSQL_COM_UNDEFINED ? "MYSQL_COM_UNDEFINED" : \
"UNKNOWN MYSQL PACKET TYPE")))))))))))))))))))
"UNKNOWN MYSQL PACKET TYPE"))))))))))))))))))
#define STRDCBSTATE(s) ((s) == DCB_STATE_ALLOC ? "DCB_STATE_ALLOC" : \
((s) == DCB_STATE_POLLING ? "DCB_STATE_POLLING" : \
@ -263,6 +262,9 @@ typedef enum skygw_chk_t {
(SERVER_IS_RELAY_SERVER(s) ? "RUNNING RELAY" : \
(SERVER_IS_RUNNING(s) ? "RUNNING (only)" : "NO STATUS")))))))
#define BREFSRV(b) (b->bref_backend->backend_server)
#define STRHINTTYPE(t) (t == HINT_ROUTE_TO_MASTER ? "HINT_ROUTE_TO_MASTER" : \
((t) == HINT_ROUTE_TO_SLAVE ? "HINT_ROUTE_TO_SLAVE" : \
((t) == HINT_ROUTE_TO_NAMED_SERVER ? "HINT_ROUTE_TO_NAMED_SERVER" : \
@ -528,8 +530,8 @@ typedef enum skygw_chk_t {
#if defined(SS_DEBUG)
#if defined(FAKE_CODE)
bool conn_open[10240];
#endif
#endif /* FAKE_CODE */
#endif /* SKYGW_DEBUG_H */

View File

@ -1696,7 +1696,8 @@ return_succp:
}
static bool file_write_footer(
skygw_file_t* file)
skygw_file_t* file,
bool shutdown)
{
bool succp = false;
size_t wbytes1;
@ -1710,10 +1711,20 @@ static bool file_write_footer(
const char* header_buf4;
CHK_FILE(file);
header_buf1 = "MaxScale is shut down.\t";
if (shutdown)
{
header_buf1 = "MaxScale is shut down.\t";
}
else
{
header_buf1 = "Closed file due log rotation.\t";
}
tslen = get_timestamp_len();
header_buf3 = (char *)malloc(tslen);
if (header_buf3 == NULL) {
if (header_buf3 == NULL)
{
goto return_succp;
}
tslen = snprint_timestamp(header_buf3, tslen);
@ -1729,7 +1740,8 @@ static bool file_write_footer(
wbytes1=fwrite((void*)header_buf1, len1, 1, file->sf_file);
wbytes4=fwrite((void*)header_buf4, len4, 1, file->sf_file);
if (wbytes1 != 1 || wbytes3 != 1 || wbytes4 != 1) {
if (wbytes1 != 1 || wbytes3 != 1 || wbytes4 != 1)
{
fprintf(stderr,
"* Writing header %s %s to %s failed.\n",
header_buf1,
@ -1743,20 +1755,30 @@ static bool file_write_footer(
succp = true;
return_succp:
if (header_buf3 != NULL) {
if (header_buf3 != NULL)
{
free(header_buf3);
}
return succp;
}
bool skygw_file_write(
/**
* Write data to a file.
*
* @param file write target
* @param data pointer to contiguous memory buffer
* @param nbytes amount of bytes to be written
* @param flush ensure that write is permanent
*
* @return 0 if succeed, errno if failed.
*/
int skygw_file_write(
skygw_file_t* file,
void* data,
size_t nbytes,
bool flush)
{
bool succp = false;
int rc;
#if !defined(LAPTOP_TEST)
int err = 0;
size_t nwritten;
@ -1771,13 +1793,14 @@ bool skygw_file_write(
nwritten = fwrite(data, nbytes, 1, file->sf_file);
if (nwritten != 1) {
rc = errno;
perror("Logfile write.\n");
fprintf(stderr,
"* Writing %ld bytes, %s to %s failed.\n",
"* Writing %ld bytes,\n%s\n to %s failed.\n",
nbytes,
(char *)data,
file->sf_fname);
goto return_succp;
goto return_rc;
}
writecount += 1;
@ -1789,10 +1812,10 @@ bool skygw_file_write(
writecount = 0;
}
#endif
succp = true;
rc = 0;
CHK_FILE(file);
return_succp:
return succp;
return_rc:
return rc;
}
skygw_file_t* skygw_file_init(
@ -1876,43 +1899,44 @@ return_file:
return file;
}
void skygw_file_done(
skygw_file_t* file)
void skygw_file_close(
skygw_file_t* file,
bool shutdown)
{
int fd;
int err;
if (file != NULL) {
CHK_FILE(file);
if (!file_write_footer(file)) {
fprintf(stderr,
"* Writing header of log file %s failed.\n",
file->sf_fname);
perror("SkyGW file open\n");
}
fd = fileno(file->sf_file);
fsync(fd);
err = fclose(file->sf_file);
if (err != 0) {
fprintf(stderr,
"* Closing file %s failed : %s.\n",
file->sf_fname,
strerror(errno));
}
else
int fd;
int err;
if (file != NULL)
{
CHK_FILE(file);
if (!file_write_footer(file, shutdown))
{
fprintf(stderr,
"* Writing footer to log file %s failed.\n",
file->sf_fname);
perror("Write fike footer\n");
}
fd = fileno(file->sf_file);
fsync(fd);
if ((err = fclose(file->sf_file)) != 0)
{
fprintf(stderr,
"* Closing file %s failed due to %d, %s.\n",
file->sf_fname,
errno,
strerror(errno));
}
else
{
ss_dfprintf(stderr, "Closed %s\n", file->sf_fname);
free(file->sf_fname);
free(file);
}
}
}
}
/**
* Find the given needle - user-provided literal - and replace it with
* replacement string. Separate user-provided literals from matching table names

View File

@ -145,8 +145,8 @@ EXTERN_C_BLOCK_END
/** Skygw file routines */
skygw_file_t* skygw_file_init(char* fname, char* symlinkname);
void skygw_file_done(skygw_file_t* file);
bool skygw_file_write(
void skygw_file_close(skygw_file_t* file, bool shutdown);
int skygw_file_write(
skygw_file_t* file,
void* data,
size_t nbytes,