/** * 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; }
/** * 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; }