//------------------------------------------------------------------------------
//! @file PathRouting.cc
//------------------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2018 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "mgm/PathRouting.hh"
#include "common/Path.hh"
#include "common/StringConversion.hh"
#include "XrdCl/XrdClURL.hh"
#include
EOSMGMNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
PathRouting::~PathRouting()
{
mThread.join();
}
//------------------------------------------------------------------------------
// Clear the routing table
//------------------------------------------------------------------------------
void
PathRouting::Clear()
{
eos::common::RWMutexWriteLock lock(mPathRouteMutex);
mPathRoute.clear();
}
//------------------------------------------------------------------------------
// Add path endpoint pair to the routing table
//------------------------------------------------------------------------------
bool
PathRouting::Add(const std::string& path, RouteEndpoint&& endpoint)
{
std::string string_rep = endpoint.ToString();
eos::common::RWMutexWriteLock route_wr_lock(mPathRouteMutex);
auto it = mPathRoute.find(path);
if (it == mPathRoute.end()) {
auto it_emplace = mPathRoute.emplace(path, std::list());
it_emplace.first->second.emplace_back(std::move(endpoint));
} else {
bool found = false;
for (const auto& ep : it->second) {
if (ep == endpoint) {
found = true;
break;
}
}
if (found) {
return false;
}
it->second.emplace_back(std::move(endpoint));
}
eos_debug("added route %s => %s", path.c_str(), string_rep.c_str());
return true;
}
//------------------------------------------------------------------------------
// Remove routing for the corresponding path
//------------------------------------------------------------------------------
bool
PathRouting::Remove(const std::string& path)
{
eos::common::RWMutexWriteLock route_wr_lock(mPathRouteMutex);
auto it = mPathRoute.find(path);
if (path.empty() || (it == mPathRoute.end())) {
return false;
}
mPathRoute.erase(it);
return true;
}
//------------------------------------------------------------------------------
// Route a path according to the configured routing table
//------------------------------------------------------------------------------
PathRouting::Status
PathRouting::Reroute(const char* inpath, const char* ininfo,
eos::common::VirtualIdentity& vid,
std::string& host, int& port, std::string& stat_info)
{
// Process and extract the path for which we need to do the routing
std::string path = (inpath ? inpath : "");
std::string surl = path;
if (ininfo) {
surl += "?";
surl += ininfo;
}
XrdCl::URL url(surl);
XrdCl::URL::ParamsMap param = url.GetParams();
// If there is a routing tag in the CGI, we use that one to map. Also note the
// tags have priorities.
std::list tags {"eos.route", "mgm.path", "mgm.quota.space"};
for (const auto& tag : tags) {
auto it = param.find(tag);
if (it != param.end() && it->second.length()) {
path = it->second;
break;
}
}
// Make sure path is not empty and is '/' terminated
if (path.empty()) {
eos_debug("%s", "msg=\"input path is empty\"");
return Status::NOROUTING;
}
path = eos::common::StringConversion::curl_unescaped(path.c_str()).c_str();
eos::common::Path cPath(path.c_str());
path = cPath.GetPath();
// Make sure path is / terminated
if (path.back() != '/') {
path += '/';
}
eos_debug("path=%s map_route_size=%d", path.c_str(), mPathRoute.size());
eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);
if (mPathRoute.empty()) {
eos_debug("%s", "msg=\"no routes defined\"");
return Status::NOROUTING;
}
auto it = mPathRoute.find(path);
if (it == mPathRoute.end()) {
// Try to find the longest possible match
if (!cPath.GetSubPathSize()) {
eos_debug("path=%s has no subpath", path.c_str());
return Status::NOROUTING;
}
for (size_t i = cPath.GetSubPathSize() - 1; i > 0; i--) {
eos_debug("[route] %s => %s\n", path.c_str(), cPath.GetSubPath(i));
it = mPathRoute.find(cPath.GetSubPath(i));
if (it != mPathRoute.end()) {
break;
}
}
// If no match found then return
if (it == mPathRoute.end()) {
return Status::NOROUTING;
}
}
std::ostringstream oss;
oss << "Rt:";
// Try to find the master endpoint, if none exists then just redirect to the
// first endpoint in the list if reachable
auto* master_ep = &it->second.front();
for (auto& endpoint : it->second) {
if (endpoint.mIsOnline.load() && endpoint.mIsMaster.load()) {
master_ep = &endpoint;
break;
}
}
if (!master_ep->mIsOnline.load()) {
eos_warning("msg=\"no online endpoints for route\" path=%s", it->first.c_str());
return Status::STALL;
}
// Http redirection
if (vid.prot == "http" || vid.prot == "https") {
port = master_ep->GetHttpPort();
oss << vid.prot.c_str();
} else {
// XRootD redirection
port = master_ep->GetXrdPort();
oss << "xrd";
}
host = master_ep->GetHostname();
oss << ":" << host;
stat_info = oss.str();
eos_debug("re-routing path=%s using match_path=%s to host=%s port=%d",
path.c_str(), it->first.c_str(), host.c_str(), port);
return Status::REROUTE;
}
//------------------------------------------------------------------------------
// Get routes listing
//------------------------------------------------------------------------------
bool
PathRouting::GetListing(const std::string& path, std::string& out) const
{
std::ostringstream oss;
eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);
auto printRoute =
[&oss](const std::pair>& route) {
bool first = true;
oss << route.first << " => ";
for (const auto& endpoint : route.second) {
if (!first) {
oss << ",";
}
if (!endpoint.mIsOnline.load()) {
oss << "_";
} else if (endpoint.mIsMaster.load()) {
oss << "*";
}
oss << endpoint.ToString();
first = false;
}
oss << std::endl;
};
// List all paths
if (path.empty()) {
for (const auto& elem : mPathRoute) {
printRoute(elem);
}
} else {
auto it = mPathRoute.find(path);
if (it == mPathRoute.end()) {
return false;
}
printRoute(*it);
}
out = oss.str();
return true;
}
//------------------------------------------------------------------------------
// Method executed by an async thread which is updating the current master
// endpoint for each routing
//------------------------------------------------------------------------------
void
PathRouting::UpdateEndpointsStatus(ThreadAssistant& assistant) noexcept
{
while (!assistant.terminationRequested()) {
{
eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);
for (auto& route : mPathRoute) {
int num_masters = 0;
eos_debug("checking route='%s'", route.first.c_str());
for (auto& endpoint : route.second) {
endpoint.UpdateStatus();
if (endpoint.mIsOnline.load() && endpoint.mIsMaster.load()) {
++num_masters;
}
}
// There is smth awfully wrong if we have more than two masters ...
if (num_masters >= 2) {
eos_warning("there is more than one master for route path=%s",
route.first.c_str());
// Mark them all as offline so that we stall the clients
for (auto& endpoint : route.second) {
endpoint.mIsOnline.store(false);
endpoint.mIsMaster.store(false);
}
}
}
}
assistant.wait_for(mTimeout);
}
}
EOSMGMNAMESPACE_END