// ---------------------------------------------------------------------- // File: TapeGc.cc // Author: Steven Murray - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 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/ShellCmd.hh" #include "mgm/proc/admin/EvictCmd.hh" #include "mgm/FsView.hh" #include "mgm/Policy.hh" #include "mgm/tgc/Constants.hh" #include "mgm/tgc/RealTapeGcMgm.hh" #include "mgm/tgc/SpaceNotFound.hh" #include "mgm/CtaUtils.hh" #include "namespace/interface/IFileMDSvc.hh" #include "namespace/ns_quarkdb/inspector/FileScanner.hh" #include "namespace/ns_quarkdb/qclient/include/qclient/QClient.hh" #include "namespace/Prefetcher.hh" #include #include EOSTGCNAMESPACE_BEGIN //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ RealTapeGcMgm::RealTapeGcMgm(XrdMgmOfs& ofs): m_ofs(ofs) { } //---------------------------------------------------------------------------- // Return the configuration of a tape-aware garbage collector //---------------------------------------------------------------------------- SpaceConfig RealTapeGcMgm::getTapeGcSpaceConfig(const std::string& spaceName) { SpaceConfig config; config.queryPeriodSecs = getSpaceConfigMemberUint64(spaceName, TGC_NAME_QRY_PERIOD_SECS, TGC_DEFAULT_QRY_PERIOD_SECS); config.availBytes = getSpaceConfigMemberUint64(spaceName, TGC_NAME_AVAIL_BYTES, TGC_DEFAULT_AVAIL_BYTES); config.freeBytesScript = getSpaceConfigMemberString(spaceName, TGC_NAME_FREE_BYTES_SCRIPT, TGC_DEFAULT_FREE_BYTES_SCRIPT); config.totalBytes = getSpaceConfigMemberUint64(spaceName, TGC_NAME_TOTAL_BYTES, TGC_DEFAULT_TOTAL_BYTES); return config; } //---------------------------------------------------------------------------- // Return the value of the specified space configuration variable //---------------------------------------------------------------------------- std::string RealTapeGcMgm::getSpaceConfigMemberString( const std::string& spaceName, const std::string& memberName, const std::string defaultValue) noexcept { try { std::string valueStr; { eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex); const auto spaceItor = FsView::gFsView.mSpaceView.find(spaceName); if (FsView::gFsView.mSpaceView.end() == spaceItor) { throw std::exception(); } if (nullptr == spaceItor->second) { throw std::exception(); } const auto& space = *(spaceItor->second); valueStr = space.GetConfigMember(memberName); } if (valueStr.empty()) { throw std::exception(); } else { return valueStr; } } catch (...) { return defaultValue; } } //---------------------------------------------------------------------------- // Return the value of the specified space configuration variable //---------------------------------------------------------------------------- std::uint64_t RealTapeGcMgm::getSpaceConfigMemberUint64( const std::string& spaceName, const std::string& memberName, const std::uint64_t defaultValue) noexcept { try { std::string valueStr; { eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex); const auto spaceItor = FsView::gFsView.mSpaceView.find(spaceName); if (FsView::gFsView.mSpaceView.end() == spaceItor) { throw std::exception(); } if (nullptr == spaceItor->second) { throw std::exception(); } const auto& space = *(spaceItor->second); valueStr = space.GetConfigMember(memberName); } if (valueStr.empty()) { throw std::exception(); } else { return CtaUtils::toUint64(valueStr); } } catch (...) { return defaultValue; } } //---------------------------------------------------------------------------- // Determine if the specified file exists and is not scheduled for deletion //---------------------------------------------------------------------------- bool RealTapeGcMgm::fileInNamespaceAndNotScheduledForDeletion( const IFileMD::id_t fid) { // Prefetch before taking lock because metadata may not be in memory Prefetcher::prefetchFileMDAndWait(m_ofs.eosView, fid); common::RWMutexReadLock lock(m_ofs.eosViewRWMutex); const auto fmd = m_ofs.eosFileService->getFileMD(fid); // A file scheduled for deletion has a container ID of 0 return nullptr != fmd && 0 != fmd->getContainerId(); } //---------------------------------------------------------------------------- // Return statistics about the specified EOS space //---------------------------------------------------------------------------- SpaceStats RealTapeGcMgm::getSpaceStats(const std::string& space) const { SpaceStats stats; // Policy::GetSpacePolicyLayout() implicitly takes a lock on FsView::gFsView.ViewMutex const auto layoutId = Policy::GetSpacePolicyLayout(space.c_str()); { eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex); if (FsView::gFsView.mSpaceView.count(space.c_str())) { stats.availBytes = FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong("stat.statfs.freebytes?configstatus@rw", false); stats.totalBytes = FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong("stat.statfs.capacity", false); } } // if there is a space policy layout defined we scale values to logical bytes if (layoutId) { const auto scalefactor = eos::common::LayoutId::GetSizeFactor(layoutId); if (scalefactor) { stats.availBytes /= scalefactor; stats.totalBytes /= scalefactor; } } return stats; } //---------------------------------------------------------------------------- // Return the size of the specified file //---------------------------------------------------------------------------- std::uint64_t RealTapeGcMgm::getFileSizeBytes(const IFileMD::id_t fid) { try { // Prefetch before taking lock because metadata may not be in memory Prefetcher::prefetchFileMDAndWait(m_ofs.eosView, fid); } catch (std::exception& ex) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": prefetchFileMDAndWait() failed: " << ex.what(); throw FailedToGetFileSize(msg.str()); } catch (...) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": prefetchFileMDAndWait() failed: Unknown exception"; throw FailedToGetFileSize(msg.str()); } common::RWMutexReadLock lock(m_ofs.eosViewRWMutex); std::shared_ptr fmd; try { fmd = m_ofs.eosFileService->getFileMD(fid); } catch (std::exception& ex) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getFileMD() failed: " << ex.what(); throw FailedToGetFileSize(msg.str()); } catch (...) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getFileMD() failed: Unknown exception"; throw FailedToGetFileSize(msg.str()); } if (nullptr == fmd) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getFileMD() returned nullptr"; throw FailedToGetFileSize(msg.str()); } std::uint64_t fileSizeBytes = 0; try { fileSizeBytes = fmd->getSize(); } catch (std::exception& ex) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getSize() failed: " << ex.what(); throw FailedToGetFileSize(msg.str()); } catch (...) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getSize() failed: Unknown exception"; throw FailedToGetFileSize(msg.str()); } IContainerMD::id_t containerId = 0; try { containerId = fmd->getContainerId(); } catch (std::exception& ex) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getContainerId() failed: " << ex.what(); throw FailedToGetFileSize(msg.str()); } catch (...) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": getContainerId() failed: Unknown exception"; throw FailedToGetFileSize(msg.str()); } // A file scheduled for deletion has a container ID of 0 if (0 == containerId) { std::ostringstream msg; msg << __FUNCTION__ << ": fid=" << fid << ": File has been scheduled for deletion"; throw FailedToGetFileSize(msg.str()); } return fileSizeBytes; } //---------------------------------------------------------------------------- // Execute evict as user root //---------------------------------------------------------------------------- void RealTapeGcMgm::evictAsRoot(const IFileMD::id_t fid) { eos::common::VirtualIdentity rootVid = eos::common::VirtualIdentity::Root(); eos::console::RequestProto req; eos::console::EvictProto* evict = req.mutable_evict(); evict->set_ignoreevictcounter(true); auto file = evict->add_file(); file->set_fid(fid); EvictCmd cmd(std::move(req), rootVid); auto const result = cmd.ProcessRequest(); if (result.retc()) { throw std::runtime_error(result.std_err()); } } //---------------------------------------------------------------------------- // Return map from file system ID to EOS space name //---------------------------------------------------------------------------- std::map RealTapeGcMgm::getFsIdToSpaceMap() { std::map fsIdToSpace; eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex); const auto spaces = getSpaces(); for (const auto& space : spaces) { const auto spaceItor = FsView::gFsView.mSpaceView.find(space); if (FsView::gFsView.mSpaceView.end() == spaceItor) { throw SpaceNotFound(std::string(__FUNCTION__) + ": Cannot find space " + space + ": FsView does not know the space name"); } if (nullptr == spaceItor->second) { throw SpaceNotFound(std::string(__FUNCTION__) + ": Cannot find space " + space + ": Pointer to FsSpace is nullptr"); } const FsSpace& fsSpace = *(spaceItor->second); for (const auto fsId : fsSpace) { const auto itor = fsIdToSpace.find(fsId); if (fsIdToSpace.end() != itor) { std::ostringstream msg; msg << __FUNCTION__ << " failed: Found a filesystem in more than one EOS space: fsId=" << fsId << " firstSpace=" << itor->second << " secondSpace=" << space; throw std::runtime_error(msg.str()); } fsIdToSpace[fsId] = space; } } return fsIdToSpace; } //---------------------------------------------------------------------------- // Return a list of the names of all the EOS spaces //---------------------------------------------------------------------------- std::set RealTapeGcMgm::getSpaces() const { std::set spaces; eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex); for (const auto& nameAndSpace : FsView::gFsView.mSpaceView) { if (0 != spaces.count(nameAndSpace.first)) { std::ostringstream msg; msg << __FUNCTION__ << " failed: Detected two EOS spaces with the same name: space=" << nameAndSpace.first; throw std::runtime_error(msg.str()); } spaces.insert(nameAndSpace.first); } return spaces; } //---------------------------------------------------------------------------- // Return map from EOS space name to disk replicas within that space //---------------------------------------------------------------------------- std::map > RealTapeGcMgm::getSpaceToDiskReplicasMap(const std::set& spacesToMap, std::atomic& stop, uint64_t& nbFilesScanned) { nbFilesScanned = 0; if (m_ofs.mQdbContactDetails.members.empty()) { std::ostringstream msg; msg << __FUNCTION__ << " failed: QdbContactDetails.members is empty"; eos_static_warning(msg.str().c_str()); throw std::runtime_error(msg.str()); } std::map > spaceToReplicas; const auto fsIdToSpace = getFsIdToSpaceMap(); qclient::QClient qdbClient(m_ofs.mQdbContactDetails.members, m_ofs.mQdbContactDetails.constructOptions()); FileScanner fileScanner(qdbClient); std::set fsIdsWithNoSpace; while (fileScanner.valid()) { eos::ns::FileMdProto file; if (stop) { eos_static_info("The creation of the EOS space name to files map has been requested to stop"); break; } if (!fileScanner.getItem(file)) { eos_static_warning("msg=\"fileScanner stopped iterating early\""); break; } const auto ctime = CtaUtils::bufToTimespec(file.ctime()); const int locationsSize = file.locations_size(); for (int locationIndex = 0; locationIndex < locationsSize; locationIndex++) { const int fsId = file.locations(locationIndex); const auto itor = fsIdToSpace.find(fsId); if (fsIdToSpace.end() == itor) { fsIdsWithNoSpace.insert(fsId); } else { const std::string& space = itor->second; if (spacesToMap.count(space)) { spaceToReplicas[space].emplace(file.id(), ctime); } } } nbFilesScanned++; fileScanner.next(); } if (!fsIdsWithNoSpace.empty()) { std::ostringstream msg; msg << "msg=\"Found file system IDs with no EOS space\" fsIds=\""; bool isFirstFsId = true; for (const auto fsId : fsIdsWithNoSpace) { if (isFirstFsId) { isFirstFsId = false; } else { msg << ","; } msg << fsId; } eos_static_warning(msg.str().c_str()); } return spaceToReplicas; } //---------------------------------------------------------------------------- // Get the stdout of the specified shell cmd as a string //---------------------------------------------------------------------------- std::string RealTapeGcMgm::getStdoutFromShellCmd(const std::string& cmdStr, const ssize_t maxLen) const { common::ShellCmd cmd(cmdStr); const size_t timeoutSecs = 5; const auto cmdRc = cmd.wait(timeoutSecs); if (cmdRc.timed_out) { std::ostringstream msg; msg << "Execution of shell command timed out after " << timeoutSecs << " seconds"; throw std::runtime_error(msg.str()); } else if (cmdRc.signaled) { std::ostringstream msg; msg << "Shell command received signal " << cmdRc.signo; throw std::runtime_error(msg.str()); } else if (cmdRc.exited && cmdRc.exit_code) { std::ostringstream msg; msg << "Shell command exited with non-zero exit code " << cmdRc.exit_code; throw std::runtime_error(msg.str()); } else if (cmdRc.exited && 0 == cmdRc.exit_code) { try { return CtaUtils::readFdIntoStr(cmd.outfd, maxLen); } catch (std::exception& ex) { std::ostringstream msg; msg << "Failed to read stdout from shell command: " << ex.what(); throw std::runtime_error(msg.str()); } } std::ostringstream msg; msg << "Shell command failed for unknown reason"; throw std::runtime_error(msg.str()); } EOSTGCNAMESPACE_END