/** * The routeQuery entry point. This is passed the query buffer * to which the filter should be applied. Once applied the * query should normally be passed to the downstream component * (filter or router) in the filter chain. * * If my_session->residual is set then duplicate that many bytes * and send them to the branch. * * If my_session->residual is zero then this must be a new request * Extract the SQL text if possible, match against that text and forward * the request. If the requets is not contained witin the packet we have * then set my_session->residual to the number of outstanding bytes * * @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) { TEE_INSTANCE *my_instance = (TEE_INSTANCE *)instance; TEE_SESSION *my_session = (TEE_SESSION *)session; char *ptr; int rval; GWBUF *buffer = NULL, *clone = NULL; unsigned char command = gwbuf_length(queue) >= 5 ? *((unsigned char*)queue->start + 4) : 1; #ifdef SS_DEBUG skygw_log_write(LOGFILE_TRACE,"Tee routeQuery: %d : %s", atomic_add(&debug_seq,1), ((char*)queue->start + 5)); #endif spinlock_acquire(&my_session->tee_lock); if(!my_session->active) { skygw_log_write(LOGFILE_TRACE, "Tee: Received a reply when the session was closed."); gwbuf_free(queue); spinlock_release(&my_session->tee_lock); return 0; } if(my_session->queue) { my_session->queue = gwbuf_append(my_session->queue,queue); buffer = modutil_get_next_MySQL_packet(&my_session->queue); } else { buffer = modutil_get_next_MySQL_packet(&queue); my_session->queue = queue; } if(buffer == NULL) { spinlock_release(&my_session->tee_lock); return 1; } clone = clone_query(my_instance, my_session,buffer); spinlock_release(&my_session->tee_lock); /* Reset session state */ if(!reset_session_state(my_session,buffer)) return 0; /** Route query downstream */ spinlock_acquire(&my_session->tee_lock); rval = route_single_query(my_instance,my_session,buffer,clone); spinlock_release(&my_session->tee_lock); return rval; }
/** * We have data from the client, this is a SQL command, or other MySQL * packet type. * * @param instance The router instance * @param router_session The router session returned from the newSession call * @param queue The queue of data buffers to route * @return The number of bytes sent */ static int execute(ROUTER *rinstance, void *router_session, GWBUF *queue) { INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance; INFO_SESSION *session = (INFO_SESSION *)router_session; uint8_t *data; int length, len, residual; char *sql; if (GWBUF_TYPE(queue) == GWBUF_TYPE_HTTP) { return handle_url(instance, session, queue); } if (session->queue) { queue = gwbuf_append(session->queue, queue); session->queue = NULL; queue = gwbuf_make_contiguous(queue); } data = (uint8_t *)GWBUF_DATA(queue); length = data[0] + (data[1] << 8) + (data[2] << 16); if (length + 4 > GWBUF_LENGTH(queue)) { // Incomplete packet, must be buffered session->queue = queue; return 1; } // We have a complete request in a signle buffer if (modutil_MySQL_Query(queue, &sql, &len, &residual)) { sql = strndup(sql, len); int rc = maxinfo_execute_query(instance, session, sql); free(sql); return rc; } else { switch (MYSQL_COMMAND(queue)) { case COM_PING: return maxinfo_ping(instance, session, queue); case COM_STATISTICS: return maxinfo_statistics(instance, session, queue); default: MXS_ERROR("maxinfo: Unexpected MySQL command 0x%x", MYSQL_COMMAND(queue)); } } return 1; }
/** * Send a "fake" format description event to the newly connected slave * * @param router The router instance * @param slave The slave to send the event to */ static void blr_slave_send_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave) { BLFILE *file; REP_HEADER hdr; GWBUF *record, *head; uint8_t *ptr; uint32_t chksum; if ((file = blr_open_binlog(router, slave->binlogfile)) == NULL) return; if ((record = blr_read_binlog(router, file, 4, &hdr)) == NULL) { blr_close_binlog(router, file); return; } blr_close_binlog(router, file); head = gwbuf_alloc(5); ptr = GWBUF_DATA(head); encode_value(ptr, hdr.event_size + 1, 24); // Payload length ptr += 3; *ptr++ = slave->seqno++; *ptr++ = 0; // OK head = gwbuf_append(head, record); ptr = GWBUF_DATA(record); encode_value(ptr, time(0), 32); // Overwrite timestamp ptr += 13; encode_value(ptr, 0, 32); // Set next position to 0 /* * Since we have changed the timestamp we must recalculate the CRC * * Position ptr to the start of the event header, * calculate a new checksum * and write it into the header */ ptr = GWBUF_DATA(record) + hdr.event_size - 4; chksum = crc32(0L, NULL, 0); chksum = crc32(chksum, GWBUF_DATA(record), hdr.event_size - 4); encode_value(ptr, chksum, 32); slave->dcb->func.write(slave->dcb, head); }
/** * The clientReply entry point. This is passed the response buffer * to which the filter should be applied. Once processed the * query is passed to the upstream component * (filter or router) in the filter chain. * * @param instance The filter instance data * @param session The filter session * @param reply The response data */ static int clientReply(FILTER* instance, void *session, GWBUF *reply) { int rc, branch, eof; TEE_SESSION *my_session = (TEE_SESSION *) session; bool route = false, mpkt; GWBUF *complete = NULL; unsigned char *ptr; uint16_t flags = 0; int min_eof = my_session->command != 0x04 ? 2 : 1; int more_results = 0; #ifdef SS_DEBUG int prev_debug_seq = atomic_add(&debug_seq, 1); ptr = (unsigned char*) reply->start; MXS_INFO("Tee clientReply [%s] [%s] [%s]: %d", instance ? "parent" : "child", my_session->active ? "open" : "closed", PTR_IS_ERR(ptr) ? "ERR" : PTR_IS_OK(ptr) ? "OK" : "RSET", prev_debug_seq); #endif spinlock_acquire(&my_session->tee_lock); if (!my_session->active) { MXS_INFO("Tee: Failed to return reply, session is closed"); gwbuf_free(reply); rc = 0; if (my_session->waiting[PARENT]) { GWBUF* errbuf = modutil_create_mysql_err_msg(1, 0, 1, "0000", "Session closed."); my_session->waiting[PARENT] = false; my_session->up.clientReply(my_session->up.instance, my_session->up.session, errbuf); } goto retblock; } branch = instance == NULL ? CHILD : PARENT; my_session->tee_partials[branch] = gwbuf_append(my_session->tee_partials[branch], reply); my_session->tee_partials[branch] = gwbuf_make_contiguous(my_session->tee_partials[branch]); complete = modutil_get_complete_packets(&my_session->tee_partials[branch]); if (complete == NULL) { /** Incomplete packet */ MXS_DEBUG("tee.c: Incomplete packet, " "waiting for a complete packet before forwarding."); rc = 1; goto retblock; } complete = gwbuf_make_contiguous(complete); if (my_session->tee_partials[branch] && GWBUF_EMPTY(my_session->tee_partials[branch])) { gwbuf_free(my_session->tee_partials[branch]); my_session->tee_partials[branch] = NULL; } ptr = (unsigned char*) complete->start; if (my_session->replies[branch] == 0) { MXS_INFO("Tee: First reply to a query for [%s].", branch == PARENT ? "PARENT" : "CHILD"); /* Reply is in a single packet if it is an OK, ERR or LOCAL_INFILE packet. * Otherwise the reply is a result set and the amount of packets is unknown. */ if (PTR_IS_ERR(ptr) || PTR_IS_LOCAL_INFILE(ptr) || PTR_IS_OK(ptr) || !my_session->multipacket[branch]) { my_session->waiting[branch] = false; my_session->multipacket[branch] = false; if (PTR_IS_OK(ptr)) { flags = get_response_flags(ptr, true); more_results = (flags & 0x08) && my_session->client_multistatement; if (more_results) { MXS_INFO("Tee: [%s] waiting for more results.", branch == PARENT ? "PARENT" : "CHILD"); } } } #ifdef SS_DEBUG else { MXS_DEBUG("tee.c: [%ld] Waiting for a result set from %s session.", my_session->d_id, branch == PARENT ? "parent" : "child"); } #endif } if (my_session->waiting[branch]) { eof = modutil_count_signal_packets(complete, my_session->use_ok, my_session->eof[branch] > 0, &more_results); more_results &= my_session->client_multistatement; my_session->eof[branch] += eof; if (my_session->eof[branch] >= min_eof) { #ifdef SS_DEBUG MXS_DEBUG("tee.c [%ld] %s received last EOF packet", my_session->d_id, branch == PARENT ? "parent" : "child"); #endif my_session->waiting[branch] = more_results; if (more_results) { my_session->eof[branch] = 0; } } } if (branch == PARENT) { my_session->tee_replybuf = gwbuf_append(my_session->tee_replybuf, complete); } else { gwbuf_free(complete); } my_session->replies[branch]++; rc = 1; mpkt = my_session->multipacket[PARENT] || my_session->multipacket[CHILD]; if (my_session->tee_replybuf != NULL) { if (my_session->branch_session == NULL) { rc = 0; gwbuf_free(my_session->tee_replybuf); my_session->tee_replybuf = NULL; MXS_ERROR("Tee child session was closed."); } if (mpkt) { if (my_session->waiting[PARENT]) { route = true; } else if (my_session->eof[PARENT] >= min_eof && my_session->eof[CHILD] >= min_eof) { route = true; #ifdef SS_DEBUG MXS_DEBUG("tee.c:[%ld] Routing final packet of response set.", my_session->d_id); #endif } } else if (!my_session->waiting[PARENT] && !my_session->waiting[CHILD]) { #ifdef SS_DEBUG MXS_DEBUG("tee.c:[%ld] Routing single packet response.", my_session->d_id); #endif route = true; } } if (route) { #ifdef SS_DEBUG MXS_DEBUG("tee.c:[%ld] Routing buffer '%p' parent(waiting [%s] replies [%d] eof[%d])" " child(waiting [%s] replies[%d] eof [%d])", my_session->d_id, my_session->tee_replybuf, my_session->waiting[PARENT] ? "true" : "false", my_session->replies[PARENT], my_session->eof[PARENT], my_session->waiting[CHILD] ? "true" : "false", my_session->replies[CHILD], my_session->eof[CHILD]); #endif rc = my_session->up.clientReply(my_session->up.instance, my_session->up.session, my_session->tee_replybuf); my_session->tee_replybuf = NULL; } if (my_session->queue && !my_session->waiting[PARENT] && !my_session->waiting[CHILD]) { GWBUF* buffer = modutil_get_next_MySQL_packet(&my_session->queue); GWBUF* clone = clone_query(my_session->instance, my_session, buffer); reset_session_state(my_session, buffer); route_single_query(my_session->instance, my_session, buffer, clone); MXS_INFO("tee: routing queued query"); } retblock: spinlock_release(&my_session->tee_lock); return rc; }
/** * We have a registered slave that is behind the current leading edge of the * binlog. We must replay the log entries to bring this node up to speed. * * There may be a large number of records to send to the slave, the process * is triggered by the slave COM_BINLOG_DUMP message and all the events must * be sent without receiving any new event. This measn there is no trigger into * MaxScale other than this initial message. However, if we simply send all the * events we end up with an extremely long write queue on the DCB and risk * running the server out of resources. * * The slave catchup routine will send a burst of replication events per single * call. The paramter "long" control the number of events in the burst. The * short burst is intended to be used when the master receive an event and * needs to put the slave into catchup mode. This prevents the slave taking * too much tiem away from the thread that is processing the master events. * * At the end of the burst a fake EPOLLOUT event is added to the poll event * queue. This ensures that the slave callback for processing DCB write drain * will be called and future catchup requests will be handled on another thread. * * @param router The binlog router * @param slave The slave that is behind * @param large Send a long or short burst of events * @return The number of bytes written */ int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) { GWBUF *head, *record; REP_HEADER hdr; int written, rval = 1, burst; int rotating; unsigned long burst_size; uint8_t *ptr; if (large) burst = router->long_burst; else burst = router->short_burst; burst_size = router->burst_size; spinlock_acquire(&slave->catch_lock); if (slave->cstate & CS_BUSY) { spinlock_release(&slave->catch_lock); return 0; } slave->cstate |= CS_BUSY; spinlock_release(&slave->catch_lock); if (slave->file == NULL) { rotating = router->rotating; if ((slave->file = blr_open_binlog(router, slave->binlogfile)) == NULL) { if (rotating) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); return rval; } LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_slave_catchup failed to open binlog file %s", slave->binlogfile))); slave->cstate &= ~CS_BUSY; slave->state = BLRS_ERRORED; dcb_close(slave->dcb); return 0; } } slave->stats.n_bursts++; while (burst-- && burst_size > 0 && (record = blr_read_binlog(router, slave->file, slave->binlog_pos, &hdr)) != NULL) { head = gwbuf_alloc(5); ptr = GWBUF_DATA(head); encode_value(ptr, hdr.event_size + 1, 24); ptr += 3; *ptr++ = slave->seqno++; *ptr++ = 0; // OK head = gwbuf_append(head, record); if (hdr.event_type == ROTATE_EVENT) { unsigned long beat1 = hkheartbeat; blr_close_binlog(router, slave->file); if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_close_binlog took %d beats", hkheartbeat - beat1))); blr_slave_rotate(slave, GWBUF_DATA(record)); beat1 = hkheartbeat; if ((slave->file = blr_open_binlog(router, slave->binlogfile)) == NULL) { if (rotating) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); return rval; } LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_slave_catchup failed to open binlog file %s", slave->binlogfile))); slave->state = BLRS_ERRORED; dcb_close(slave->dcb); break; } if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_open_binlog took %d beats", hkheartbeat - beat1))); } slave->stats.n_bytes += gwbuf_length(head); written = slave->dcb->func.write(slave->dcb, head); if (written && hdr.event_type != ROTATE_EVENT) { slave->binlog_pos = hdr.next_pos; } rval = written; slave->stats.n_events++; burst_size -= hdr.event_size; } if (record == NULL) slave->stats.n_failed_read++; spinlock_acquire(&slave->catch_lock); slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); if (record) { slave->stats.n_flows++; spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } else if (slave->binlog_pos == router->binlog_position && strcmp(slave->binlogfile, router->binlog_name) == 0) { int state_change = 0; spinlock_acquire(&router->binlog_lock); spinlock_acquire(&slave->catch_lock); /* * Now check again since we hold the router->binlog_lock * and slave->catch_lock. */ if (slave->binlog_pos != router->binlog_position || strcmp(slave->binlogfile, router->binlog_name) != 0) { slave->cstate &= ~CS_UPTODATE; slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); spinlock_release(&router->binlog_lock); poll_fake_write_event(slave->dcb); } else { if ((slave->cstate & CS_UPTODATE) == 0) { slave->stats.n_upd++; slave->cstate |= CS_UPTODATE; spinlock_release(&slave->catch_lock); spinlock_release(&router->binlog_lock); state_change = 1; } } if (state_change) { slave->stats.n_caughtup++; if (slave->stats.n_caughtup == 1) { LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s is up to date %s, %u.", router->service->name, slave->dcb->remote, slave->binlogfile, slave->binlog_pos))); } else if ((slave->stats.n_caughtup % 50) == 0) { LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s is up to date %s, %u.", router->service->name, slave->dcb->remote, slave->binlogfile, slave->binlog_pos))); } } } else { if (slave->binlog_pos >= blr_file_size(slave->file) && router->rotating == 0 && strcmp(router->binlog_name, slave->binlogfile) != 0 && blr_master_connected(router)) { /* We may have reached the end of file of a non-current * binlog file. * * Note if the master is rotating there is a window during * which the rotate event has been written to the old binlog * but the new binlog file has not yet been created. Therefore * we ignore these issues during the rotate processing. */ LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Slave reached end of file for binlong file %s at %u " "which is not the file currently being downloaded. " "Master binlog is %s, %lu.", slave->binlogfile, slave->binlog_pos, router->binlog_name, router->binlog_position))); if (blr_slave_fake_rotate(router, slave)) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } else { slave->state = BLRS_ERRORED; dcb_close(slave->dcb); } } else { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } } return rval; }
/** * Read event for EPOLLIN on the httpd protocol module. * * @param dcb The descriptor control block * @return */ static int httpd_read_event(DCB *dcb) { SESSION *session = dcb->session; GWBUF *buf = NULL; char *ptr, *sol; HTTPD_session *client_data = NULL; int n; // Read all the available data if ((n = dcb_read(dcb, &buf)) != -1) { client_data = dcb->data; if (client_data->saved) { buf = gwbuf_append(client_data->saved, buf); client_data->saved = NULL; } buf = gwbuf_make_contiguous(buf); ptr = GWBUF_DATA(buf); if (strncasecmp(ptr, "POST", 4)) { client_data->method = METHOD_POST; gwbuf_add_property(buf, "Method", "POST"); ptr = ptr + 4; } else if (strncasecmp(ptr, "PUT", 3)) { client_data->method = METHOD_PUT; gwbuf_add_property(buf, "Method", "PUT"); ptr = ptr + 3; } else if (strncasecmp(ptr, "GET", 3)) { client_data->method = METHOD_GET; gwbuf_add_property(buf, "Method", "GET"); ptr = ptr + 3; } else if (strncasecmp(ptr, "HEAD", 4)) { client_data->method = METHOD_HEAD; gwbuf_add_property(buf, "Method", "HEAD"); ptr = ptr + 4; } while (ptr < (char *)(buf->end) && isspace(*ptr)) ptr++; sol = ptr; while (ptr < (char *)(buf->end) && isspace(*ptr) == 0) ptr++; client_data->url = strndup(sol, ptr - sol); gwbuf_add_property(buf, "URL", client_data->url); while ((sol = httpd_nextline(buf, ptr)) != NULL && *sol != '\n' && *sol != '\r') { httpd_process_header(buf, sol, client_data); ptr = sol; } /* * We have read all the headers, or run out of data to * examine. */ if (sol == NULL) { client_data->saved = buf; return 0; } else { if (((char *)(buf->end) - sol) < client_data->request_len) { client_data->saved = buf; } else { LOGIF(LT, (skygw_log_write( LOGFILE_TRACE, "HTTPD: request %s.\n", client_data->url))); SESSION_ROUTE_QUERY(session, buf); if (client_data->url) { free(client_data->url); client_data->url = NULL; } } } } return 0; }