Moved common monitor structures and definitions to a separate header and added master down script to galeramon.
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
add_library(mysqlmon SHARED mysql_mon.c mon_exec.c)
|
||||
add_library(mysqlmon SHARED mysql_mon.c)
|
||||
target_link_libraries(mysqlmon log_manager utils)
|
||||
install(TARGETS mysqlmon DESTINATION modules)
|
||||
|
||||
add_library(galeramon SHARED galera_mon.c)
|
||||
add_library(galeramon SHARED galeramon.c)
|
||||
target_link_libraries(galeramon log_manager utils)
|
||||
install(TARGETS galeramon DESTINATION modules)
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <monitor.h>
|
||||
#include <mysqlmon.h>
|
||||
#include <galeramon.h>
|
||||
#include <thread.h>
|
||||
#include <mysql.h>
|
||||
#include <mysqld_error.h>
|
||||
@ -60,7 +60,7 @@ extern __thread log_info_t tls_log_info;
|
||||
|
||||
static void monitorMain(void *);
|
||||
|
||||
static char *version_str = "V1.4.0";
|
||||
static char *version_str = "V1.5.0";
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_MONITOR,
|
||||
@ -142,16 +142,16 @@ GetModuleObject()
|
||||
static void *
|
||||
startMonitor(void *arg,void* opt)
|
||||
{
|
||||
MYSQL_MONITOR *handle;
|
||||
GALERA_MONITOR *handle;
|
||||
CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
|
||||
if (arg != NULL)
|
||||
{
|
||||
handle = (MYSQL_MONITOR *)arg;
|
||||
handle = (GALERA_MONITOR *)arg;
|
||||
handle->shutdown = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((handle = (MYSQL_MONITOR *)malloc(sizeof(MYSQL_MONITOR))) == NULL)
|
||||
if ((handle = (GALERA_MONITOR *)malloc(sizeof(GALERA_MONITOR))) == NULL)
|
||||
return NULL;
|
||||
handle->databases = NULL;
|
||||
handle->shutdown = 0;
|
||||
@ -161,11 +161,12 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
|
||||
handle->interval = MONITOR_INTERVAL;
|
||||
handle->disableMasterFailback = 0;
|
||||
handle->availableWhenDonor = 0;
|
||||
handle->disableMasterRoleSetting = 0;
|
||||
handle->disableMasterRoleSetting = 0;
|
||||
handle->master = NULL;
|
||||
handle->connect_timeout=DEFAULT_CONNECT_TIMEOUT;
|
||||
handle->read_timeout=DEFAULT_READ_TIMEOUT;
|
||||
handle->write_timeout=DEFAULT_WRITE_TIMEOUT;
|
||||
handle->master_down_script = NULL;
|
||||
spinlock_init(&handle->lock);
|
||||
}
|
||||
|
||||
@ -178,6 +179,12 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
|
||||
handle->availableWhenDonor = config_truth_value(params->value);
|
||||
else if(!strcmp(params->name,"disable_master_role_setting"))
|
||||
handle->disableMasterRoleSetting = config_truth_value(params->value);
|
||||
else if(!strcmp(params->name,"master_down_script"))
|
||||
{
|
||||
if(handle->master_down_script)
|
||||
externcmd_free(handle->master_down_script);
|
||||
handle->master_down_script = externcmd_allocate(params->value);
|
||||
}
|
||||
params = params->next;
|
||||
}
|
||||
|
||||
@ -193,7 +200,7 @@ CONFIG_PARAMETER* params = (CONFIG_PARAMETER*)opt;
|
||||
static void
|
||||
stopMonitor(void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
|
||||
handle->shutdown = 1;
|
||||
thread_wait((void *)handle->tid);
|
||||
@ -209,7 +216,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
static void
|
||||
registerServer(void *arg, SERVER *server)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr, *db;
|
||||
|
||||
if ((db = (MONITOR_SERVERS *)malloc(sizeof(MONITOR_SERVERS))) == NULL)
|
||||
@ -239,7 +246,7 @@ MONITOR_SERVERS *ptr, *db;
|
||||
static void
|
||||
unregisterServer(void *arg, SERVER *server)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr, *lptr;
|
||||
|
||||
spinlock_acquire(&handle->lock);
|
||||
@ -278,7 +285,7 @@ MONITOR_SERVERS *ptr, *lptr;
|
||||
static void
|
||||
diagnostics(DCB *dcb, void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
MONITOR_SERVERS *db;
|
||||
char *sep;
|
||||
|
||||
@ -326,7 +333,7 @@ char *sep;
|
||||
static void
|
||||
defaultUsers(void *arg, char *uname, char *passwd)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
|
||||
if (handle->defaultUser)
|
||||
free(handle->defaultUser);
|
||||
@ -343,7 +350,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
* @param database The database to probe
|
||||
*/
|
||||
static void
|
||||
monitorDatabase(MYSQL_MONITOR *handle, MONITOR_SERVERS *database)
|
||||
monitorDatabase(GALERA_MONITOR *handle, MONITOR_SERVERS *database)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
MYSQL_RES *result;
|
||||
@ -497,7 +504,7 @@ char *server_string;
|
||||
static void
|
||||
monitorMain(void *arg)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
MONITOR_SERVERS *ptr;
|
||||
size_t nrounds = 0;
|
||||
MONITOR_SERVERS *candidate_master = NULL;
|
||||
@ -550,6 +557,15 @@ int log_no_members = 1;
|
||||
{
|
||||
monitorDatabase(handle, ptr);
|
||||
|
||||
if(ptr->mon_prev_status & SERVER_MASTER &&
|
||||
SERVER_IS_DOWN(ptr->server))
|
||||
{
|
||||
if(externcmd_execute(handle->master_down_script) == -1)
|
||||
{
|
||||
skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute master server failure script in galeramon.");
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
@ -584,6 +600,7 @@ int log_no_members = 1;
|
||||
/** Increase this server'e error count */
|
||||
dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING);
|
||||
ptr->mon_err_count += 1;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -673,7 +690,7 @@ int log_no_members = 1;
|
||||
static void
|
||||
setInterval(void *arg, size_t interval)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
memcpy(&handle->interval, &interval, sizeof(unsigned long));
|
||||
}
|
||||
|
||||
@ -759,7 +776,7 @@ static MONITOR_SERVERS *set_cluster_master(MONITOR_SERVERS *current_master, MONI
|
||||
static void
|
||||
disableMasterFailback(void *arg, int disable)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
memcpy(&handle->disableMasterFailback, &disable, sizeof(int));
|
||||
}
|
||||
|
||||
@ -775,7 +792,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
static void
|
||||
availableWhenDonor(void *arg, int disable)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
memcpy(&handle->availableWhenDonor, &disable, sizeof(int));
|
||||
}
|
||||
|
||||
@ -789,7 +806,7 @@ MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
static void
|
||||
setNetworkTimeout(void *arg, int type, int value)
|
||||
{
|
||||
MYSQL_MONITOR *handle = (MYSQL_MONITOR *)arg;
|
||||
GALERA_MONITOR *handle = (GALERA_MONITOR *)arg;
|
||||
int max_timeout = (int)(handle->interval/1000);
|
||||
int new_timeout = max_timeout -1;
|
||||
|
||||
65
server/modules/monitor/galeramon.h
Normal file
65
server/modules/monitor/galeramon.h
Normal file
@ -0,0 +1,65 @@
|
||||
#ifndef _GALERAMON_H
|
||||
#define _GALERAMON_H
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
|
||||
#include <monitor_common.h>
|
||||
#include <spinlock.h>
|
||||
#include <mon_exec.h>
|
||||
|
||||
/**
|
||||
* @file galeramon.h - The Galera cluster monitor
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 07/05/15 Markus Makela Initial Implementation of galeramon.h
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* The handle for an instance of a MySQL Monitor module
|
||||
*/
|
||||
typedef struct {
|
||||
SPINLOCK lock; /**< The monitor spinlock */
|
||||
pthread_t tid; /**< id of monitor thread */
|
||||
int shutdown; /**< Flag to shutdown the monitor thread */
|
||||
int status; /**< Monitor status */
|
||||
char *defaultUser; /**< Default username for monitoring */
|
||||
char *defaultPasswd; /**< Default password for monitoring */
|
||||
unsigned long interval; /**< Monitor sampling interval */
|
||||
unsigned long id; /**< Monitor ID */
|
||||
int detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
|
||||
int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */
|
||||
int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */
|
||||
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.
|
||||
*/
|
||||
EXTERNCMD* master_down_script;
|
||||
} GALERA_MONITOR;
|
||||
|
||||
#endif
|
||||
@ -1,150 +0,0 @@
|
||||
#include <mon_exec.h>
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
/**
|
||||
* Tokenize a string into arguments suitable for a execvp call.
|
||||
* @param args Argument string
|
||||
* @param argv Array of char pointers to be filled with tokenized arguments
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tokenize_arguments(char* args, char** argv)
|
||||
{
|
||||
int i = 0;
|
||||
bool quoted = false;
|
||||
bool read = false;
|
||||
bool escaped = false;
|
||||
char *ptr,*start;
|
||||
char qc;
|
||||
|
||||
start = args;
|
||||
ptr = start;
|
||||
|
||||
while(*ptr != '\0' && i < MAXSCALE_EXTCMD_ARG_MAX)
|
||||
{
|
||||
if(escaped)
|
||||
{
|
||||
escaped = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(*ptr == '\\')
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
else if(quoted && !escaped && *ptr == qc) /** End of quoted string */
|
||||
{
|
||||
*ptr = '\0';
|
||||
argv[i++] = strdup(start);
|
||||
read = false;
|
||||
quoted = false;
|
||||
}
|
||||
else if (!quoted)
|
||||
{
|
||||
if(isspace(*ptr))
|
||||
{
|
||||
*ptr = '\0';
|
||||
if(read) /** New token */
|
||||
{
|
||||
argv[i++] = strdup(start);
|
||||
read = false;
|
||||
}
|
||||
}
|
||||
else if( *ptr == '\"' || *ptr == '\'')
|
||||
{
|
||||
/** New quoted token, strip quotes */
|
||||
quoted = true;
|
||||
qc = *ptr;
|
||||
start = ptr + 1;
|
||||
}
|
||||
else if(!read)
|
||||
{
|
||||
start = ptr;
|
||||
read = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
if(read)
|
||||
argv[i++] = strdup(start);
|
||||
argv[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new external command.
|
||||
* The name and parameters are copied into the external command structure so
|
||||
* the original memory can be freed if needed.
|
||||
* @param command Command to execute with the parameters
|
||||
* @return Pointer to new external command struct or NULL if an error occurred
|
||||
*/
|
||||
EXTERNCMD* externcmd_allocate(char* argstr)
|
||||
{
|
||||
EXTERNCMD* cmd;
|
||||
|
||||
if(argstr == NULL)
|
||||
return NULL;
|
||||
|
||||
if((cmd = (EXTERNCMD*)malloc(sizeof(EXTERNCMD))) != NULL)
|
||||
{
|
||||
if(tokenize_arguments(argstr,cmd->parameters) == -1)
|
||||
{
|
||||
free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a previously allocated external command.
|
||||
* @param cmd Command to free
|
||||
*/
|
||||
void externcmd_free(EXTERNCMD* cmd)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0;cmd->parameters[i] != NULL;i++)
|
||||
{
|
||||
free(cmd->parameters[i]);
|
||||
}
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
*Execute a command in a separate process.
|
||||
*@param cmd Command to execute
|
||||
*@return 0 on success, -1 on error.
|
||||
*/
|
||||
int externcmd_execute(EXTERNCMD* cmd)
|
||||
{
|
||||
int rval = 0;
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
|
||||
if(pid < 0)
|
||||
{
|
||||
skygw_log_write(LOGFILE_ERROR,"Error: Failed to execute command '%s', fork failed: [%d] %s",
|
||||
cmd->parameters[0],errno,strerror(errno));
|
||||
rval = -1;
|
||||
}
|
||||
else if(pid == 0)
|
||||
{
|
||||
/** Child process, execute command */
|
||||
execvp(cmd->parameters[0],cmd->parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd->n_exec++;
|
||||
LOGIF(LD,skygw_log_write(LD,"[monitor_exec_cmd] Forked child process %d : %s.",pid,cmd));
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
#ifndef MON_EXEC_HG
|
||||
#define MON_EXEC_HG
|
||||
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#define MAXSCALE_EXTCMD_ARG_MAX 256
|
||||
|
||||
typedef struct extern_cmd_t{
|
||||
char* parameters[MAXSCALE_EXTCMD_ARG_MAX]; /*< Command arguments */
|
||||
int n_exec; /*< Number of times executed */
|
||||
}EXTERNCMD;
|
||||
|
||||
EXTERNCMD* externcmd_allocate(char* argstr);
|
||||
void externcmd_free(EXTERNCMD* cmd);
|
||||
int externcmd_execute(EXTERNCMD* cmd);
|
||||
#endif
|
||||
57
server/modules/monitor/monitor_common.h
Normal file
57
server/modules/monitor/monitor_common.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef _MONITOR_COMMON_HG
|
||||
#define _MONITOR_COMMON_HG
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
|
||||
#include <server.h>
|
||||
#include <mysql.h>
|
||||
|
||||
/**
|
||||
* @file monitor_common.h - The generic monitor structures all monitors use
|
||||
*
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 07/05/15 Markus Makela Initial Implementation of galeramon.h
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#define MONITOR_RUNNING 1
|
||||
#define MONITOR_STOPPING 2
|
||||
#define MONITOR_STOPPED 3
|
||||
|
||||
#define MONITOR_INTERVAL 10000 // in milliseconds
|
||||
#define MONITOR_DEFAULT_ID 1UL // unsigned long value
|
||||
#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server
|
||||
|
||||
|
||||
/**
|
||||
* The linked list of servers that are being monitored by the MySQL
|
||||
* Monitor module.
|
||||
*/
|
||||
typedef struct monitor_servers {
|
||||
SERVER *server; /**< The server being monitored */
|
||||
MYSQL *con; /**< The MySQL connection */
|
||||
int mon_err_count;
|
||||
unsigned int mon_prev_status;
|
||||
unsigned int pending_status; /**< Pending Status flag bitmap */
|
||||
struct monitor_servers
|
||||
*next; /**< The next server in the list */
|
||||
} MONITOR_SERVERS;
|
||||
|
||||
#endif
|
||||
@ -17,9 +17,9 @@
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
#include <server.h>
|
||||
|
||||
#include <spinlock.h>
|
||||
#include <mysql.h>
|
||||
#include <monitor_common.h>
|
||||
#include <mon_exec.h>
|
||||
/**
|
||||
* @file mysqlmon.h - The MySQL monitor functionality within the gateway
|
||||
@ -37,24 +37,10 @@
|
||||
* 07/11/14 Massimiliano Pinto Addition of NetworkTimeout: connect, read, write
|
||||
* 20/04/15 Guillaume Lefranc Addition of availableWhenDonor
|
||||
* 22/04/15 Martin Brampton Addition of disableMasterRoleSetting
|
||||
*
|
||||
* 07/05/15 Markus Makela Addition of command execution on Master server failure
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
/**
|
||||
* The linked list of servers that are being monitored by the MySQL
|
||||
* Monitor module.
|
||||
*/
|
||||
typedef struct monitor_servers {
|
||||
SERVER *server; /**< The server being monitored */
|
||||
MYSQL *con; /**< The MySQL connection */
|
||||
int mon_err_count;
|
||||
unsigned int mon_prev_status;
|
||||
unsigned int pending_status; /**< Pending Status flag bitmap */
|
||||
struct monitor_servers
|
||||
*next; /**< The next server in the list */
|
||||
} MONITOR_SERVERS;
|
||||
|
||||
/**
|
||||
* The handle for an instance of a MySQL Monitor module
|
||||
*/
|
||||
@ -84,12 +70,4 @@ typedef struct {
|
||||
EXTERNCMD* master_down_script;
|
||||
} MYSQL_MONITOR;
|
||||
|
||||
#define MONITOR_RUNNING 1
|
||||
#define MONITOR_STOPPING 2
|
||||
#define MONITOR_STOPPED 3
|
||||
|
||||
#define MONITOR_INTERVAL 10000 // in milliseconds
|
||||
#define MONITOR_DEFAULT_ID 1UL // unsigned long value
|
||||
#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user