// ----------------------------------------------------------------------
// File: proc/user/Accounting.cc
// Author: Jozsef Makai - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2017 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 "common/StringTokenizer.hh"
#include "common/ExpiryCache.hh"
#include "common/Logging.hh"
#include "mgm/proc/ProcInterface.hh"
#include "mgm/proc/ProcCommand.hh"
#include "mgm/XrdMgmOfs.hh"
#include "mgm/Quota.hh"
#include "json/json.h"
EOSMGMNAMESPACE_BEGIN
int
ProcCommand::Accounting()
{
static eos::common::ExpiryCache accountingCache(
std::chrono::seconds(600));
static const auto generateAccountingJson = [](
eos::common::VirtualIdentity & vid) {
static const auto processAccountingAttribute = [](
std::pair attr, Json::Value & storageShare) {
static auto accountingAttrPrefix = "sys.accounting";
if (attr.first.find(accountingAttrPrefix) == 0) {
auto objectPath =
eos::common::StringTokenizer::split>(attr.first, '.');
Json::Value* keyEndpointPtr = nullptr;
// Creating a new json object following the path in this case
if (objectPath.size() > 3) {
// we keep a pointer to the previous json object along the path
auto* prevObject = &(storageShare[objectPath[2]]);
for (size_t i = 3; i < objectPath.size(); ++i) {
try {
auto number = std::stoul(objectPath[i]);
prevObject = &((*prevObject)[Json::ArrayIndex(number)]);
} catch (std::invalid_argument& err) {
prevObject = &((*prevObject)[objectPath[i]]);
}
}
// finally set the value of the attribute
keyEndpointPtr = prevObject;
} else {
keyEndpointPtr = &(storageShare[objectPath[objectPath.size() - 1]]);
}
// This value is interpreted as a list of elements separated by comma
if (attr.second.find(',') != std::string::npos) {
auto attrs = eos::common::StringTokenizer::split>
(attr.second, ',');
for (auto && attrValue : attrs) {
(*keyEndpointPtr).append(attrValue);
}
} else {
*keyEndpointPtr = attr.second;
}
}
};
Json::Value root;
Json::Value storageShare;
eos::IContainerMD::XAttrMap attributes;
XrdOucErrInfo errInfo;
// start with extended attributes so they can't overwrite fields
gOFS->_attr_ls(gOFS->MgmProcPath.c_str(), errInfo, vid, nullptr,
attributes);
for (const auto& attr : attributes) {
processAccountingAttribute(attr, storageShare);
}
for (const auto& member : storageShare.getMemberNames()) {
root["storageservice"][member] = storageShare[member];
}
root["storageservice"]["name"] = gOFS->MgmOfsInstanceName.c_str();
std::ostringstream version;
version << VERSION << "-" << RELEASE;
root["storageservice"]["implementation"] = "EOS";
root["storageservice"]["implementationversion"] = version.str().c_str();
root["storageservice"]["latestupdate"] = Json::Int64{std::time(nullptr)};
auto capacityOnline = Json::UInt64{0};
auto usedOnline = Json::UInt64{0};
for (const auto& quota : Quota::GetAllGroupsLogicalQuotaValues()) {
storageShare.clear();
attributes.clear();
errInfo.clear();
gOFS->_attr_ls(quota.first.c_str(), errInfo, vid, nullptr, attributes);
for (const auto& attr : attributes) {
processAccountingAttribute(attr, storageShare);
}
auto usedSizeofShare = Json::UInt64{std::get<0>(quota.second)};
auto totalSizeofShare = Json::UInt64{std::get<1>(quota.second)};
capacityOnline += totalSizeofShare;
usedOnline += usedSizeofShare;
storageShare["path"].append(quota.first);
storageShare["usedsize"] = usedSizeofShare;
storageShare["totalsize"] = totalSizeofShare;
storageShare["numberoffiles"] = Json::UInt64{std::get<2>(quota.second)};
storageShare["timestamp"] = Json::Int64{std::time(nullptr)};
root["storageservice"]["storageshares"].append(storageShare);
}
root["storageservice"]["storagecapacity"]["online"]["totalsize"] =
capacityOnline;
root["storageservice"]["storagecapacity"]["online"]["usedsize"] = usedOnline;
root["storageservice"]["storagecapacity"]["offline"]["totalsize"] = Json::UInt64{0};
root["storageservice"]["storagecapacity"]["offline"]["usedsize"] = Json::UInt64{0};
return new std::string(SSTR(root));
};
retc = SFS_OK;
if (mSubCmd == "config") {
if (!pVid->sudoer) {
stdErr += "error: only sudoers are allowed to change cache configuration";
retc = EPERM;
return retc;
}
if (pOpaque->Get("mgm.accounting.expired")) {
try {
auto minutes = std::stoi(pOpaque->Get("mgm.accounting.expired"));
accountingCache.SetExpiredAfter(
std::chrono::duration_cast(
std::chrono::minutes(std::max(minutes, 1))
)
);
stdOut += "success: expired time frame set to ";
stdOut += minutes;
stdOut += "\n";
} catch (std::invalid_argument& err) {
stdErr += "error: provided number is not configurable";
retc = EINVAL;
}
}
if (pOpaque->Get("mgm.accounting.invalid")) {
try {
auto minutes = std::stoi(pOpaque->Get("mgm.accounting.invalid"));
accountingCache.SetInvalidAfter(
std::chrono::duration_cast(
std::chrono::minutes(std::max(minutes, 5))
)
);
stdOut += "success: invalid time frame set to ";
stdOut += minutes;
stdOut += "\n";
} catch (std::invalid_argument& err) {
stdErr += "error: provided number is not configurable";
retc = EINVAL;
}
}
} else if (mSubCmd == "report") {
auto options = std::string(pOpaque->Get("mgm.option") ?
pOpaque->Get("mgm.option") : "");
bool forceUpdate = options.find('f') != std::string::npos;
try {
auto json = accountingCache.getCachedObject(forceUpdate, generateAccountingJson,
std::ref(*pVid));
stdOut += json.c_str();
} catch (eos::common::UpdateException& err) {
stdErr += err.what();
retc = EAGAIN;
}
} else {
stdErr += "error: command is not supported";
retc = ENOTSUP;
}
return retc;
}
EOSMGMNAMESPACE_END