412 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			412 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2016 MariaDB Corporation Ab
 | 
						|
 *
 | 
						|
 * Use of this software is governed by the Business Source License included
 | 
						|
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | 
						|
 *
 | 
						|
 * Change Date: 2022-01-01
 | 
						|
 *
 | 
						|
 * On the date above, in accordance with the Business Source License, use
 | 
						|
 * of this software will be governed by version 2 or later of the General
 | 
						|
 * Public License.
 | 
						|
 */
 | 
						|
 | 
						|
#define MXS_MODULE_NAME "hintrouter"
 | 
						|
#include "hintroutersession.hh"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <functional>
 | 
						|
 | 
						|
#include <maxscale/alloc.h>
 | 
						|
#include "hintrouter.hh"
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
/**
 | 
						|
 * Writer is a function object that writes a clone of a provided GWBUF,
 | 
						|
 * to each dcb it is called with.
 | 
						|
 */
 | 
						|
class Writer : std::unary_function<HintRouterSession::MapElement, bool>
 | 
						|
{
 | 
						|
public:
 | 
						|
    Writer(GWBUF* pPacket)
 | 
						|
        : m_pPacket(pPacket)
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    bool operator()(HintRouterSession::MapElement& elem)
 | 
						|
    {
 | 
						|
        bool rv = false;
 | 
						|
        Dcb& dcb = elem.second;
 | 
						|
        GWBUF* pPacket = gwbuf_clone(m_pPacket);
 | 
						|
 | 
						|
        if (pPacket)
 | 
						|
        {
 | 
						|
            SERVER* pServer = dcb.server();
 | 
						|
            HR_DEBUG("Writing packet to %p %s.", dcb.get(), pServer ? pserver->name() : "(null)");
 | 
						|
            rv = dcb.write(pPacket);
 | 
						|
        }
 | 
						|
        return rv;
 | 
						|
    }
 | 
						|
 | 
						|
private:
 | 
						|
    GWBUF* m_pPacket;
 | 
						|
};
 | 
						|
}
 | 
						|
 | 
						|
HintRouterSession::HintRouterSession(MXS_SESSION* pSession,
 | 
						|
                                     HintRouter* pRouter,
 | 
						|
                                     const BackendMap& backends)
 | 
						|
    : maxscale::RouterSession(pSession)
 | 
						|
    , m_router(pRouter)
 | 
						|
    , m_backends(backends)
 | 
						|
    , m_master(NULL)
 | 
						|
    , m_n_routed_to_slave(0)
 | 
						|
    , m_surplus_replies(0)
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
    update_connections();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
HintRouterSession::~HintRouterSession()
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void HintRouterSession::close()
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
    m_master = Dcb(NULL);
 | 
						|
    m_slaves.clear();
 | 
						|
    m_backends.clear();
 | 
						|
}
 | 
						|
 | 
						|
int32_t HintRouterSession::routeQuery(GWBUF* pPacket)
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
 | 
						|
    bool success = false;
 | 
						|
 | 
						|
    if (pPacket->hint)
 | 
						|
    {
 | 
						|
        /* At least one hint => look for match. Only use the later hints if the
 | 
						|
         * first is unsuccessful. */
 | 
						|
        HINT* current_hint = pPacket->hint;
 | 
						|
        HR_DEBUG("Hint, looking for match.");
 | 
						|
        while (!success && current_hint)
 | 
						|
        {
 | 
						|
            success = route_by_hint(pPacket, current_hint, false);
 | 
						|
            if (!success)
 | 
						|
            {
 | 
						|
                current_hint = current_hint->next;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!success)
 | 
						|
    {
 | 
						|
        HR_DEBUG("No hints or hint-based routing failed, falling back to default action.");
 | 
						|
        HINT default_hint = {};
 | 
						|
        default_hint.type = m_router->get_default_action();
 | 
						|
        if (default_hint.type == HINT_ROUTE_TO_NAMED_SERVER)
 | 
						|
        {
 | 
						|
            default_hint.data = MXS_STRDUP(m_router->get_default_server().c_str());
 | 
						|
            // Ignore allocation error, it will just result in an error later on
 | 
						|
        }
 | 
						|
        success = route_by_hint(pPacket, &default_hint, true);
 | 
						|
        if (default_hint.type == HINT_ROUTE_TO_NAMED_SERVER)
 | 
						|
        {
 | 
						|
            MXS_FREE(default_hint.data);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!success)
 | 
						|
    {
 | 
						|
        gwbuf_free(pPacket);
 | 
						|
    }
 | 
						|
    return success;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void HintRouterSession::clientReply(GWBUF* pPacket, DCB* pBackend)
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
 | 
						|
    SERVER* pServer = pBackend->server;
 | 
						|
 | 
						|
    if (m_surplus_replies == 0)
 | 
						|
    {
 | 
						|
        HR_DEBUG("Returning packet from %s.", pServer ? pserver->name() : "(null)");
 | 
						|
 | 
						|
        MXS_SESSION_ROUTE_REPLY(pBackend->session, pPacket);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        HR_DEBUG("Ignoring reply packet from %s.", pServer ? pserver->name() : "(null)");
 | 
						|
 | 
						|
        --m_surplus_replies;
 | 
						|
        gwbuf_free(pPacket);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void HintRouterSession::handleError(GWBUF* pMessage,
 | 
						|
                                    DCB* pProblem,
 | 
						|
                                    mxs_error_action_t action,
 | 
						|
                                    bool* pSuccess)
 | 
						|
{
 | 
						|
    HR_ENTRY();
 | 
						|
 | 
						|
    mxb_assert(pProblem->role == DCB::Role::BACKEND);
 | 
						|
 | 
						|
    MXS_SESSION* pSession = pProblem->session;
 | 
						|
    mxs_session_state_t sesstate = pSession->state;
 | 
						|
 | 
						|
    switch (action)
 | 
						|
    {
 | 
						|
    case ERRACT_REPLY_CLIENT:
 | 
						|
        {
 | 
						|
            /* React to failed authentication, send message to client */
 | 
						|
            if (sesstate == SESSION_STATE_STARTED)
 | 
						|
            {
 | 
						|
                /* Send error report to client */
 | 
						|
                GWBUF* pCopy = gwbuf_clone(pMessage);
 | 
						|
                if (pCopy)
 | 
						|
                {
 | 
						|
                    DCB* pClient = pSession->client_dcb;
 | 
						|
                    pClient->func.write(pClient, pCopy);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            *pSuccess = false;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case ERRACT_NEW_CONNECTION:
 | 
						|
        {
 | 
						|
            HR_DEBUG("ERRACT_NEW_CONNECTION");
 | 
						|
            *pSuccess = true;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        mxb_assert(!true);
 | 
						|
        *pSuccess = false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
bool HintRouterSession::route_by_hint(GWBUF* pPacket, HINT* hint, bool print_errors)
 | 
						|
{
 | 
						|
    bool success = false;
 | 
						|
    switch (hint->type)
 | 
						|
    {
 | 
						|
    case HINT_ROUTE_TO_MASTER:
 | 
						|
        {
 | 
						|
            bool master_ok = false;
 | 
						|
            // The master server should be already known, but may have changed
 | 
						|
            if (m_master.get() && m_master.server()->is_master())
 | 
						|
            {
 | 
						|
                master_ok = true;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                update_connections();
 | 
						|
                if (m_master.get())
 | 
						|
                {
 | 
						|
                    master_ok = true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            if (master_ok)
 | 
						|
            {
 | 
						|
                HR_DEBUG("Writing packet to master: '%s'.", m_master.server()->name());
 | 
						|
                success = m_master.write(pPacket);
 | 
						|
                if (success)
 | 
						|
                {
 | 
						|
                    m_router->m_routed_to_master++;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    HR_DEBUG("Write to master failed.");
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (print_errors)
 | 
						|
            {
 | 
						|
                MXS_ERROR("Hint suggests routing to master when no master connected.");
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case HINT_ROUTE_TO_SLAVE:
 | 
						|
        success = route_to_slave(pPacket, print_errors);
 | 
						|
        break;
 | 
						|
 | 
						|
    case HINT_ROUTE_TO_NAMED_SERVER:
 | 
						|
        {
 | 
						|
            string backend_name((hint->data) ? (const char*)(hint->data) : "");
 | 
						|
            BackendMap::const_iterator iter = m_backends.find(backend_name);
 | 
						|
            if (iter != m_backends.end())
 | 
						|
            {
 | 
						|
                HR_DEBUG("Writing packet to %s.", iter->second.server()->name());
 | 
						|
                success = iter->second.write(pPacket);
 | 
						|
                if (success)
 | 
						|
                {
 | 
						|
                    m_router->m_routed_to_named++;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    HR_DEBUG("Write failed.");
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (print_errors)
 | 
						|
            {
 | 
						|
                /* This shouldn't be possible with current setup as server names are
 | 
						|
                 * checked on startup. With a different filter and the 'print_errors'
 | 
						|
                 * on for the first call this is possible. */
 | 
						|
                MXS_ERROR("Hint suggests routing to backend '%s' when no such backend connected.",
 | 
						|
                          backend_name.c_str());
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case HINT_ROUTE_TO_ALL:
 | 
						|
        {
 | 
						|
            HR_DEBUG("Writing packet to %lu backends.", m_backends.size());
 | 
						|
            BackendMap::size_type n_writes =
 | 
						|
                std::count_if(m_backends.begin(), m_backends.end(), Writer(pPacket));
 | 
						|
            if (n_writes != 0)
 | 
						|
            {
 | 
						|
                m_surplus_replies = n_writes - 1;
 | 
						|
            }
 | 
						|
            BackendMap::size_type size = m_backends.size();
 | 
						|
            success = (n_writes == size);
 | 
						|
            if (success)
 | 
						|
            {
 | 
						|
                gwbuf_free(pPacket);
 | 
						|
                m_router->m_routed_to_all++;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                HR_DEBUG("Write to all failed.");
 | 
						|
                if (print_errors)
 | 
						|
                {
 | 
						|
                    MXS_ERROR("Write failed for '%lu' out of '%lu' backends.",
 | 
						|
                              (size - n_writes),
 | 
						|
                              size);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        MXS_ERROR("Unsupported hint type '%d'", hint->type);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
    return success;
 | 
						|
}
 | 
						|
 | 
						|
bool HintRouterSession::route_to_slave(GWBUF* pPacket, bool print_errors)
 | 
						|
{
 | 
						|
    bool success = false;
 | 
						|
    // Find a valid slave
 | 
						|
    size_type size = m_slaves.size();
 | 
						|
    if (size)
 | 
						|
    {
 | 
						|
        size_type begin = m_n_routed_to_slave % size;
 | 
						|
        size_type limit = begin + size;
 | 
						|
        for (size_type curr = begin; curr != limit; curr++)
 | 
						|
        {
 | 
						|
            Dcb& candidate = m_slaves.at(curr % size);
 | 
						|
            if (candidate.server()->is_slave())
 | 
						|
            {
 | 
						|
                HR_DEBUG("Writing packet to slave: '%s'.", candidate.server()->name());
 | 
						|
                success = candidate.write(pPacket);
 | 
						|
                if (success)
 | 
						|
                {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    HR_DEBUG("Write to slave failed.");
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* It is (in theory) possible, that none of the slaves in the slave-array are
 | 
						|
     * working (or have been promoted to master) and the previous master is now
 | 
						|
     * a slave. In this situation, re-arranging the dcb:s will help. */
 | 
						|
    if (!success)
 | 
						|
    {
 | 
						|
        update_connections();
 | 
						|
        size = m_slaves.size();
 | 
						|
        if (size)
 | 
						|
        {
 | 
						|
            size_type begin = m_n_routed_to_slave % size;
 | 
						|
            size_type limit = begin + size;
 | 
						|
            for (size_type curr = begin; curr != limit; curr++)
 | 
						|
            {
 | 
						|
                Dcb& candidate = m_slaves.at(curr % size);
 | 
						|
                HR_DEBUG("Writing packet to slave: '%s'.", candidate.server()->name());
 | 
						|
                success = candidate.write(pPacket);
 | 
						|
                if (success)
 | 
						|
                {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    HR_DEBUG("Write to slave failed.");
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (success)
 | 
						|
    {
 | 
						|
        m_router->m_routed_to_slave++;
 | 
						|
        m_n_routed_to_slave++;
 | 
						|
    }
 | 
						|
    else if (print_errors)
 | 
						|
    {
 | 
						|
        if (!size)
 | 
						|
        {
 | 
						|
            MXS_ERROR("Hint suggests routing to slave when no slaves found.");
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            MXS_ERROR("Could not write to any of '%lu' slaves.", size);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return success;
 | 
						|
}
 | 
						|
 | 
						|
void HintRouterSession::update_connections()
 | 
						|
{
 | 
						|
    /* Attempt to rearrange the dcb:s in the session such that the master and
 | 
						|
     * slave containers are correct again. Do not try to make new connections,
 | 
						|
     * since those would not have the correct session state anyways. */
 | 
						|
    m_master = Dcb(NULL);
 | 
						|
    m_slaves.clear();
 | 
						|
 | 
						|
    for (BackendMap::const_iterator iter = m_backends.begin();
 | 
						|
         iter != m_backends.end(); iter++)
 | 
						|
    {
 | 
						|
        SERVER* server = iter->second.get()->server;
 | 
						|
        if (server->is_master())
 | 
						|
        {
 | 
						|
            if (!m_master.get())
 | 
						|
            {
 | 
						|
                m_master = iter->second;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                MXS_WARNING("Found multiple master servers when updating connections.");
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (server->is_slave())
 | 
						|
        {
 | 
						|
            m_slaves.push_back(iter->second);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |