//------------------------------------------------------------------------------ // File: QoSCmd.cc // Author: Mihai Patrascoiu - CERN //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2019 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/LayoutId.hh" #include "common/StringConversion.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/Scheduler.hh" #include "mgm/qos/QoSClass.hh" #include "mgm/qos/QoSConfig.hh" #include "QoSCmd.hh" EOSMGMNAMESPACE_BEGIN // Helper function forward declaration static int CheckValidIdentifier(const char*, eos::common::VirtualIdentity&, std::string&); //------------------------------------------------------------------------------ // Method implementing the specific behaviour of the command executed //------------------------------------------------------------------------------ eos::console::ReplyProto QoSCmd::ProcessRequest() noexcept { eos::console::ReplyProto reply; eos::console::QoSProto qos = mReqProto.qos(); const auto& subcmd = qos.subcmd_case(); bool jsonOutput = (mReqProto.format() == eos::console::RequestProto::JSON); if (subcmd == eos::console::QoSProto::kList) { ListSubcmd(qos.list(), reply, jsonOutput); } else if (subcmd == eos::console::QoSProto::kGet) { GetSubcmd(qos.get(), reply, jsonOutput); } else if (subcmd == eos::console::QoSProto::kSet) { SetSubcmd(qos.set(), reply, jsonOutput); } else { reply.set_retc(EINVAL); reply.set_std_err("error: command not supported"); } return reply; } //------------------------------------------------------------------------------ // Execute list subcommand //------------------------------------------------------------------------------ void QoSCmd::ListSubcmd(const eos::console::QoSProto_ListProto& list, eos::console::ReplyProto& reply, bool jsonOutput) { Json::StreamWriterBuilder builder; std::unique_ptr jsonwriter( builder.newStreamWriter()); std::ostringstream out; if (!gOFS->MgmQoSEnabled) { reply.set_std_err("error: QoS support is disabled"); reply.set_retc(ENOTSUP); return; } if (list.classname().empty()) { // List available QoS classes if (!jsonOutput) { if (gOFS->mQoSClassMap.empty()) { out << "No QoS classes defined"; } else { out << "Available QoS classes: ["; for (const auto& it: gOFS->mQoSClassMap) { out << " " << it.first << ","; } out.seekp(-1, std::ios_base::end); out << " ]"; } } else { Json::Value json; json["name"] = Json::arrayValue; for (const auto& it: gOFS->mQoSClassMap) { json["name"].append(it.first); } jsonwriter->write(json, &out); } } else { // List properties of the given QoS class if (!gOFS->mQoSClassMap.count(list.classname())) { reply.set_std_err("error: QoS class not found"); reply.set_retc(EINVAL); return; } auto qos = gOFS->mQoSClassMap.at(list.classname()); if (jsonOutput) { jsonwriter->write(QoSConfig::QoSClassToJson(qos), &out); } else { out << QoSConfig::QoSClassToString(qos); } } reply.set_std_out(out.str()); } //------------------------------------------------------------------------------ // Execute get subcommand //------------------------------------------------------------------------------ void QoSCmd::GetSubcmd(const eos::console::QoSProto_GetProto& get, eos::console::ReplyProto& reply, bool jsonOutput) { std::ostringstream out; std::ostringstream err; XrdOucErrInfo errInfo; std::string errmsg; std::string path; int retc = 0; path = PathFromIdentifierProto(get.identifier(), errmsg); if (!path.length()) { reply.set_std_err(errmsg); reply.set_retc(errno); return; } // Check path points to a valid entry if ((retc = CheckValidIdentifier(path.c_str(), mVid, errmsg))) { reply.set_std_err(errmsg); reply.set_retc(retc); return; } // Check for access permission if (gOFS->_access(path.c_str(), R_OK, errInfo, mVid, 0)) { err << "error: " << errInfo.getErrText(); reply.set_std_err(err.str().c_str()); reply.set_retc(errInfo.getErrInfo()); return; } // Keep a key set to avoid processing duplicates std::set qosKeys; eos::IFileMD::QoSAttrMap qosMap; for (const auto& key: get.key()) { if (key == "class") { qosKeys.insert({"current_qos", "target_qos"}); continue; } else if (key == "all") { qosKeys.clear(); break; } qosKeys.insert(key); } // Process specified keys for (const auto& key: qosKeys) { if (key == "cdmi") { eos::IFileMD::QoSAttrMap cdmiMap; if (gOFS->_qos_ls(path.c_str(), errInfo, mVid, cdmiMap, true)) { err << "error: " << errInfo.getErrText() << std::endl; retc = errInfo.getErrInfo(); continue; } qosMap.insert(cdmiMap.begin(), cdmiMap.end()); } else { XrdOucString value; if (gOFS->_qos_get(path.c_str(), errInfo, mVid, key.c_str(), value)) { err << "error: " << errInfo.getErrText() << std::endl; retc = errInfo.getErrInfo(); continue; } qosMap[key] = value.c_str(); } } // No keys specified -- extract all if (qosKeys.empty()) { if (gOFS->_qos_ls(path.c_str(), errInfo, mVid, qosMap)) { err << "error: " << errInfo.getErrText() << std::endl; retc = errInfo.getErrInfo(); } } // Avoid showing an empty target QoS field if ((qosMap.count("target_qos")) && (qosMap.at("target_qos") == "null")) { qosMap.erase("target_qos"); } // Format QoS properties map to desired output out << (jsonOutput ? MapToJSONOutput(qosMap) : MapToDefaultOutput(qosMap)); reply.set_retc(retc); reply.set_std_out(out.str()); reply.set_std_err(err.str()); } //------------------------------------------------------------------------------ // Execute set subcommand //------------------------------------------------------------------------------ void QoSCmd::SetSubcmd(const eos::console::QoSProto_SetProto& set, eos::console::ReplyProto& reply, bool jsonOutput) { using eos::common::LayoutId; std::ostringstream out; std::ostringstream err; XrdOucErrInfo errInfo; std::string errmsg; std::string path; int retc = 0; path = PathFromIdentifierProto(set.identifier(), errmsg); if (!path.length()) { reply.set_std_err(errmsg); reply.set_retc(errno); return; } // Check path points to a valid entry if ((retc = CheckValidIdentifier(path.c_str(), mVid, errmsg))) { reply.set_std_err(errmsg); reply.set_retc(retc); return; } if (!gOFS->MgmQoSEnabled) { reply.set_std_err("error: QoS support is disabled"); reply.set_retc(ENOTSUP); return; } if (!gOFS->mQoSClassMap.count(set.classname())) { reply.set_std_err(SSTR("error: unrecognized QoS class name '" << set.classname() << "'")); reply.set_retc(EINVAL); return; } auto qos = gOFS->mQoSClassMap.at(set.classname()); std::string conversion_id = ""; if (gOFS->_qos_set(path.c_str(), errInfo, mVid, qos, conversion_id)) { err << "error: " << errInfo.getErrText() << std::endl; retc = errInfo.getErrInfo(); } if (!retc && !jsonOutput) { out << "scheduled QoS conversion job: " << conversion_id; } else if (jsonOutput) { Json::Value jsonOut; jsonOut["retc"] = (Json::Value::UInt64) retc; jsonOut["conversionid"] = (retc) ? "null" : conversion_id; out << jsonOut; } reply.set_retc(retc); reply.set_std_out(out.str()); reply.set_std_err(err.str()); } //------------------------------------------------------------------------------ // Translate the identifier proto into a namespace path //------------------------------------------------------------------------------ std::string QoSCmd::PathFromIdentifierProto( const eos::console::QoSProto_IdentifierProto& identifier, std::string& err_msg) { using eos::console::QoSProto; const auto& type = identifier.Identifier_case(); std::string path = ""; if (type == QoSProto::IdentifierProto::kPath) { path = identifier.path().c_str(); } else if (type == QoSProto::IdentifierProto::kFileId) { GetPathFromFid(path, identifier.fileid(), err_msg); } else if (type == QoSProto::IdentifierProto::kContainerId) { GetPathFromCid(path, identifier.containerid(), err_msg); } else { err_msg = "error: received empty string path"; } return path; } //------------------------------------------------------------------------------ // Process a QoS properties map into a default printable output //------------------------------------------------------------------------------ std::string QoSCmd::MapToDefaultOutput(const eos::IFileMD::QoSAttrMap& map) { std::ostringstream out; for (const auto& it: map) { out << it.first << "=" << it.second << std::endl; } return out.str(); } //------------------------------------------------------------------------------ // Process a QoS properties map into a default printable output //------------------------------------------------------------------------------ std::string QoSCmd::MapToJSONOutput(const eos::IFileMD::QoSAttrMap& map) { static std::set storage_attributes = { "checksum", "layout", "replica", "placement" }; Json::Value jsonOut, jsonCDMI, jsonAttributes; // Parse the placement array string into JSON Array // Format: [ location, location, ... ] auto parsePlacementArray = [](std::string splacement) -> Json::Value { Json::Value placement = Json::arrayValue; size_t start = splacement.find("["); size_t end = splacement.find("]"); if ((start == std::string::npos) || (end == std::string::npos)) { return placement; } std::vector locations; splacement = splacement.substr(start + 1, end - 1).c_str(); eos::common::StringConversion::ReplaceStringInPlace(splacement, ",", " "); eos::common::StringConversion::Tokenize(splacement, locations); for (const auto& location: locations) { placement.append(location); } return placement; }; for (const auto& it: map) { XrdOucString key = it.first.c_str(); if (key.beginswith("cdmi_")) { if (key == CDMI_PLACEMENT_TAG) { jsonCDMI[CDMI_PLACEMENT_TAG] = parsePlacementArray(it.second); } else { jsonCDMI[it.first] = it.second; } } else if (storage_attributes.count(it.first)) { jsonAttributes[it.first] = it.second; } else { jsonOut[it.first] = it.second; } } if (!jsonAttributes.empty()) { jsonOut["attributes"] = jsonAttributes; } if (!jsonCDMI.empty()) { jsonOut["metadata"] = jsonCDMI; } Json::StreamWriterBuilder builder; std::unique_ptr jsonwriter( builder.newStreamWriter()); std::stringstream ss; jsonwriter->write(jsonOut, &ss); return ss.str(); } //------------------------------------------------------------------------------ //! Check that the given path points to a valid entry. //! //! @param path the path to check //! @param vid virtual identity of the client //! @param err_msg string to place error message //! //! @return 0 if path points to file, error code otherwise //------------------------------------------------------------------------------ static int CheckValidIdentifier(const char* path, eos::common::VirtualIdentity& vid, std::string& err_msg) { XrdSfsFileExistence fileExists; XrdOucErrInfo errInfo; // Check for path existence if (gOFS->_exists(path, fileExists, errInfo, vid)) { err_msg = "error: unable to check for path existence"; return errInfo.getErrInfo(); } if (fileExists == XrdSfsFileExistNo) { err_msg = "error: path does not point to a valid entry"; return EINVAL; } else if ((fileExists != XrdSfsFileExistIsFile) && (fileExists != XrdSfsFileExistIsDirectory)) { err_msg = "error: path does not point to a file or container"; return EINVAL; } return 0; } EOSMGMNAMESPACE_END