MaxScale/server/modules/routing/hintrouter/hintroutersession.cc
2019-06-25 10:11:55 +03:00

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: 2023-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 <maxbase/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);
}
}
}