/** * gw_do_connect_to_backend * * This routine creates socket and connects to a backend server. * Connect it non-blocking operation. If connect fails, socket is closed. * * @param host The host to connect to * @param port The host TCP/IP port * @param *fd where connected fd is copied * @return 0/1 on success and -1 on failure * If succesful, fd has file descriptor to socket which is connected to * backend server. In failure, fd == -1 and socket is closed. * */ int gw_do_connect_to_backend( char *host, int port, int* fd) { struct sockaddr_in serv_addr; int rv; int so = 0; memset(&serv_addr, 0, sizeof serv_addr); serv_addr.sin_family = AF_INET; 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 " "%s:%d failed. Socket creation failed due " "%d, %s.", host, port, eno, strerror(eno)))); rv = -1; goto return_rv; } /* prepare for connect */ setipaddress(&serv_addr.sin_addr, host); serv_addr.sin_port = htons(port); /* 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) { rv = 1; } else { int rc; int oldfd = so; 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; } } *fd = so; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_do_connect_to_backend] Connected to backend server " "%s:%d, fd %d.", pthread_self(), host, port, so))); #if defined(SS_DEBUG) conn_open[so] = true; #endif return_rv: return rv; }
/** * Monitor an individual server * * @param database The database to probe */ static void monitorDatabase(MONITOR_SERVERS *database, char *defaultUser, char *defaultPasswd, MYSQL_MONITOR *handle) { MYSQL_ROW row; MYSQL_RES *result; int num_fields; int isjoined = 0; char *uname = defaultUser, *passwd = 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 even probe server flagged as in maintenance */ if (SERVER_IN_MAINT(database->server)) return; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); int rc; 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) { 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)))); server_clear_status(database->server, SERVER_RUNNING); if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR) { server_set_status(database->server, SERVER_AUTH_ERROR); } database->server->node_id = -1; free(dpwd); return; } else { server_clear_status(database->server, SERVER_AUTH_ERROR); } free(dpwd); } /* If we get this far then we have a working connection */ server_set_status(database->server, 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); } /* Check if the the SQL node is able to contact one or more data nodes */ if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0 && (result = mysql_store_result(database->con)) != NULL) { num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { if (atoi(row[1]) > 0) isjoined = 1; } mysql_free_result(result); } /* Check the the SQL node id in the MySQL cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_cluster_node_id'") == 0 && (result = mysql_store_result(database->con)) != NULL) { long cluster_node_id = -1; num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { cluster_node_id = strtol(row[1], NULL, 10); if ((errno == ERANGE && (cluster_node_id == LONG_MAX || cluster_node_id == LONG_MIN)) || (errno != 0 && cluster_node_id == 0)) { cluster_node_id = -1; } database->server->node_id = cluster_node_id; } mysql_free_result(result); } if (isjoined) { server_set_status(database->server, SERVER_NDB); database->server->depth = 0; } else { server_clear_status(database->server, SERVER_NDB); database->server->depth = -1; } }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the * query is passed to the downstream component * (filter or router) in the filter chain. * * The function checks whether required logging trigger conditions are met and if so, * tries to extract a SQL query out of the query buffer, canonize the query, add * a timestamp to it and publish the resulting string on the exchange. * The message is tagged with an unique identifier and the clientReply will * use the same identifier for the reply from the backend to form a query-reply pair. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char *ptr, t_buf[128], *combined,*canon_q,*sesshost,*sessusr; bool success = false, src_ok = false,schema_ok = false,obj_ok = false; int length, i, j,dbcount = 0; char** sesstbls; unsigned int plen = 0; amqp_basic_properties_t *prop; /**The user is changing databases*/ if(*((char*)(queue->start + 4)) == 0x02){ if(my_session->db){ free(my_session->db); } plen = pktlen(queue->start); my_session->db = calloc(plen,sizeof(char)); memcpy(my_session->db,queue->start + 5,plen - 1); } if(modutil_is_SQL(queue)){ /**Parse the query*/ if (!query_is_parsed(queue)){ success = parse_query(queue); } if(!success){ skygw_log_write(LOGFILE_ERROR,"Error: Parsing query failed."); goto send_downstream; } if(!my_instance->log_all){ if(!skygw_is_real_query(queue)){ goto send_downstream; } } if(my_instance->trgtype == TRG_ALL){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_ALL"); schema_ok = true; src_ok = true; obj_ok = true; goto validate_triggers; } if(my_instance->trgtype & TRG_SOURCE && my_instance->src_trg){ if(session_isvalid(my_session->session)){ sessusr = session_getUser(my_session->session); sesshost = session_get_remote(my_session->session); /**Username was configured*/ if(my_instance->src_trg->usize > 0){ for(i = 0;i<my_instance->src_trg->usize;i++){ if(strcmp(my_instance->src_trg->user[i],sessusr) == 0) { skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: user: %s = %s",my_instance->src_trg->user[i],sessusr); src_ok = true; break; } } } /**If username was not matched, try to match hostname*/ if(!src_ok && my_instance->src_trg->hsize > 0){ for(i = 0;i<my_instance->src_trg->hsize;i++){ if(strcmp(my_instance->src_trg->host[i],sesshost) == 0) { skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: host: %s = %s",my_instance->src_trg->host[i],sesshost); src_ok = true; break; } } } } if(src_ok && !my_instance->strict_logging){ schema_ok = true; obj_ok = true; goto validate_triggers; } }else{ src_ok = true; } if(my_instance->trgtype & TRG_SCHEMA && my_instance->shm_trg){ int tbsz = 0,z; char** tblnames = skygw_get_table_names(queue,&tbsz,true); char* tmp; bool all_remotes = true; for(z = 0;z<tbsz;z++){ if((tmp = strchr(tblnames[z],'.')) != NULL){ char *lasts; tmp = strtok_r(tblnames[z],".",&lasts); for(i = 0; i<my_instance->shm_trg->size; i++){ if(strcmp(tmp,my_instance->shm_trg->objects[i]) == 0){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",tmp,my_instance->shm_trg->objects[i]); schema_ok = true; break; } } }else{ all_remotes = false; } free(tblnames[z]); } free(tblnames); if(!schema_ok && !all_remotes && my_session->db && strlen(my_session->db)>0){ for(i = 0; i<my_instance->shm_trg->size; i++){ if(strcmp(my_session->db,my_instance->shm_trg->objects[i]) == 0){ skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",my_session->db,my_instance->shm_trg->objects[i]); schema_ok = true; break; } } } if(schema_ok && !my_instance->strict_logging){ src_ok = true; obj_ok = true; goto validate_triggers; } }else{ schema_ok = true; } if(my_instance->trgtype & TRG_OBJECT && my_instance->obj_trg){ sesstbls = skygw_get_table_names(queue,&dbcount,false); for(j = 0; j<dbcount; j++){ char* tbnm = NULL; if((strchr(sesstbls[j],'.')) != NULL){ char *lasts; tbnm = strtok_r(sesstbls[j],".",&lasts); tbnm = strtok_r(NULL,".",&lasts); }else{ tbnm = sesstbls[j]; } for(i = 0; i<my_instance->obj_trg->size; i++){ if(!strcmp(tbnm,my_instance->obj_trg->objects[i])){ obj_ok = true; skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_OBJECT: %s = %s",my_instance->obj_trg->objects[i],sesstbls[j]); break; } } } if(dbcount > 0){ for(j = 0; j<dbcount; j++){ free(sesstbls[j]); } free(sesstbls); dbcount = 0; } if(obj_ok && !my_instance->strict_logging){ src_ok = true; schema_ok = true; goto validate_triggers; } }else{ obj_ok = true; } validate_triggers: if(src_ok&&schema_ok&&obj_ok){ /** * Something matched the trigger, log the query */ skygw_log_write_flush(LOGFILE_TRACE,"Routing message to: %s:%d %s as %s/%s, exchange: %s<%s> key:%s queue:%s", my_instance->hostname,my_instance->port, my_instance->vhost,my_instance->username, my_instance->password,my_instance->exchange, my_instance->exchange_type,my_instance->key, my_instance->queue); if(my_session->uid == NULL){ my_session->uid = calloc(33,sizeof(char)); if(!my_session->uid){ skygw_log_write(LOGFILE_ERROR,"Error : Out of memory."); }else{ genkey(my_session->uid,32); } } if (queue->next != NULL) { queue = gwbuf_make_contiguous(queue); } if(modutil_extract_SQL(queue, &ptr, &length)){ my_session->was_query = true; if((prop = malloc(sizeof(amqp_basic_properties_t)))){ prop->_flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop->content_type = amqp_cstring_bytes("text/plain"); prop->delivery_mode = AMQP_DELIVERY_PERSISTENT; prop->correlation_id = amqp_cstring_bytes(my_session->uid); prop->message_id = amqp_cstring_bytes("query"); } if(success){ /**Try to convert to a canonical form and use the plain query if unsuccessful*/ if((canon_q = skygw_get_canonical(queue)) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error: Cannot form canonical query."); } } memset(t_buf,0,128); sprintf(t_buf, "%lu|",(unsigned long)time(NULL)); int qlen = strnlen(canon_q,length) + strnlen(t_buf,128); if((combined = malloc((qlen+1)*sizeof(char))) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error: Out of memory"); } strcpy(combined,t_buf); strncat(combined,canon_q,length); pushMessage(my_instance,prop,combined); free(canon_q); } } /** Pass the query downstream */ } send_downstream: return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }
/** * Associate a new session with this instance of the router. * * @param instance The router instance data * @param session The session itself * @return Session specific data for this session */ static void * newSession(ROUTER *instance, SESSION *session) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES *client_rses; BACKEND *candidate = NULL; int i; BACKEND *master_host = NULL; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [newSession] new router session with session " "%p, and inst %p.", pthread_self(), session, inst))); client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); if (client_rses == NULL) { return NULL; } #if defined(SS_DEBUG) client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; #endif /** * Find the Master host from available servers */ master_host = get_root_master(inst->servers); /** * Find a backend server to connect to. This is the extent of the * load balancing algorithm we need to implement for this simple * connection router. */ /* * Loop over all the servers and find any that have fewer connections * than the candidate server. * * If a server has less connections than the current candidate we mark this * as the new candidate to connect to. * * If a server has the same number of connections currently as the candidate * and has had less connections over time than the candidate it will also * become the new candidate. This has the effect of spreading the * connections over different servers during periods of very low load. */ for (i = 0; inst->servers[i]; i++) { if(inst->servers[i]) { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [newSession] Examine server in port %d with " "%d connections. Status is %s, " "inst->bitvalue is %d", pthread_self(), inst->servers[i]->server->port, inst->servers[i]->current_connection_count, STRSRVSTATUS(inst->servers[i]->server), inst->bitmask))); } if (SERVER_IN_MAINT(inst->servers[i]->server)) continue; if (inst->servers[i]->weight == 0) continue; /* Check server status bits against bitvalue from router_options */ if (inst->servers[i] && SERVER_IS_RUNNING(inst->servers[i]->server) && (inst->servers[i]->server->status & inst->bitmask & inst->bitvalue)) { if (master_host) { if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_SLAVE)) { /* skip root Master here, as it could also be slave of an external server * that is not in the configuration. * Intermediate masters (Relay Servers) are also slave and will be selected * as Slave(s) */ continue; } if (inst->servers[i] == master_host && (inst->bitvalue & SERVER_MASTER)) { /* If option is "master" return only the root Master as there * could be intermediate masters (Relay Servers) * and they must not be selected. */ candidate = master_host; break; } } else { /* master_host is NULL, no master server. * If requested router_option is 'master' * candidate wll be NULL. */ if (inst->bitvalue & SERVER_MASTER) { candidate = NULL; break; } } /* If no candidate set, set first running server as our initial candidate server */ if (candidate == NULL) { candidate = inst->servers[i]; } else if ((inst->servers[i]->current_connection_count * 1000) / inst->servers[i]->weight < (candidate->current_connection_count * 1000) / candidate->weight) { /* This running server has fewer connections, set it as a new candidate */ candidate = inst->servers[i]; } else if ((inst->servers[i]->current_connection_count * 1000) / inst->servers[i]->weight == (candidate->current_connection_count * 1000) / candidate->weight && inst->servers[i]->server->stats.n_connections < candidate->server->stats.n_connections) { /* This running server has the same number of connections currently as the candidate but has had fewer connections over time than candidate, set this server to candidate*/ candidate = inst->servers[i]; } } } /* There is no candidate server here! * With router_option=slave a master_host could be set, so route traffic there. * Otherwise, just clean up and return NULL */ if (!candidate) { if (master_host) { candidate = master_host; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create new routing session. " "Couldn't find eligible candidate server. Freeing " "allocated resources."))); free(client_rses); return NULL; } } client_rses->rses_capabilities = RCAP_TYPE_PACKET_INPUT; /* * We now have the server with the least connections. * Bump the connection count for this server */ atomic_add(&candidate->current_connection_count, 1); client_rses->backend = candidate; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [newSession] Selected server in port %d. " "Connections : %d\n", pthread_self(), candidate->server->port, candidate->current_connection_count))); /* * Open a backend connection, putting the DCB for this * connection in the client_rses->backend_dcb */ client_rses->backend_dcb = dcb_connect(candidate->server, session, candidate->server->protocol); if (client_rses->backend_dcb == NULL) { atomic_add(&candidate->current_connection_count, -1); free(client_rses); return NULL; } dcb_add_callback( client_rses->backend_dcb, DCB_REASON_NOT_RESPONDING, &handle_state_switch, client_rses); inst->stats.n_sessions++; /** * Add this session to the list of active sessions. */ spinlock_acquire(&inst->lock); client_rses->next = inst->connections; inst->connections = client_rses; spinlock_release(&inst->lock); CHK_CLIENT_RSES(client_rses); skygw_log_write( LOGFILE_TRACE, "Readconnroute: New session for server %s. " "Connections : %d", candidate->server->unique_name, candidate->current_connection_count); return (void *)client_rses; }
/** * 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; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; int master_stickiness = handle->disableMasterFailback; int is_cluster=0; int log_no_members = 1; 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; /* reset cluster members counter */ is_cluster=0; ptr = handle->databases; while (ptr) { 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; /* clear M/S status */ server_clear_status(ptr->server, SERVER_SLAVE); server_clear_status(ptr->server, SERVER_MASTER); /* clear master sticky status */ server_clear_status(ptr->server, SERVER_MASTER_STICKINESS); } /* Log server status change */ if (mon_status_changed(ptr)) { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "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; } /* * 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; 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); /* 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); } } is_cluster++; ptr = ptr->next; } if (is_cluster == 0 && log_no_members) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error: there are no cluster members"))); log_no_members = 0; } else { if (is_cluster > 0 && log_no_members == 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Info: found cluster members"))); log_no_members = 1; } } } }
/** * The entry point for the monitoring module thread * * @param arg The handle of the monitor */ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*)arg; MM_MONITOR *handle; MONITOR_SERVERS *ptr; int detect_stale_master; MONITOR_SERVERS *root_master; size_t nrounds = 0; spinlock_acquire(&mon->lock); handle = (MM_MONITOR *)mon->handle; spinlock_release(&mon->lock); detect_stale_master = handle->detectStaleMaster; 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)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; continue; } nrounds += 1; /* start from the first server in the list */ ptr = mon->databases; while (ptr) { /* copy server status into monitor pending_status */ ptr->pending_status = ptr->server->status; /* monitor current node */ monitorDatabase(mon, ptr); if (mon_status_changed(ptr)) { dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); } if (mon_status_changed(ptr) || mon_print_fail_status(ptr)) { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "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(mon); /* Update server status from monitor pending status on that server*/ ptr = mon->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; } ptr = mon->databases; monitor_event_t evtype; while(ptr) { if(mon_status_changed(ptr)) { evtype = mon_get_event_type(ptr); if(isMySQLEvent(evtype)) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } } } ptr = ptr->next; } } }
/** * Create an instance of the filter for a particular service * within MaxScale. * * @param options The options for this filter * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { LAG_INSTANCE *my_instance; int i,cflags = 0; if ((my_instance = calloc(1, sizeof(LAG_INSTANCE))) != NULL) { my_instance->count = 0; my_instance->time = 0; my_instance->stats.n_add_count = 0; my_instance->stats.n_add_time = 0; my_instance->stats.n_modified = 0; my_instance->match = NULL; my_instance->nomatch = NULL; for (i = 0; params && params[i]; i++) { if (!strcmp(params[i]->name, "count")) my_instance->count = atoi(params[i]->value); else if (!strcmp(params[i]->name, "time")) my_instance->time = atoi(params[i]->value); else if (!strcmp(params[i]->name, "match")) my_instance->match = strdup(params[i]->value); else if (!strcmp(params[i]->name, "ignore")) my_instance->nomatch = strdup(params[i]->value); else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "lagfilter: Unexpected parameter '%s'.\n", params[i]->name))); } } if (options) { for (i = 0; options[i]; i++) { if (!strcasecmp(options[i], "ignorecase")) { cflags |= REG_ICASE; } else if (!strcasecmp(options[i], "case")) { cflags &= ~REG_ICASE; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "lagfilter: unsupported option '%s'.", options[i]))); } } } if(my_instance->match) { if(regcomp(&my_instance->re,my_instance->match,cflags)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "lagfilter: Failed to compile regex '%s'.", my_instance->match))); } } if(my_instance->nomatch) { if(regcomp(&my_instance->nore,my_instance->nomatch,cflags)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "lagfilter: Failed to compile regex '%s'.", my_instance->nomatch))); } } } return (FILTER *)my_instance; }
/** * Add a DCB to the set of descriptors within the polling * environment. * * @param dcb The descriptor to add to the poll * @return -1 on error or 0 on success */ int poll_add_dcb(DCB *dcb) { int rc = -1; dcb_state_t old_state = DCB_STATE_UNDEFINED; dcb_state_t new_state; struct epoll_event ev; CHK_DCB(dcb); #ifdef EPOLLRDHUP ev.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLHUP | EPOLLET; #else ev.events = EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLET; #endif ev.data.ptr = dcb; /*< * Choose new state according to the role of dcb. */ if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER) { new_state = DCB_STATE_POLLING; } else { ss_dassert(dcb->dcb_role == DCB_ROLE_SERVICE_LISTENER); new_state = DCB_STATE_LISTENING; } /*< * If dcb is in unexpected state, state change fails indicating that dcb * is not polling anymore. */ if (dcb_set_state(dcb, new_state, &old_state)) { rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dcb->fd, &ev); if (rc != 0) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Adding dcb %p in state %s " "to poll set failed. epoll_ctl failed due " "%d, %s.", dcb, STRDCBSTATE(dcb->state), eno, strerror(eno)))); } else { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [poll_add_dcb] Added dcb %p in state %s to " "poll set.", pthread_self(), dcb, STRDCBSTATE(dcb->state)))); } ss_info_dassert(rc == 0, "Unable to add poll"); /*< trap in debug */ } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to set new state for dcb %p " "in state %s. Adding to poll set failed.", dcb, STRDCBSTATE(dcb->state)))); } return rc; }
/** * Process a configuration context and turn it into the set of object * we need. * * @param context The configuration data * @return A zero result indicates a fatal error */ static int process_config_context(CONFIG_CONTEXT *context) { CONFIG_CONTEXT *obj; int error_count = 0; /** * Process the data and create the services and servers defined * in the data. */ obj = context; while (obj) { char *type = config_get_value(obj->parameters, "type"); if (type == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Configuration object '%s' has no type.", obj->object))); error_count++; } else if (!strcmp(type, "service")) { char *router = config_get_value(obj->parameters, "router"); if (router) { obj->element = service_alloc(obj->object, router); char *user = config_get_value(obj->parameters, "user"); char *auth = config_get_value(obj->parameters, "passwd"); char *enable_root_user = config_get_value(obj->parameters, "enable_root_user"); if (enable_root_user) serviceEnableRootUser(obj->element, atoi(enable_root_user)); if (!auth) auth = config_get_value(obj->parameters, "auth"); if (obj->element && user && auth) { serviceSetUser(obj->element, user, auth); } else if (user && auth == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Service '%s' has a " "user defined but no " "corresponding password.", obj->object))); } } else { obj->element = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : No router defined for service " "'%s'\n", obj->object))); error_count++; } } else if (!strcmp(type, "server")) { char *address; char *port; char *protocol; char *monuser; char *monpw; address = config_get_value(obj->parameters, "address"); port = config_get_value(obj->parameters, "port"); protocol = config_get_value(obj->parameters, "protocol"); monuser = config_get_value(obj->parameters, "monitoruser"); monpw = config_get_value(obj->parameters, "monitorpw"); if (address && port && protocol) { obj->element = server_alloc(address, protocol, atoi(port)); } else { obj->element = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Server '%s' is missing a " "required configuration parameter. A " "server must " "have address, port and protocol " "defined.", obj->object))); error_count++; } if (obj->element && monuser && monpw) serverAddMonUser(obj->element, monuser, monpw); else if (monuser && monpw == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Server '%s' has a monitoruser" "defined but no corresponding password.", obj->object))); } } obj = obj->next; } /* * Now we have the services we can add the servers to the services * add the protocols to the services */ obj = context; while (obj) { char *type = config_get_value(obj->parameters, "type"); if (type == NULL) ; else if (!strcmp(type, "service")) { char *servers; char *roptions; servers = config_get_value(obj->parameters, "servers"); roptions = config_get_value(obj->parameters, "router_options"); if (servers && obj->element) { char *s = strtok(servers, ","); while (s) { CONFIG_CONTEXT *obj1 = context; while (obj1) { if (strcmp(s, obj1->object) == 0 && obj->element && obj1->element) { serviceAddBackend( obj->element, obj1->element); } obj1 = obj1->next; } s = strtok(NULL, ","); } } else if (servers == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : The service '%s' is missing a " "definition of the servers that provide " "the service.", obj->object))); } if (roptions && obj->element) { char *s = strtok(roptions, ","); while (s) { serviceAddRouterOption(obj->element, s); s = strtok(NULL, ","); } } } else if (!strcmp(type, "listener")) { char *service; char *address; char *port; char *protocol; service = config_get_value(obj->parameters, "service"); port = config_get_value(obj->parameters, "port"); address = config_get_value(obj->parameters, "address"); protocol = config_get_value(obj->parameters, "protocol"); if (service && port && protocol) { CONFIG_CONTEXT *ptr = context; while (ptr && strcmp(ptr->object, service) != 0) ptr = ptr->next; if (ptr && ptr->element) { serviceAddProtocol(ptr->element, protocol, address, atoi(port)); } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Listener '%s', " "service '%s' not found. " "Listener will not execute.", obj->object, service))); error_count++; } } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Listener '%s' is misisng a " "required " "parameter. A Listener must have a " "service, port and protocol defined.", obj->object))); error_count++; } } else if (!strcmp(type, "monitor")) { char *module; char *servers; char *user; char *passwd; module = config_get_value(obj->parameters, "module"); servers = config_get_value(obj->parameters, "servers"); user = config_get_value(obj->parameters, "user"); passwd = config_get_value(obj->parameters, "passwd"); if (module) { obj->element = monitor_alloc(obj->object, module); if (servers && obj->element) { char *s = strtok(servers, ","); while (s) { CONFIG_CONTEXT *obj1 = context; while (obj1) { if (strcmp(s, obj1->object) == 0 && obj->element && obj1->element) { monitorAddServer( obj->element, obj1->element); } obj1 = obj1->next; } s = strtok(NULL, ","); } } if (obj->element && user && passwd) { monitorAddUser(obj->element, user, passwd); } else if (obj->element && user) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error: " "Monitor '%s' defines a " "username with no password.", obj->object))); error_count++; } } else { obj->element = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Monitor '%s' is missing a " "require module parameter.", obj->object))); error_count++; } } else if (strcmp(type, "server") != 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Configuration object '%s' has an " "invalid type specified.", obj->object))); error_count++; } obj = obj->next; } if (error_count) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : %d errors where encountered processing the " "configuration file '%s'.", error_count, config_file))); return 0; } return 1; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create listener for service %s.", service->name))); goto retblock; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; 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))); { /* Try loading authentication data from file cache */ char *ptr, path[4097]; strcpy(path, "/usr/local/mariadb-maxscale"); if ((ptr = getenv("MAXSCALE_HOME")) != NULL) { strncpy(path, ptr, 4096); } strncat(path, "/", 4096); strncat(path, service->name, 4096); strncat(path, "/.cache/dbusers", 4096); loaded = dbusers_load(service->users, path); if (loaded != -1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Using cached credential information."))); } } if (loaded == -1) { hashtable_free(service->users->data); free(service->users); dcb_free(port->listener); port->listener = NULL; goto retblock; } } else { /* Save authentication data to file cache */ char *ptr, path[4097]; int mkdir_rval = 0; strcpy(path, "/usr/local/mariadb-maxscale"); if ((ptr = getenv("MAXSCALE_HOME")) != NULL) { strncpy(path, ptr, 4096); } strncat(path, "/", 4096); strncat(path, service->name, 4096); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); } if(mkdir_rval) { skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", path, errno, strerror(errno)); mkdir_rval = 0; } strncat(path, "/.cache", 4096); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); } if(mkdir_rval) { skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", path, errno, strerror(errno)); mkdir_rval = 0; } strncat(path, "/dbusers", 4096); dbusers_save(service->users, path); } if (loaded == 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Service %s: failed to load any user " "information. Authentication will " "probably fail as a result.", service->name))); } /* 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))); } } else { if (service->users == NULL) { /* Generic users table */ service->users = users_alloc(); } } if ((funcs=(GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); goto retblock; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create session to service %s.", service->name))); if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_close(port->listener); port->listener = NULL; goto retblock; } } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_close(port->listener); port->listener = NULL; } retblock: return listeners; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { return 0; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded = load_mysql_users(service); LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "Loaded %d MySQL Users.", loaded))); } if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); return 0; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { dcb_close(port->listener); } } else { dcb_close(port->listener); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); } return listeners; }
/** * The clientReply entry point. This is passed the response buffer * to which the filter should be applied. Once processed the * query is passed to the upstream component * (filter or router) in the filter chain. * * The function tries to extract a SQL query response out of the response buffer, * adds a timestamp to it and publishes the resulting string on the exchange. * The message is tagged with the same identifier that the query was. * * @param instance The filter instance data * @param session The filter session * @param reply The response data */ static int clientReply(FILTER* instance, void *session, GWBUF *reply) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char t_buf[128],*combined; unsigned int err_code = AMQP_STATUS_OK, pkt_len = pktlen(reply->sbuf->data), offset = 0; amqp_basic_properties_t prop; spinlock_acquire(my_instance->rconn_lock); if(my_instance->conn_stat != AMQP_STATUS_OK){ if(difftime(time(NULL),my_instance->last_rconn) > my_instance->rconn_intv){ my_instance->last_rconn = time(NULL); if(init_conn(my_instance,my_session)){ my_instance->rconn_intv = 1.0; my_instance->conn_stat = AMQP_STATUS_OK; }else{ my_instance->rconn_intv += 5.0; skygw_log_write(LOGFILE_ERROR, "Error : Failed to reconnect to the MQRabbit server "); } err_code = my_instance->conn_stat; } } spinlock_release(my_instance->rconn_lock); if (err_code == AMQP_STATUS_OK && my_session->was_query){ int packet_ok = 0, was_last = 0; my_session->was_query = 0; if(pkt_len > 0){ prop._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop.content_type = amqp_cstring_bytes("text/plain"); prop.delivery_mode = AMQP_DELIVERY_PERSISTENT; prop.correlation_id = amqp_cstring_bytes(my_session->uid); prop.message_id = amqp_cstring_bytes("reply"); if(!(combined = calloc(GWBUF_LENGTH(reply) + 256,sizeof(char)))){ skygw_log_write_flush(LOGFILE_ERROR, "Error : Out of memory"); } memset(t_buf,0,128); sprintf(t_buf,"%lu|",(unsigned long)time(NULL)); memcpy(combined + offset,t_buf,strnlen(t_buf,40)); offset += strnlen(t_buf,40); if(*(reply->sbuf->data + 4) == 0x00){ /**OK packet*/ unsigned int aff_rows = 0, l_id = 0, s_flg = 0, wrn = 0; unsigned char *ptr = (unsigned char*)(reply->sbuf->data + 5); pkt_len = pktlen(reply->sbuf->data); aff_rows = consume_leitoi(&ptr); l_id = consume_leitoi(&ptr); s_flg |= *ptr++; s_flg |= (*ptr++ << 8); wrn |= *ptr++; wrn |= (*ptr++ << 8); sprintf(combined + offset,"OK - affected_rows: %d " " last_insert_id: %d " " status_flags: %#0x " " warnings: %d ", aff_rows,l_id,s_flg,wrn); offset += strnlen(combined,GWBUF_LENGTH(reply) + 256) - offset; if(pkt_len > 7){ int plen = consume_leitoi(&ptr); if(plen > 0){ sprintf(combined + offset," message: %.*s\n",plen,ptr); } } packet_ok = 1; was_last = 1; }else if(*(reply->sbuf->data + 4) == 0xff){ /**ERR packet*/ sprintf(combined + offset,"ERROR - message: %.*s", (int)(reply->end - ((void*)(reply->sbuf->data + 13))), (char *)reply->sbuf->data + 13); packet_ok = 1; was_last = 1; }else if(*(reply->sbuf->data + 4) == 0xfb){ /**LOCAL_INFILE request packet*/ unsigned char *rset = (unsigned char*)reply->sbuf->data; strcpy(combined + offset,"LOCAL_INFILE: "); strncat(combined + offset,(const char*)rset+5,pktlen(rset)); packet_ok = 1; was_last = 1; }else{ /**Result set*/ unsigned char *rset = (unsigned char*)(reply->sbuf->data + 4); char *tmp; unsigned int col_cnt = consume_leitoi(&rset); tmp = calloc(256,sizeof(char)); sprintf(tmp,"Columns: %d",col_cnt); memcpy(combined + offset,tmp,strnlen(tmp,256)); offset += strnlen(tmp,256); memcpy(combined + offset,"\n",1); offset++; free(tmp); packet_ok = 1; was_last = 1; } if(packet_ok){ if((err_code = amqp_basic_publish(my_session->conn,my_session->channel, amqp_cstring_bytes(my_instance->exchange), amqp_cstring_bytes(my_instance->key), 0,0,&prop,amqp_cstring_bytes(combined)) ) != AMQP_STATUS_OK){ spinlock_acquire(my_instance->rconn_lock); my_instance->conn_stat = err_code; spinlock_release(my_instance->rconn_lock); skygw_log_write_flush(LOGFILE_ERROR, "Error : Failed to publish message to MQRabbit server: " "%s",amqp_error_string2(err_code)); }else if(was_last){ /**Successful reply received and sent, releasing uid*/ free(my_session->uid); my_session->uid = NULL; } } free(combined); } } return my_session->up.clientReply(my_session->up.instance, my_session->up.session, reply); }
/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once processed the * query is passed to the downstream component * (filter or router) in the filter chain. * * The function tries to extract a SQL query out of the query buffer, * adds a timestamp to it and publishes the resulting string on the exchange. * The message is tagged with an unique identifier and the clientReply will * use the same identifier for the reply from the backend. * * @param instance The filter instance data * @param session The filter session * @param queue The query data */ static int routeQuery(FILTER *instance, void *session, GWBUF *queue) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char *ptr, t_buf[128], *combined; int length, err_code = AMQP_STATUS_OK; amqp_basic_properties_t prop; spinlock_acquire(my_instance->rconn_lock); if(my_instance->conn_stat != AMQP_STATUS_OK){ if(difftime(time(NULL),my_instance->last_rconn) > my_instance->rconn_intv){ my_instance->last_rconn = time(NULL); if(init_conn(my_instance,my_session)){ my_instance->rconn_intv = 1.0; my_instance->conn_stat = AMQP_STATUS_OK; }else{ my_instance->rconn_intv += 5.0; skygw_log_write(LOGFILE_ERROR, "Error : Failed to reconnect to the MQRabbit server "); } err_code = my_instance->conn_stat; } } spinlock_release(my_instance->rconn_lock); if(modutil_is_SQL(queue)){ if(my_session->uid == NULL){ my_session->uid = calloc(33,sizeof(char)); if(!my_session->uid){ skygw_log_write(LOGFILE_ERROR,"Error : Out of memory."); }else{ genkey(my_session->uid,32); } } } if (err_code == AMQP_STATUS_OK){ if(modutil_extract_SQL(queue, &ptr, &length)){ my_session->was_query = 1; prop._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop.content_type = amqp_cstring_bytes("text/plain"); prop.delivery_mode = AMQP_DELIVERY_PERSISTENT; prop.correlation_id = amqp_cstring_bytes(my_session->uid); prop.message_id = amqp_cstring_bytes("query"); memset(t_buf,0,128); sprintf(t_buf, "%lu|",(unsigned long)time(NULL)); int qlen = length + strnlen(t_buf,128); if((combined = malloc((qlen+1)*sizeof(char))) == NULL){ skygw_log_write_flush(LOGFILE_ERROR, "Error : Out of memory"); } strcpy(combined,t_buf); strncat(combined,ptr,length); if((err_code = amqp_basic_publish(my_session->conn,my_session->channel, amqp_cstring_bytes(my_instance->exchange), amqp_cstring_bytes(my_instance->key), 0,0,&prop,amqp_cstring_bytes(combined)) ) != AMQP_STATUS_OK){ spinlock_acquire(my_instance->rconn_lock); my_instance->conn_stat = err_code; spinlock_release(my_instance->rconn_lock); skygw_log_write_flush(LOGFILE_ERROR, "Error : Failed to publish message to MQRabbit server: " "%s",amqp_error_string2(err_code)); } } } /** Pass the query downstream */ return my_session->down.routeQuery(my_session->down.instance, my_session->down.session, queue); }
/** * Load the user/passwd form mysql.user table into the service users' hashtable * environment. * * @param service The current service * @param users The users table into which to load the users * @return -1 on any error or the number of users inserted (0 means no users at all) */ static int getUsers(SERVICE *service, struct users *users) { MYSQL *con = NULL; MYSQL_ROW row; MYSQL_RES *result = NULL; int num_fields = 0; char *service_user = NULL; char *service_passwd = NULL; char *dpwd; int total_users = 0; SERVER *server; char *users_query; unsigned char hash[SHA_DIGEST_LENGTH]=""; char *users_data = NULL; int nusers = 0; int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN; struct sockaddr_in serv_addr; MYSQL_USER_HOST key; /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ if(service->enable_root) { users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; } else { users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; } serviceGetUser(service, &service_user, &service_passwd); /** multi-thread environment requires that thread init succeeds. */ if (mysql_thread_init()) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : mysql_thread_init failed."))); return -1; } con = mysql_init(NULL); if (con == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : mysql_init: %s", mysql_error(con)))); return -1; } if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed to set external connection. " "It is needed for backend server connections. " "Exiting."))); return -1; } /* * Attempt to connect to each database in the service in turn until * we find one that we can connect to or until we run out of databases * to try */ 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; } 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))); mysql_close(con); return -1; } if (mysql_query(con, MYSQL_USERS_COUNT)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading users for service %s encountered " "error: %s.", service->name, mysql_error(con)))); mysql_close(con); return -1; } result = mysql_store_result(con); if (result == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading users for service %s encountered " "error: %s.", service->name, mysql_error(con)))); mysql_close(con); return -1; } num_fields = mysql_num_fields(result); row = mysql_fetch_row(result); nusers = atoi(row[0]); mysql_free_result(result); if (!nusers) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Counting users for service %s returned 0", service->name))); mysql_close(con); return -1; } if (mysql_query(con, users_query)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading users for service %s encountered " "error: %s.", service->name, mysql_error(con)))); mysql_close(con); return -1; } result = mysql_store_result(con); if (result == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading users for service %s encountered " "error: %s.", service->name, mysql_error(con)))); mysql_close(con); return -1; } num_fields = mysql_num_fields(result); users_data = (char *)malloc(nusers * (users_data_row_len * sizeof(char)) + 1); if(users_data == NULL) return -1; while ((row = mysql_fetch_row(result))) { /** * Four fields should be returned. * user and passwd+1 (escaping the first byte that is '*') are * added to hashtable. */ char ret_ip[INET_ADDRSTRLEN + 1]=""; const char *rc; /* prepare the user@host data struct */ memset(&serv_addr, 0, sizeof(serv_addr)); memset(&key, 0, sizeof(key)); /* if host == '%', 0 is passed */ if (setipaddress(&serv_addr.sin_addr, strcmp(row[1], "%") ? row[1] : "0.0.0.0")) { key.user = strdup(row[0]); if(key.user == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "%lu [getUsers()] strdup() failed for user %s", pthread_self(), row[0]))); continue; } memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); rc = inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); /* add user@host as key and passwd as value in the MySQL users hash table */ if (mysql_users_add(users, &key, strlen(row[2]) ? row[2]+1 : row[2])) { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [mysql_users_add()] Added user %s@%s(%s)", pthread_self(), row[0], row[1], rc == NULL ? "NULL" : ret_ip))); /* Append data in the memory area for SHA1 digest */ strncat(users_data, row[3], users_data_row_len); total_users++; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "%lu [mysql_users_add()] Failed adding user %s@%s(%s)", pthread_self(), row[0], row[1], rc == NULL ? "NULL" : ret_ip))); continue; } } else { /* setipaddress() failed, skip user add and log this*/ LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "%lu [getUsers()] setipaddress failed: user %s@%s not added", pthread_self(), row[0], row[1]))); } } /* compute SHA1 digest for users' data */ SHA1((const unsigned char *) users_data, strlen(users_data), hash); memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); free(users_data); mysql_free_result(result); mysql_close(con); mysql_thread_end(); return total_users; }
/** * This routine reads data from a binary file named ".secrets" and extracts the AES encryption key * and the AES Init Vector. * If the path parameter is not null the custom path is interpreted as a folder * containing the .secrets file. Otherwise the default location is used. * @return The keys structure or NULL on error */ static MAXKEYS * secrets_readKeys(char* path) { char secret_file[PATH_MAX+1]; char *home; MAXKEYS *keys; struct stat secret_stats; int fd; int len; static int reported = 0; if(path != NULL) snprintf(secret_file, PATH_MAX, "%s/.secrets", path); else snprintf(secret_file, PATH_MAX, "%s/.secrets", get_datadir()); /* Try to access secrets file */ if (access(secret_file, R_OK) == -1) { int eno = errno; errno = 0; if (eno == ENOENT) { if (!reported) { LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "Encrypted password file %s can't be accessed " "(%s). Password encryption is not used.", secret_file, strerror(eno)))); reported = 1; } } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : access for secrets file " "[%s] failed. Error %d, %s.", secret_file, eno, strerror(eno)))); } return NULL; } /* open secret file */ if ((fd = open(secret_file, O_RDONLY)) < 0) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed opening secret " "file [%s]. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } /* accessing file details */ if (fstat(fd, &secret_stats) < 0) { int eno = errno; errno = 0; close(fd); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : fstat for secret file %s " "failed. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } if (secret_stats.st_size != sizeof(MAXKEYS)) { int eno = errno; errno = 0; close(fd); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Secrets file %s has " "incorrect size. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } if (secret_stats.st_mode != (S_IRUSR|S_IFREG)) { close(fd); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Ignoring secrets file " "%s, invalid permissions.", secret_file))); return NULL; } if ((keys = (MAXKEYS *)malloc(sizeof(MAXKEYS))) == NULL) { close(fd); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Memory allocation failed " "for key structure."))); return NULL; } /** * Read all data from file. * MAXKEYS (secrets.h) is struct for key, _not_ length-related macro. */ len = read(fd, keys, sizeof(MAXKEYS)); if (len != sizeof(MAXKEYS)) { int eno = errno; errno = 0; close(fd); free(keys); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Read from secrets file " "%s failed. Read %d, expected %d bytes. Error %d, %s.", secret_file, len, sizeof(MAXKEYS), eno, strerror(eno)))); return NULL; } /* Close the file */ if (close(fd) < 0) { int eno = errno; errno = 0; free(keys); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed closing the " "secrets file %s. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } ss_dassert(keys != NULL); return keys; }
/** * Process a configuration context update and turn it into the set of object * we need. * * @param context The configuration data */ static int process_config_update(CONFIG_CONTEXT *context) { CONFIG_CONTEXT *obj; SERVICE *service; SERVER *server; /** * Process the data and create the services and servers defined * in the data. */ obj = context; while (obj) { char *type = config_get_value(obj->parameters, "type"); if (type == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Configuration object %s has no type.", obj->object))); } else if (!strcmp(type, "service")) { char *router = config_get_value(obj->parameters, "router"); if (router) { if ((service = service_find(obj->object)) != NULL) { char *user; char *auth; char *enable_root_user; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); user = config_get_value(obj->parameters, "user"); auth = config_get_value(obj->parameters, "passwd"); if (user && auth) { service_update(service, router, user, auth); if (enable_root_user) serviceEnableRootUser(service, atoi(enable_root_user)); } obj->element = service; } else { char *user; char *auth; char *enable_root_user; enable_root_user = config_get_value(obj->parameters, "enable_root_user"); user = config_get_value(obj->parameters, "user"); auth = config_get_value(obj->parameters, "passwd"); obj->element = service_alloc(obj->object, router); if (obj->element && user && auth) { serviceSetUser(obj->element, user, auth); if (enable_root_user) serviceEnableRootUser(service, atoi(enable_root_user)); } } } else { obj->element = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : No router defined for service " "'%s'.", obj->object))); } } else if (!strcmp(type, "server")) { char *address; char *port; char *protocol; char *monuser; char *monpw; address = config_get_value(obj->parameters, "address"); port = config_get_value(obj->parameters, "port"); protocol = config_get_value(obj->parameters, "protocol"); monuser = config_get_value(obj->parameters, "monitoruser"); monpw = config_get_value(obj->parameters, "monitorpw"); if (address && port && protocol) { if ((server = server_find(address, atoi(port))) != NULL) { server_update(server, protocol, monuser, monpw); obj->element = server; } else { obj->element = server_alloc(address, protocol, atoi(port)); if (obj->element && monuser && monpw) { serverAddMonUser(obj->element, monuser, monpw); } } } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Server '%s' is missing a " "required " "configuration parameter. A server must " "have address, port and protocol " "defined.", obj->object))); } } obj = obj->next; } /* * Now we have the services we can add the servers to the services * add the protocols to the services */ obj = context; while (obj) { char *type = config_get_value(obj->parameters, "type"); if (type == NULL) ; else if (!strcmp(type, "service")) { char *servers; char *roptions; servers = config_get_value(obj->parameters, "servers"); roptions = config_get_value(obj->parameters, "router_options"); if (servers && obj->element) { char *s = strtok(servers, ","); while (s) { CONFIG_CONTEXT *obj1 = context; while (obj1) { if (strcmp(s, obj1->object) == 0 && obj->element && obj1->element) { if (!serviceHasBackend(obj->element, obj1->element)) { serviceAddBackend( obj->element, obj1->element); } } obj1 = obj1->next; } s = strtok(NULL, ","); } } if (roptions && obj->element) { char *s = strtok(roptions, ","); serviceClearRouterOptions(obj->element); while (s) { serviceAddRouterOption(obj->element, s); s = strtok(NULL, ","); } } } else if (!strcmp(type, "listener")) { char *service; char *port; char *protocol; char *address; service = config_get_value(obj->parameters, "service"); address = config_get_value(obj->parameters, "address"); port = config_get_value(obj->parameters, "port"); protocol = config_get_value(obj->parameters, "protocol"); if (service && port && protocol) { CONFIG_CONTEXT *ptr = context; while (ptr && strcmp(ptr->object, service) != 0) ptr = ptr->next; if (ptr && ptr->element && serviceHasProtocol(ptr->element, protocol, atoi(port)) == 0) { serviceAddProtocol(ptr->element, protocol, address, atoi(port)); serviceStartProtocol(ptr->element, protocol, atoi(port)); } } } else if (strcmp(type, "server") != 0 && strcmp(type, "monitor") != 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Configuration object %s has an invalid " "type specified.", obj->object))); } obj = obj->next; } return 1; }
/** * Monitor an individual server * * @param handle The MySQL Monitor object * @param database The database to probe */ static void monitorDatabase(MONITOR* mon, MONITOR_SERVERS *database) { MM_MONITOR *handle = mon->handle; MYSQL_ROW row; MYSQL_RES *result; int isslave = 0; int ismaster = 0; char *uname = mon->user; char *passwd = mon->password; 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 previous status */ database->mon_prev_status = database->server->status; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); int read_timeout = 1; if(database->con) mysql_close(database->con); database->con = mysql_init(NULL); 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; if(mysql_field_count(database->con) != 1) { mysql_free_result(result); skygw_log_write(LE,"Error: Unexpected result for 'SELECT @@server_id'. Expected 1 column." " MySQL Version: %s",version_str); return; } 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; if(mysql_field_count(database->con) < 42) { mysql_free_result(result); skygw_log_write(LE,"Error: \"SHOW ALL SLAVES STATUS\" " "returned less than the expected amount of columns. Expected 42 columns" " MySQL Version: %s",version_str); return; } 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; if(mysql_field_count(database->con) < 40) { mysql_free_result(result); if(server_version < 5*10000 + 5*100) { if(database->log_version_err) { skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " " for versions less than 5.5 does not have master_server_id, " "replication tree cannot be resolved for server %s." " MySQL Version: %s",database->server->unique_name,version_str); database->log_version_err = false; } } else { skygw_log_write(LE,"Error: \"SHOW SLAVE STATUS\" " "returned less than the expected amount of columns. Expected 40 columns." " MySQL Version: %s",version_str); } return; } 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) { if(mysql_field_count(database->con) < 2) { mysql_free_result(result); skygw_log_write(LE,"Error: Unexpected result for \"SHOW GLOBAL VARIABLES LIKE 'read_only'\". Expected 2 columns." " MySQL Version: %s",version_str); return; } while ((row = mysql_fetch_row(result))) { if (strncasecmp(row[1], "OFF", 3) == 0) { ismaster = 1; } else { isslave = 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 (ismaster) { monitor_clear_pending_status(database, SERVER_SLAVE); monitor_set_pending_status(database, SERVER_MASTER); /* Set replication depth to 0 */ database->server->depth = 0; } }
/** * Create an instance of the filter for a particular service * within MaxScale. * * @param options The options for this filter * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { int i; TOPN_INSTANCE *my_instance; if ((my_instance = calloc(1, sizeof(TOPN_INSTANCE))) != NULL) { my_instance->topN = 10; my_instance->match = NULL; my_instance->exclude = NULL; my_instance->source = NULL; my_instance->user = NULL; my_instance->filebase = strdup("top"); for (i = 0; params && params[i]; i++) { if (!strcmp(params[i]->name, "count")) my_instance->topN = atoi(params[i]->value); else if (!strcmp(params[i]->name, "filebase")) { free(my_instance->filebase); my_instance->filebase = strdup(params[i]->value); } else if (!strcmp(params[i]->name, "match")) { my_instance->match = strdup(params[i]->value); } else if (!strcmp(params[i]->name, "exclude")) { my_instance->exclude = strdup(params[i]->value); } else if (!strcmp(params[i]->name, "source")) my_instance->source = strdup(params[i]->value); else if (!strcmp(params[i]->name, "user")) my_instance->user = strdup(params[i]->value); else if (!filter_standard_parameter(params[i]->name)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "topfilter: Unexpected parameter '%s'.\n", params[i]->name))); } } if (options) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, "topfilter: Options are not supported by this " " filter. They will be ignored\n"))); } my_instance->sessions = 0; if (my_instance->match && regcomp(&my_instance->re, my_instance->match, REG_ICASE)) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, "topfilter: Invalid regular expression '%s'" " for the match parameter.\n", my_instance->match))); free(my_instance->match); free(my_instance->source); free(my_instance->user); free(my_instance->filebase); free(my_instance); return NULL; } if (my_instance->exclude && regcomp(&my_instance->exre, my_instance->exclude, REG_ICASE)) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, "qlafilter: Invalid regular expression '%s'" " for the nomatch paramter.\n", my_instance->match))); regfree(&my_instance->re); free(my_instance->match); free(my_instance->source); free(my_instance->user); free(my_instance->filebase); free(my_instance); return NULL; } } return (FILTER *)my_instance; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { return 0; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; /* Allocate specific data for MySQL users */ service->users = mysql_users_alloc(); loaded = load_mysql_users(service); /* 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.", loaded))); } else { /* Generic users table */ service->users = users_alloc(); } if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); return 0; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { dcb_close(port->listener); } } else { dcb_close(port->listener); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); } return listeners; }
/** * Monitor an individual server * * @param handle The MySQL Monitor object * @param database The database to probe */ static void monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database) { GALERA_MONITOR* handle = (GALERA_MONITOR*)mon->handle; MYSQL_ROW row; MYSQL_RES *result,*result2; int isjoined = 0; char *uname = mon->user; char *passwd = mon->password; 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 even probe server flagged as in maintenance */ if (SERVER_IN_MAINT(database->server)) return; /** Store previous status */ database->mon_prev_status = database->server->status; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); int connect_timeout = mon->connect_timeout; int read_timeout = mon->read_timeout; int write_timeout = mon->write_timeout; if(database->con) mysql_close(database->con); database->con = mysql_init(NULL); mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&connect_timeout); mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *)&read_timeout); 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) { free(dpwd); 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); } database->server->node_id = -1; if (mon_status_changed(database) && 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)))); } return; } else { server_clear_status(database->server, SERVER_AUTH_ERROR); } free(dpwd); } /* If we get this far then we have a working connection */ server_set_status(database->server, SERVER_RUNNING); /* 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); } /* Check if the the Galera FSM shows this node is joined to the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state'") == 0 && (result = mysql_store_result(database->con)) != NULL) { if(mysql_field_count(database->con) < 2) { mysql_free_result(result); skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'wsrep_local_state'\". Expected 2 columns." " MySQL Version: %s",version_str); return; } while ((row = mysql_fetch_row(result))) { if (strcmp(row[1], "4") == 0) isjoined = 1; /* Check if the node is a donor and is using xtrabackup, in this case it can stay alive */ else if (strcmp(row[1], "2") == 0 && handle->availableWhenDonor == 1) { if (mysql_query(database->con, "SHOW VARIABLES LIKE 'wsrep_sst_method'") == 0 && (result2 = mysql_store_result(database->con)) != NULL) { if(mysql_field_count(database->con) < 2) { mysql_free_result(result); mysql_free_result(result2); skygw_log_write(LE,"Error: Unexpected result for \"SHOW VARIABLES LIKE 'wsrep_sst_method'\". Expected 2 columns." " MySQL Version: %s",version_str); return; } while ((row = mysql_fetch_row(result))) { if (strncmp(row[1], "xtrabackup", 10) == 0) isjoined = 1; } mysql_free_result(result2); } } } mysql_free_result(result); } /* Check the the Galera node index in the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_index'") == 0 && (result = mysql_store_result(database->con)) != NULL) { long local_index = -1; if(mysql_field_count(database->con) < 2) { mysql_free_result(result); skygw_log_write(LE,"Error: Unexpected result for \"SHOW STATUS LIKE 'wsrep_local_index'\". Expected 2 columns." " MySQL Version: %s",version_str); return; } while ((row = mysql_fetch_row(result))) { local_index = strtol(row[1], NULL, 10); if ((errno == ERANGE && (local_index == LONG_MAX || local_index == LONG_MIN)) || (errno != 0 && local_index == 0)) { local_index = -1; } database->server->node_id = local_index; } mysql_free_result(result); } if (isjoined) server_set_status(database->server, SERVER_JOINED); else server_clear_status(database->server, SERVER_JOINED); }
/** * Load the dynamic library related to a gateway module. The routine * will look for library files in the current directory, * $MAXSCALE_HOME/modules and /usr/local/skysql/MaxScale/modules. * * @param module Name of the module to load * @param type Type of module, used purely for registration * @return The module specific entry point structure or NULL */ void * load_module(const char *module, const char *type) { char *home, *version; char fname[MAXPATHLEN]; void *dlhandle, *sym; char *(*ver)(); void *(*ep)(), *modobj; MODULES *mod; MODULE_INFO *mod_info = NULL; if ((mod = find_module(module)) == NULL) { /*< * The module is not already loaded * * Search of the shared object. */ sprintf(fname, "./lib%s.so", module); if (access(fname, F_OK) == -1) { home = get_maxscale_home (); sprintf(fname, "%s/modules/lib%s.so", home, module); if (access(fname, F_OK) == -1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to find library for " "module: %s.", module))); return NULL; } } if ((dlhandle = dlopen(fname, RTLD_NOW|RTLD_LOCAL)) == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load library for module: " "%s\n\n\t\t %s." "\n\n", module, dlerror()))); return NULL; } if ((sym = dlsym(dlhandle, "version")) == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Version interface not supported by " "module: %s\n\t\t\t %s.", module, dlerror()))); dlclose(dlhandle); return NULL; } ver = sym; version = ver(); /* * If the module has a ModuleInit function cal it now. */ if ((sym = dlsym(dlhandle, "ModuleInit")) != NULL) { void (*ModuleInit)() = sym; ModuleInit(); } if ((sym = dlsym(dlhandle, "info")) != NULL) { int fatal = 0; mod_info = sym; if (strcmp(type, MODULE_PROTOCOL) == 0 && mod_info->modapi != MODULE_API_PROTOCOL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Module '%s' does not implement " "the protocol API.\n", module))); fatal = 1; } if (strcmp(type, MODULE_ROUTER) == 0 && mod_info->modapi != MODULE_API_ROUTER) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Module '%s' does not implement " "the router API.\n", module))); fatal = 1; } if (strcmp(type, MODULE_MONITOR) == 0 && mod_info->modapi != MODULE_API_MONITOR) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Module '%s' does not implement " "the monitor API.\n", module))); fatal = 1; } if (strcmp(type, MODULE_FILTER) == 0 && mod_info->modapi != MODULE_API_FILTER) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Module '%s' does not implement " "the filter API.\n", module))); fatal = 1; } if (fatal) { dlclose(dlhandle); return NULL; } } if ((sym = dlsym(dlhandle, "GetModuleObject")) == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Expected entry point interface missing " "from module: %s\n\t\t\t %s.", module, dlerror()))); dlclose(dlhandle); return NULL; } ep = sym; modobj = ep(); LOGIF(LM, (skygw_log_write_flush( LOGFILE_MESSAGE, "Loaded module %s: %s from %s", module, version, fname))); register_module(module, type, dlhandle, version, modobj, mod_info); } else { /* * The module is already loaded, get the entry points again and * return a reference to the already loaded module. */ modobj = mod->modobj; } return modobj; }
/** * The entry point for the monitoring module thread * * @param arg The handle of the monitor */ static void monitorMain(void *arg) { MONITOR* mon = (MONITOR*)arg; GALERA_MONITOR *handle; MONITOR_SERVERS *ptr; size_t nrounds = 0; MONITOR_SERVERS *candidate_master = NULL; int master_stickiness; int is_cluster=0; int log_no_members = 1; monitor_event_t evtype; spinlock_acquire(&mon->lock); handle = (GALERA_MONITOR *)mon->handle; spinlock_release(&mon->lock); master_stickiness = handle->disableMasterFailback; 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)%mon->interval) >= MON_BASE_INTERVAL_MS) { nrounds += 1; continue; } nrounds += 1; /* reset cluster members counter */ is_cluster=0; ptr = mon->databases; while (ptr) { ptr->mon_prev_status = ptr->server->status; monitorDatabase(mon, 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; /* clear M/S status */ server_clear_status(ptr->server, SERVER_SLAVE); server_clear_status(ptr->server, SERVER_MASTER); /* clear master sticky status */ server_clear_status(ptr->server, SERVER_MASTER_STICKINESS); } /* Log server status change */ if (mon_status_changed(ptr)) { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "Backend server %s:%d state : %s", ptr->server->name, ptr->server->port, STRSRVSTATUS(ptr->server)))); } if (!(SERVER_IS_RUNNING(ptr->server)) || !(SERVER_IS_IN_CLUSTER(ptr->server))) { dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); } if (SERVER_IS_DOWN(ptr->server)) { /** Increase this server'e error count */ dcb_call_foreach(ptr->server,DCB_REASON_NOT_RESPONDING); ptr->mon_err_count += 1; } else { /** Reset this server's error count */ ptr->mon_err_count = 0; } 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, following MIN(node_id) rule */ candidate_master = get_candidate_master(mon->databases); /* Select the master, based on master_stickiness */ if (1 == handle->disableMasterRoleSetting) { handle->master = NULL; } else { handle->master = set_cluster_master(handle->master, candidate_master, master_stickiness); } ptr = mon->databases; while (ptr) { if (!SERVER_IS_JOINED(ptr->server) || SERVER_IN_MAINT(ptr->server)) { ptr = ptr->next; continue; } if (handle->master) { if (ptr != handle->master) { /* set the Slave role */ server_set_status(ptr->server, SERVER_SLAVE); server_clear_status(ptr->server, SERVER_MASTER); /* clear master stickiness */ 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 stickiness */ server_set_status(handle->master->server, SERVER_MASTER_STICKINESS); } else { /* clear master stickiness */ server_clear_status(ptr->server, SERVER_MASTER_STICKINESS); } } } is_cluster++; ptr = ptr->next; } if (is_cluster == 0 && log_no_members) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error: there are no cluster members"))); log_no_members = 0; } else { if (is_cluster > 0 && log_no_members == 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Info: found cluster members"))); log_no_members = 1; } } ptr = mon->databases; while(ptr) { /** Execute monitor script if a server state has changed */ if(mon_status_changed(ptr)) { evtype = mon_get_event_type(ptr); if(isGaleraEvent(evtype)) { skygw_log_write(LOGFILE_TRACE,"Server changed state: %s[%s:%u]: %s", ptr->server->unique_name, ptr->server->name,ptr->server->port, mon_get_event_name(ptr)); if(handle->script && handle->events[evtype]) { monitor_launch_script(mon,ptr,handle->script); } } } ptr = ptr->next; } } }
/** * 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 isjoined = 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 even probe server flagged as in maintenance */ if (SERVER_IN_MAINT(database->server)) return; /** Store previous status */ database->mon_prev_status = database->server->status; if (database->con == NULL || mysql_ping(database->con) != 0) { char *dpwd = decryptPassword(passwd); int rc; 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) { free(dpwd); 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); } database->server->node_id = -1; if (mon_status_changed(database) && 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)))); } return; } else { server_clear_status(database->server, SERVER_AUTH_ERROR); } free(dpwd); } /* If we get this far then we have a working connection */ server_set_status(database->server, 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); } /* Check if the the Galera FSM shows this node is joined to the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_state_comment'") == 0 && (result = mysql_store_result(database->con)) != NULL) { num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { if (strncasecmp(row[1], "SYNCED", 3) == 0) isjoined = 1; } mysql_free_result(result); } /* Check the the Galera node index in the cluster */ if (mysql_query(database->con, "SHOW STATUS LIKE 'wsrep_local_index'") == 0 && (result = mysql_store_result(database->con)) != NULL) { long local_index = -1; num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { local_index = strtol(row[1], NULL, 10); if ((errno == ERANGE && (local_index == LONG_MAX || local_index == LONG_MIN)) || (errno != 0 && local_index == 0)) { local_index = -1; } database->server->node_id = local_index; } mysql_free_result(result); } if (isjoined) server_set_status(database->server, SERVER_JOINED); else server_clear_status(database->server, SERVER_JOINED); }
/** * Create an instance of the filter for a particular service * within MaxScale. * * @param options The options for this filter * @param params The array of name/value pair parameters for the filter * * @return The instance data for this new instance */ static FILTER * createInstance(char **options, FILTER_PARAMETER **params) { QLA_INSTANCE *my_instance; int i; if ((my_instance = calloc(1, sizeof(QLA_INSTANCE))) != NULL) { if (options){ my_instance->filebase = strdup(options[0]); }else{ my_instance->filebase = strdup("qla"); } my_instance->source = NULL; my_instance->userName = NULL; my_instance->match = NULL; my_instance->nomatch = NULL; if (params) { for (i = 0; params[i]; i++) { if (!strcmp(params[i]->name, "match")) { my_instance->match = strdup(params[i]->value); } else if (!strcmp(params[i]->name, "exclude")) { my_instance->nomatch = strdup(params[i]->value); } else if (!strcmp(params[i]->name, "source")) my_instance->source = strdup(params[i]->value); else if (!strcmp(params[i]->name, "user")) my_instance->userName = strdup(params[i]->value); else if (!strcmp(params[i]->name, "filebase")) { if (my_instance->filebase){ free(my_instance->filebase); my_instance->filebase = NULL; } my_instance->filebase = strdup(params[i]->value); } else if (!filter_standard_parameter(params[i]->name)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "qlafilter: Unexpected parameter '%s'.\n", params[i]->name))); } } } my_instance->sessions = 0; if (my_instance->match && regcomp(&my_instance->re, my_instance->match, REG_ICASE)) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, "qlafilter: Invalid regular expression '%s'" " for the match parameter.\n", my_instance->match))); free(my_instance->match); free(my_instance->source); if(my_instance->filebase){ free(my_instance->filebase); } free(my_instance); return NULL; } if (my_instance->nomatch && regcomp(&my_instance->nore, my_instance->nomatch, REG_ICASE)) { LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR, "qlafilter: Invalid regular expression '%s'" " for the nomatch paramter.\n", my_instance->match))); if (my_instance->match) regfree(&my_instance->re); free(my_instance->match); free(my_instance->source); if(my_instance->filebase){ free(my_instance->filebase); } free(my_instance); return NULL; } } return (FILTER *)my_instance; }
/** * secrets_readKeys * * This routine reads data from a binary file and extracts the AES encryption key * and the AES Init Vector * * @return The keys structure or NULL on error */ static MAXKEYS * secrets_readKeys() { char secret_file[255]; char *home; MAXKEYS *keys; struct stat secret_stats; int fd; int len; home = getenv("MAXSCALE_HOME"); if (home == NULL) { home = "/usr/local/skysql/MaxScale"; } snprintf(secret_file, 255, "%s/etc/.secrets", home); /* Try to access secrets file */ if (access(secret_file, R_OK) == -1) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : access for secrets file " "[%s] failed. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } /* open secret file */ if ((fd = open(secret_file, O_RDONLY)) < 0) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed opening secret " "file [%s]. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } /* accessing file details */ if (fstat(fd, &secret_stats) < 0) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : fstat for secret file %s " "failed. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } if (secret_stats.st_size != sizeof(MAXKEYS)) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Secrets file %s has " "incorrect size. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } if (secret_stats.st_mode != (S_IRUSR|S_IFREG)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Ignoring secrets file " "%s, invalid permissions.", secret_file))); return NULL; } if ((keys = (MAXKEYS *)malloc(sizeof(MAXKEYS))) == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Memory allocation failed " "for key structure."))); return NULL; } /** * Read all data from file. * MAXKEYS (secrets.h) is struct for key, _not_ length-related macro. */ len = read(fd, keys, sizeof(MAXKEYS)); if (len != sizeof(MAXKEYS)) { int eno = errno; errno = 0; free(keys); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Read from secrets file " "%s failed. Read %d, expected %d bytes. Error %d, %s.", secret_file, len, sizeof(MAXKEYS), eno, strerror(eno)))); return NULL; } /* Close the file */ if (close(fd) < 0) { int eno = errno; errno = 0; free(keys); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed closing the " "secrets file %s. Error %d, %s.", secret_file, eno, strerror(eno)))); return NULL; } ss_dassert(keys != NULL); return keys; }
/** * Allocate a new session for a new client of the specified service. * * Create the link to the router session by calling the newSession * entry point of the router using the router instance of the * service this session is part of. * * @param service The service this connection was established by * @param client_dcb The client side DCB * @return The newly created session or NULL if an error occured */ SESSION * session_alloc(SERVICE *service, DCB *client_dcb) { SESSION *session; session = (SESSION *)calloc(1, sizeof(SESSION)); ss_info_dassert(session != NULL, "Allocating memory for session failed."); if (session == NULL) { int eno = errno; errno = 0; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to allocate memory for " "session object due error %d, %s.", eno, strerror(eno)))); goto return_session; } #if defined(SS_DEBUG) session->ses_chk_top = CHK_NUM_SESSION; session->ses_chk_tail = CHK_NUM_SESSION; #endif spinlock_init(&session->ses_lock); /*< * Prevent backend threads from accessing before session is completely * initialized. */ spinlock_acquire(&session->ses_lock); session->service = service; session->client = client_dcb; session->n_filters = 0; memset(&session->stats, 0, sizeof(SESSION_STATS)); session->stats.connect = time(0); session->state = SESSION_STATE_ALLOC; /*< * Associate the session to the client DCB and set the reference count on * the session to indicate that there is a single reference to the * session. There is no need to protect this or use atomic add as the * session has not been made available to the other threads at this * point. */ session->data = client_dcb->data; client_dcb->session = session; session->refcount = 1; /*< * This indicates that session is ready to be shared with backend * DCBs. Note that this doesn't mean that router is initialized yet! */ session->state = SESSION_STATE_READY; /*< Release session lock */ spinlock_release(&session->ses_lock); /* * Only create a router session if we are not the listening * DCB or an internal DCB. Creating a router session may create a connection to a * backend server, depending upon the router module implementation * and should be avoided for the listener session * * Router session creation may create other DCBs that link to the * session, therefore it is important that the session lock is * relinquished beforethe router call. */ if (client_dcb->state != DCB_STATE_LISTENING && client_dcb->dcb_role != DCB_ROLE_INTERNAL) { session->router_session = service->router->newSession(service->router_instance, session); if (session->router_session == NULL) { /** * Inform other threads that session is closing. */ session->state = SESSION_STATE_STOPPING; /*< * Decrease refcount, set dcb's session pointer NULL * and set session pointer to NULL. */ session_free(session); client_dcb->session = NULL; session = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create %s session.", service->name))); 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 * be pushed to the filter and the head updated. * * NB This dictates that filters are created starting at the end * of the chain nearest the router working back to the client * protocol end of the chain. */ session->head.instance = service->router_instance; session->head.session = session->router_session; session->head.routeQuery = (void *)(service->router->routeQuery); session->tail.instance = session; session->tail.session = session; session->tail.clientReply = session_reply; if (service->n_filters > 0) { if (!session_setup_filters(session)) { /** * Inform other threads that session is closing. */ session->state = SESSION_STATE_STOPPING; /*< * Decrease refcount, set dcb's session pointer NULL * and set session pointer to NULL. */ session_free(session); client_dcb->session = NULL; session = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create %s session.", service->name))); goto return_session; } } } spinlock_acquire(&session_spin); if (session->state != SESSION_STATE_READY) { session_free(session); client_dcb->session = NULL; session = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create %s session.", service->name))); spinlock_release(&session_spin); } else { session->state = SESSION_STATE_ROUTER_READY; session->next = allSessions; allSessions = session; spinlock_release(&session_spin); atomic_add(&service->stats.n_sessions, 1); atomic_add(&service->stats.n_current, 1); CHK_SESSION(session); } return_session: return session; }
/** * 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; long master_id; 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; master_id = -1; ptr = handle->databases; while (ptr) { unsigned int prev_status = ptr->server->status; monitorDatabase(ptr, handle->defaultUser, handle->defaultPasswd,handle); if (ptr->server->status != prev_status || SERVER_IS_DOWN(ptr->server)) { LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "Backend server %s:%d state : %s", ptr->server->name, ptr->server->port, STRSRVSTATUS(ptr->server)))); } ptr = ptr->next; } } }
/** * secrets_writeKeys * * This routine writes into a binary file the AES encryption key * and the AES Init Vector * * @param secret_file The file with secret keys * @return 0 on success and 1 on failure */ int secrets_writeKeys(char *path) { int fd,randfd; unsigned int randval; MAXKEYS key; char secret_file[PATH_MAX + 10]; if(strlen(path) > PATH_MAX) { skygw_log_write(LOGFILE_ERROR,"Error: Pathname too long."); return 1; } sprintf(secret_file,"%s/.secrets",path); /* Open for writing | Create | Truncate the file for writing */ if ((fd = open(secret_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR)) < 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed opening secret " "file [%s]. Error %d, %s.", secret_file, errno, strerror(errno)))); return 1; } /* Open for writing | Create | Truncate the file for writing */ if ((randfd = open("/dev/random", O_RDONLY)) < 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed opening /dev/random. Error %d, %s.", errno, strerror(errno)))); close(fd); return 1; } if(read(randfd,(void*)&randval,sizeof(unsigned int)) < 1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed to read /dev/random."))); close(fd); close(randfd); return 1; } close(randfd); srand(randval); secrets_random_str(key.enckey, MAXSCALE_KEYLEN); secrets_random_str(key.initvector, MAXSCALE_IV_LEN); /* Write data */ if (write(fd, &key, sizeof(key)) < 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed writing into " "secret file [%s]. Error %d, %s.", secret_file, errno, strerror(errno)))); close(fd); return 1; } /* close file */ if (close(fd) < 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : failed closing the " "secret file [%s]. Error %d, %s.", secret_file, errno, strerror(errno)))); } 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; }
/** * The clientReply entry point. This is passed the response buffer * to which the filter should be applied. Once processed the * query is passed to the upstream component * (filter or router) in the filter chain. * * The function tries to extract a SQL query response out of the response buffer, * adds a timestamp to it and publishes the resulting string on the exchange. * The message is tagged with the same identifier that the query was. * * @param instance The filter instance data * @param session The filter session * @param reply The response data */ static int clientReply(FILTER* instance, void *session, GWBUF *reply) { MQ_SESSION *my_session = (MQ_SESSION *)session; MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; char t_buf[128],*combined; unsigned int pkt_len = pktlen(reply->sbuf->data), offset = 0; amqp_basic_properties_t *prop; if (my_session->was_query){ int packet_ok = 0, was_last = 0; my_session->was_query = false; if(pkt_len > 0){ if((prop = malloc(sizeof(amqp_basic_properties_t)))){ prop->_flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_MESSAGE_ID_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; prop->content_type = amqp_cstring_bytes("text/plain"); prop->delivery_mode = AMQP_DELIVERY_PERSISTENT; prop->correlation_id = amqp_cstring_bytes(my_session->uid); prop->message_id = amqp_cstring_bytes("reply"); } if(!(combined = calloc(GWBUF_LENGTH(reply) + 256,sizeof(char)))){ skygw_log_write_flush(LOGFILE_ERROR, "Error : Out of memory"); } memset(t_buf,0,128); sprintf(t_buf,"%lu|",(unsigned long)time(NULL)); memcpy(combined + offset,t_buf,strnlen(t_buf,40)); offset += strnlen(t_buf,40); if(*(reply->sbuf->data + 4) == 0x00){ /**OK packet*/ unsigned int aff_rows = 0, l_id = 0, s_flg = 0, wrn = 0; unsigned char *ptr = (unsigned char*)(reply->sbuf->data + 5); pkt_len = pktlen(reply->sbuf->data); aff_rows = consume_leitoi(&ptr); l_id = consume_leitoi(&ptr); s_flg |= *ptr++; s_flg |= (*ptr++ << 8); wrn |= *ptr++; wrn |= (*ptr++ << 8); sprintf(combined + offset,"OK - affected_rows: %d " " last_insert_id: %d " " status_flags: %#0x " " warnings: %d ", aff_rows,l_id,s_flg,wrn); offset += strnlen(combined,GWBUF_LENGTH(reply) + 256) - offset; if(pkt_len > 7){ int plen = consume_leitoi(&ptr); if(plen > 0){ sprintf(combined + offset," message: %.*s\n",plen,ptr); } } packet_ok = 1; was_last = 1; }else if(*(reply->sbuf->data + 4) == 0xff){ /**ERR packet*/ sprintf(combined + offset,"ERROR - message: %.*s", (int)(reply->end - ((void*)(reply->sbuf->data + 13))), (char *)reply->sbuf->data + 13); packet_ok = 1; was_last = 1; }else if(*(reply->sbuf->data + 4) == 0xfb){ /**LOCAL_INFILE request packet*/ unsigned char *rset = (unsigned char*)reply->sbuf->data; strcpy(combined + offset,"LOCAL_INFILE: "); strncat(combined + offset,(const char*)rset+5,pktlen(rset)); packet_ok = 1; was_last = 1; }else{ /**Result set*/ unsigned char *rset = (unsigned char*)(reply->sbuf->data + 4); char *tmp; unsigned int col_cnt = consume_leitoi(&rset); tmp = calloc(256,sizeof(char)); sprintf(tmp,"Columns: %d",col_cnt); memcpy(combined + offset,tmp,strnlen(tmp,256)); offset += strnlen(tmp,256); memcpy(combined + offset,"\n",1); offset++; free(tmp); packet_ok = 1; was_last = 1; } if(packet_ok){ pushMessage(my_instance,prop,combined); if(was_last){ /**Successful reply received and sent, releasing uid*/ free(my_session->uid); my_session->uid = NULL; } } } } return my_session->up.clientReply(my_session->up.instance, my_session->up.session, reply); }
/** * Receive the MySQL authentication packet from backend, packet # is 2 * * @param protocol The MySQL protocol structure * @return -1 in case of failure, 0 if there was nothing to read, 1 if read * was successful. */ int gw_receive_backend_auth( MySQLProtocol *protocol) { int n = -1; GWBUF *head = NULL; DCB *dcb = protocol->owner_dcb; uint8_t *ptr = NULL; int rc = 0; n = dcb_read(dcb, &head); /*< * Read didn't fail and there is enough data for mysql packet. */ if (n != -1 && head != NULL && GWBUF_LENGTH(head) >= 5) { ptr = GWBUF_DATA(head); /*< * 5th byte is 0x0 if successful. */ if (ptr[4] == '\x00') { rc = 1; } else { uint8_t* tmpbuf = (uint8_t *)calloc(1, GWBUF_LENGTH(head)+1); memcpy(tmpbuf, ptr, GWBUF_LENGTH(head)); LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Invalid " "authentication message from backend dcb %p " "fd %d, ptr[4] = %p, msg %s.", pthread_self(), dcb, dcb->fd, tmpbuf[4], tmpbuf))); free(tmpbuf); rc = -1; } /*< * Remove data from buffer. */ head = gwbuf_consume(head, GWBUF_LENGTH(head)); } else if (n == 0) { /*< * This is considered as success because call didn't fail, * although no bytes was read. */ rc = 0; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Read zero bytes from " "backend dcb %p fd %d in state %s. n %d, head %p, len %d", pthread_self(), dcb, dcb->fd, STRDCBSTATE(dcb->state), n, head, (head == NULL) ? 0 : GWBUF_LENGTH(head)))); } else { ss_dassert(n < 0 && head == NULL); rc = -1; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Reading from backend dcb %p " "fd %d in state %s failed. n %d, head %p, len %d", pthread_self(), dcb, dcb->fd, STRDCBSTATE(dcb->state), n, head, (head == NULL) ? 0 : GWBUF_LENGTH(head)))); } return rc; }