//------------------------------------------------------------------------------
// File: EvictCmd.cc
// Author: Jozsef Makai - CERN
//------------------------------------------------------------------------------
/************************************************************************
* 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 "common/Path.hh"
#include "common/Timing.hh"
#include "EvictCmd.hh"
#include "mgm/XrdMgmOfs.hh"
#include "mgm/EosCtaReporter.hh"
#include "mgm/Acl.hh"
#include "common/Constants.hh"
#include "namespace/interface/IView.hh"
#include
EOSMGMNAMESPACE_BEGIN
eos::console::RequestProto eos::mgm::EvictCmd::convertStagerRmToEvict(
const eos::console::RequestProto& req, std::ostringstream& errStream,
int& ret_c)
{
struct timespec ts_now;
ts_now.tv_sec = 0;
ts_now.tv_nsec = 0;
eos::console::RequestProto new_req;
auto req_evict = new_req.mutable_evict();
auto req_stagerrm = req.stagerrm();
for (int i = 0; i < req_stagerrm.file_size(); i++) {
const auto& file_stagerrm = req_stagerrm.file(i);
switch (file_stagerrm.File_case()) {
case eos::console::StagerRmProto::FileProto::kPath:
req_evict->add_file()->set_path(file_stagerrm.path());
break;
case eos::console::StagerRmProto::FileProto::kFid:
req_evict->add_file()->set_fid(file_stagerrm.fid());
break;
default:
errStream <<
"error: Received a file with neither a path nor an fid, unable to convert stagerrm request to evict request"
<<
std::endl;
ret_c = EINVAL;
EosCtaReporterEvict eosLog;
eosLog
.addParam(EosCtaReportParam::SEC_APP, "tape_evict")
.addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))
.addParam(EosCtaReportParam::RUID, mVid.uid)
.addParam(EosCtaReportParam::RGID, mVid.gid)
.addParam(EosCtaReportParam::TD, mVid.tident.c_str())
.addParam(EosCtaReportParam::TS, ts_now.tv_sec)
.addParam(EosCtaReportParam::TNS, ts_now.tv_nsec)
.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
break;
}
}
return new_req;
}
eos::console::ReplyProto
eos::mgm::EvictCmd::ProcessRequest() noexcept
{
eos::console::ReplyProto reply;
std::ostringstream errStream;
std::ostringstream outStream;
bool allReplicasRemoved = false;
int ret_c = 0;
// TODO: Remove this segment of code when the StagerRm command is deprecated, and replace by the line bellow
eos::console::RequestProto req;
if (mReqProto.command_case() == eos::console::RequestProto::kStagerRm) {
req = convertStagerRmToEvict(mReqProto, errStream, ret_c);
} else {
req = mReqProto;
}
const auto& evict = req.evict();
// TODO: Replace the code removed above by this line
// const auto& evict = mReqProto.evict();
XrdOucErrInfo errInfo;
eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();
struct timespec ts_now;
eos::common::Timing::GetTimeSpec(ts_now);
std::optional fsid =
evict.has_evictsinglereplica() ? std::optional(
evict.evictsinglereplica().fsid()) : std::nullopt;
bool ignoreEvictCounter = evict.ignoreevictcounter();
bool ignoreRemovalOnFst = evict.ignoreremovalonfst();
if (fsid.has_value() && !ignoreEvictCounter) {
reply.set_retc(EINVAL);
errStream << "error: Parameter 'fsid' can only be used with 'ignore-evict-counter'" <<
std::endl;
reply.set_std_err(errStream.str());
reply.set_std_out(outStream.str());
return reply;
}
if(ignoreRemovalOnFst && !fsid.has_value()) {
reply.set_retc(EINVAL);
errStream << "error: Parameter 'ignore-removal-on-fst' can only be used with 'fsid'" <<
std::endl;
reply.set_std_err(errStream.str());
reply.set_std_out(outStream.str());
return reply;
}
int count_some_disk_replicas_removed = 0;
int count_all_disk_replicas_removed = 0;
int count_evict_counter_not_zero = 0;
for (int i = 0; i < evict.file_size(); i++) {
EosCtaReporterEvict eosLog;
eosLog
.addParam(EosCtaReportParam::SEC_APP, "tape_evict")
.addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))
.addParam(EosCtaReportParam::RUID, mVid.uid)
.addParam(EosCtaReportParam::RGID, mVid.gid)
.addParam(EosCtaReportParam::TD, mVid.tident.c_str())
.addParam(EosCtaReportParam::TS, ts_now.tv_sec)
.addParam(EosCtaReportParam::TNS, ts_now.tv_nsec);
const auto& file = evict.file(i);
std::string path;
std::string err;
switch (file.File_case()) {
case eos::console::EvictProto::FileProto::kPath:
path = file.path();
if (0 == path.length()) {
errStream << "error: Received an empty string path" << std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
eosLog.addParam(EosCtaReportParam::PATH, path);
break;
case eos::console::EvictProto::FileProto::kFid:
GetPathFromFid(path, file.fid(), err);
if (0 == path.length()) {
errStream << "error: Received an unknown fid: value=" << file.fid() <<
std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
eosLog.addParam(EosCtaReportParam::PATH, path);
break;
default:
errStream << "error: Received a file with neither a path nor an fid" <<
std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
// check that we have the correct permission
eos::common::Path cPath(path.c_str());
errInfo.clear();
if (gOFS->_access(cPath.GetParentPath(), P_OK, errInfo, mVid, "") != 0) {
errStream << "error: you don't have 'p' acl flag permission on path '"
<< cPath.GetParentPath() << "'" << std::endl;
ret_c = EPERM;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
// check if this file exists
XrdSfsFileExistence file_exists;
errInfo.clear();
if (gOFS->_exists(path.c_str(), file_exists, errInfo, mVid, nullptr)) {
errStream << "error: unable to run exists on path '" << path << "'" <<
std::endl;
ret_c = errno;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
if (file_exists == XrdSfsFileExistNo) {
errStream << "error: no such file with path '" << path << "'" << std::endl;
ret_c = ENODATA;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
} else if (file_exists == XrdSfsFileExistIsDirectory) {
errStream << "error: given path is a directory '" << path << "'" << std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
struct stat buf;
if (gOFS->_stat(path.c_str(), &buf, errInfo, mVid, nullptr, nullptr,
false) != 0) {
errStream << "error: unable to run stat for replicas on path '" << path << "'"
<< std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
// we don't remove anything if it's not on tape
if ((buf.st_mode & EOS_TAPE_MODE_T) == 0) {
errStream << "error: no tape replicas for file '" << path << "'" << std::endl;
ret_c = EINVAL;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
continue;
}
int diskReplicaCount = 0;
XrdOucString options;
if (fsid.has_value()) {
auto fmd = gOFS->eosView->getFile(path.c_str());
bool diskReplicaFound = false;
for (auto location : fmd->getLocations()) {
// Ignore tape replica
if (location == eos::common::TAPE_FS_ID) {
continue;
}
if (location == fsid.value()) {
diskReplicaFound = true;
}
++diskReplicaCount;
}
if (!diskReplicaFound) {
eos_static_err("msg=\"unable to find disk replica of %s\" fsid=\"%u\" reason=\"%s\"",
path.c_str(), fsid.value(), errInfo.getErrText());
errStream << "error: unable to find disk replica of '" << path << "'" <<
std::endl;
eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID, fsid.value());
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
ret_c = SFS_ERROR;
continue;
}
} else {
auto fmd = gOFS->eosView->getFile(path.c_str());
for (auto location : fmd->getLocations()) {
// Ignore tape replica
if (location == eos::common::TAPE_FS_ID) {
continue;
}
++diskReplicaCount;
}
if (diskReplicaCount == 0) {
eos_static_err("msg=\"unable to find any disk replica of %s\" reason=\"%s\"",
path.c_str(), errInfo.getErrText());
errStream << "error: unable to find any disk replica of '" << path << "'" <<
std::endl;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
ret_c = SFS_ERROR;
continue;
}
}
errInfo.clear();
if (fsid.has_value() && ignoreEvictCounter) {
// Drop single stripe
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
if (gOFS->_dropstripe(path.c_str(), 0, errInfo, root_vid, fsid.value(),
ignoreRemovalOnFst) != 0) {
eos_static_err("msg=\"could not delete replica of %s\" fsid=\"%u\" reason=\"%s\"",
path.c_str(), fsid.value(), errInfo.getErrText());
errStream << "error: could not delete replica of '" << path << "'" <<
std::endl;
eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID, fsid.value());
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
ret_c = SFS_ERROR;
} else {
if (diskReplicaCount <= 1) {
allReplicasRemoved = true;
count_all_disk_replicas_removed++;
} else {
count_some_disk_replicas_removed++;
}
}
#pragma GCC diagnostic pop
} else {
// May drop all stripes
if (!ignoreEvictCounter) {
// Check the eviction counter first, if not force
int evictionCounter = 0;
try {
eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);
auto fmd = gOFS->eosView->getFile(path.c_str());
if (fmd->hasAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME)) {
evictionCounter = std::stoi(fmd->getAttribute(
eos::common::RETRIEVE_EVICT_COUNTER_NAME));
}
eosLog.addParam(EosCtaReportParam::EVICTCMD_EVICTCOUNTER, evictionCounter);
evictionCounter = std::max(0, evictionCounter - 1);
fmd->setAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME,
std::to_string(evictionCounter));
gOFS->eosView->updateFileStore(fmd.get());
} catch (eos::MDException& ex) {
eos_static_err("msg=\"could not update eviction counter for file %s\"",
path.c_str());
}
if (evictionCounter > 0) {
// Do not remove if eviction counter not zero
eosLog.addParam(EosCtaReportParam::EVICTCMD_FILEREMOVED, false);
count_evict_counter_not_zero++;
continue;
}
}
// Drop all stripes
if (gOFS->_dropallstripes(path.c_str(), errInfo, root_vid) != 0) {
eos_static_err("msg=\"could not delete all disk replicas of %s\" reason=\"%s\"",
path.c_str(), errInfo.getErrText());
errStream << "error: could not delete all disk replicas of '" << path << "'" <<
std::endl;
eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());
ret_c = SFS_ERROR;
} else {
count_all_disk_replicas_removed++;
allReplicasRemoved = true;
}
}
if (allReplicasRemoved) {
// reset the retrieves counter in case of success
eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);
try {
auto fmd = gOFS->eosView->getFile(path.c_str());
fmd->setAttribute(eos::common::RETRIEVE_REQID_ATTR_NAME, "");
fmd->setAttribute(eos::common::RETRIEVE_REQTIME_ATTR_NAME, "");
fmd->removeAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME);
gOFS->eosView->updateFileStore(fmd.get());
} catch (eos::MDException& ex) {
eos_static_err("msg=\"could not reset Prepare request ID list or eviction counter for "
"file %s. Try removing the %s, %s or %s attributes\"",
path.c_str(), eos::common::RETRIEVE_REQID_ATTR_NAME,
eos::common::RETRIEVE_REQTIME_ATTR_NAME,
eos::common::RETRIEVE_EVICT_COUNTER_NAME);
}
if (fsid.has_value()) {
eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID, fsid.value());
}
eosLog.addParam(EosCtaReportParam::EVICTCMD_FILEREMOVED, true);
}
}
reply.set_retc(ret_c);
reply.set_std_err(errStream.str());
std::string stdout_reply_s;
if ((count_all_disk_replicas_removed + count_some_disk_replicas_removed +
count_evict_counter_not_zero) > 0) {
if (fsid.has_value()) {
outStream << "found and removed the fsid=" << fsid.value() <<
" disk replica for "
<< (count_all_disk_replicas_removed + count_some_disk_replicas_removed)
<< "/" << evict.file_size() << " files";
} else {
outStream << "removed all disk replicas for "
<< count_all_disk_replicas_removed << "/" << evict.file_size() << " files";
if (!ignoreEvictCounter) {
outStream << "; reduced evict counter for " << count_evict_counter_not_zero <<
"/"
<< evict.file_size() << " files";
}
}
}
reply.set_std_out(outStream.str());
return reply;
}
EOSMGMNAMESPACE_END