/************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2016 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 "namespace/ns_quarkdb/accounting/FileSystemView.hh" #include "namespace/ns_quarkdb/flusher/MetadataFlusher.hh" #include "namespace/ns_quarkdb/persistency/RequestBuilder.hh" #include "namespace/ns_quarkdb/ConfigurationParser.hh" #include "namespace/ns_quarkdb/QdbContactDetails.hh" #include "namespace/ns_quarkdb/Constants.hh" #include "namespace/ns_quarkdb/FileMD.hh" #include "common/StringTokenizer.hh" #include "common/Logging.hh" #include "qclient/structures/QScanner.hh" #include "qclient/structures/QSet.hh" #include #include EOSNSNAMESPACE_BEGIN const std::chrono::minutes QuarkFileSystemView::sCacheCleanerTimeout { 45 }; //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ QuarkFileSystemView::QuarkFileSystemView(qclient::QClient* qcl, MetadataFlusher* flusher) : pFlusher(flusher), pQcl(qcl), mExecutor(new folly::IOThreadPoolExecutor(8)) { } //----------------------------------------------------------------------------- // Destructor //------------------------------------------------------------------------------ QuarkFileSystemView::~QuarkFileSystemView() { mCacheCleanerThread.join(); } //------------------------------------------------------------------------------ // Configure the container service //------------------------------------------------------------------------------ void QuarkFileSystemView::configure(const std::map& config) { // No configuration to read, everything we need has been passed to the // constructor already. auto start = std::time(nullptr); loadFromBackend(); auto end = std::time(nullptr); std::chrono::seconds duration(end - start); eos_static_info("msg=\"FileSystemView loadFromBackend\" duration=%llus", duration.count()); mNoReplicas.reset(new FileSystemHandler(mExecutor.get(), pQcl, pFlusher, IsNoReplicaListTag())); mCacheCleanerThread.reset(&QuarkFileSystemView::CleanCacheJob, this); } //------------------------------------------------------------------------------ // Run cache cleanup of the different FileSystemHandle objects tracked by // the FileSystemView in order to keep the memory overhead under control //------------------------------------------------------------------------------ void QuarkFileSystemView::CleanCacheJob(ThreadAssistant& assistant) noexcept { while (!assistant.terminationRequested()) { assistant.wait_for(sCacheCleanerTimeout); if (assistant.terminationRequested()) { break; } eos_static_info("%s", "msg=\"running file system clean cache job\""); // Collect list of all file system ids being tracked std::set fsids; { std::unique_lock lock(mMutex); for (const auto& elem : mFiles) { fsids.insert(elem.first); } for (const auto& elem : mUnlinkedFiles) { fsids.insert(elem.first); } } for (const auto& fsid : fsids) { auto* handle = fetchRegularFilelistIfExists(fsid); if (handle) { handle->clearCache(); } handle = fetchUnlinkedFilelistIfExists(fsid); if (handle) { handle->clearCache(); } } mNoReplicas->clearCache(); } } //------------------------------------------------------------------------------ // Notify the me about changes in the main view //------------------------------------------------------------------------------ void QuarkFileSystemView::fileMDChanged(IFileMDChangeListener::Event* e) { std::string key, val; QuarkFileMD* file = static_cast(e->file); qclient::QSet fs_set; switch (e->action) { //---------------------------------------------------------------------------- // New file has been created //---------------------------------------------------------------------------- case IFileMDChangeListener::Created: if (!file->isLink()) { mNoReplicas->insert(file->getIdentifier()); } break; //---------------------------------------------------------------------------- // File has been deleted //---------------------------------------------------------------------------- case IFileMDChangeListener::Deleted: { mNoReplicas->erase(file->getIdentifier()); break; } //---------------------------------------------------------------------------- // Add location //---------------------------------------------------------------------------- case IFileMDChangeListener::LocationAdded: { FileSystemHandler* handler = initializeRegularFilelist(e->location); handler->insert(file->getIdentifier()); mNoReplicas->erase(file->getIdentifier()); break; } //---------------------------------------------------------------------------- // Remove location. // // Perform destructive actions (ie erase) at the end. // This ensures that if we crash in the middle, we don't lose data, just // become inconsistent. //---------------------------------------------------------------------------- case IFileMDChangeListener::LocationRemoved: { if (!file->getNumUnlinkedLocation() && !file->getNumLocation()) { mNoReplicas->insert(file->getIdentifier()); } FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(e->location); if (handlerUnlinked) { handlerUnlinked->erase(file->getIdentifier()); } break; } //---------------------------------------------------------------------------- // Unlink location. // // Perform destructive actions (ie erase) at the end. // This ensures that if we crash in the middle, we don't lose data, just // become inconsistent. //---------------------------------------------------------------------------- case IFileMDChangeListener::LocationUnlinked: { FileSystemHandler* handlerUnlinked = initializeUnlinkedFilelist(e->location); handlerUnlinked->insert(file->getIdentifier()); FileSystemHandler* handlerRegular = fetchRegularFilelistIfExists(e->location); if (handlerRegular) { handlerRegular->erase(file->getIdentifier()); } break; } default: break; } } //------------------------------------------------------------------------------ // Recheck the current file object and make any modifications necessary so // that the information is consistent in the back-end KV store. //------------------------------------------------------------------------------ bool QuarkFileSystemView::fileMDCheck(IFileMD* file) { std::string key; IFileMD::LocationVector replica_locs = file->getLocations(); IFileMD::LocationVector unlink_locs = file->getUnlinkedLocations(); bool has_no_replicas = replica_locs.empty() && unlink_locs.empty(); std::string cursor {"0"}; std::pair> reply; qclient::AsyncHandler ah; qclient::QSet no_replica_set(*pQcl, fsview::sNoReplicaPrefix); // If file has no replicas make sure it's accounted for if (has_no_replicas) { no_replica_set.sadd_async(std::to_string(file->getId()), &ah); } else { no_replica_set.srem_async(std::to_string(file->getId()), &ah); } // Make sure all active locations are accounted for qclient::QSet replica_set(*pQcl, ""); for (IFileMD::location_t location : replica_locs) { replica_set.setKey(eos::RequestBuilder::keyFilesystemFiles(location)); replica_set.sadd_async(std::to_string(file->getId()), &ah); } // Make sure all unlinked locations are accounted for. qclient::QSet unlink_set(*pQcl, ""); for (IFileMD::location_t location : unlink_locs) { unlink_set.setKey(eos::RequestBuilder::keyFilesystemUnlinked(location)); unlink_set.sadd_async(std::to_string(file->getId()), &ah); } // Make sure there's no other filesystems that erroneously contain this file. for (auto it = this->getFileSystemIterator(); it->valid(); it->next()) { IFileMD::location_t fsid = it->getElement(); if (std::find(replica_locs.begin(), replica_locs.end(), fsid) == replica_locs.end()) { replica_set.setKey(eos::RequestBuilder::keyFilesystemFiles(fsid)); replica_set.srem_async(std::to_string(file->getId()), &ah); } if (std::find(unlink_locs.begin(), unlink_locs.end(), fsid) == unlink_locs.end()) { unlink_set.setKey(eos::RequestBuilder::keyFilesystemUnlinked(fsid)); unlink_set.srem_async(std::to_string(file->getId()), &ah); } } // Wait for all async responses return ah.Wait(); } //------------------------------------------------------------------------------ // Get iterator object to run through all currently active filesystem IDs //------------------------------------------------------------------------------ std::shared_ptr> QuarkFileSystemView::getFileSystemIterator() { std::unique_lock lock(mMutex); return std::shared_ptr> (new ListFileSystemIterator(mFiles)); } //---------------------------------------------------------------------------- // Get iterator to list of files on a particular file system //---------------------------------------------------------------------------- std::shared_ptr> QuarkFileSystemView::getFileList(IFileMD::location_t location) { FileSystemHandler* handler = fetchRegularFilelistIfExists(location); if (handler) { return handler->getFileList(); } return nullptr; } //---------------------------------------------------------------------------- // Get streaming iterator to list of files on a particular file system //---------------------------------------------------------------------------- std::shared_ptr> QuarkFileSystemView::getStreamingFileList(IFileMD::location_t location) { FileSystemHandler* handler = fetchRegularFilelistIfExists(location); if (handler) { return handler->getStreamingFileList(); } return nullptr; } //------------------------------------------------------------------------------ // Erase an entry from all filesystem view collections //------------------------------------------------------------------------------ void QuarkFileSystemView::eraseEntry(IFileMD::location_t location, IFileMD::id_t fid) { { FileSystemHandler* handler = fetchRegularFilelistIfExists(location); if (handler) { if (handler->hasFileId(fid)) { handler->erase(FileIdentifier(fid)); } } } { FileSystemHandler* handler = fetchUnlinkedFilelistIfExists(location); if (handler) { if (handler->hasFileId(fid)) { handler->erase(FileIdentifier(fid)); } } } mNoReplicas->erase(FileIdentifier(fid)); return ; } //---------------------------------------------------------------------------- // Get an approximately random file residing within the given filesystem. //---------------------------------------------------------------------------- bool QuarkFileSystemView::getApproximatelyRandomFileInFs(IFileMD::location_t location, IFileMD::id_t& retval) { FileSystemHandler* handler = fetchRegularFilelistIfExists(location); if (handler) { return handler->getApproximatelyRandomFile(retval); } return false; } //------------------------------------------------------------------------------ // Get iterator to list of unlinked files on a particular file system //------------------------------------------------------------------------------ std::shared_ptr> QuarkFileSystemView::getUnlinkedFileList(IFileMD::location_t location) { FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(location); if (handlerUnlinked) { return handlerUnlinked->getFileList(); } return nullptr; } //------------------------------------------------------------------------------ // Get iterator to list of files without replicas //------------------------------------------------------------------------------ std::shared_ptr> QuarkFileSystemView::getNoReplicasFileList() { return mNoReplicas->getFileList(); } //------------------------------------------------------------------------------ // Get number of files with no replicas //------------------------------------------------------------------------------ uint64_t QuarkFileSystemView::getNumNoReplicasFiles() { return mNoReplicas->size(); } //------------------------------------------------------------------------------ // Get number of files on the given file system //------------------------------------------------------------------------------ uint64_t QuarkFileSystemView::getNumFilesOnFs(IFileMD::location_t fs_id) { FileSystemHandler* handler = fetchRegularFilelistIfExists(fs_id); if (handler) { return handler->size(); } return 0ull; } //------------------------------------------------------------------------------ // Get number of unlinked files on the given file system //------------------------------------------------------------------------------ uint64_t QuarkFileSystemView::getNumUnlinkedFilesOnFs(IFileMD::location_t fs_id) { FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(fs_id); if (handlerUnlinked) { return handlerUnlinked->size(); } return 0ull; } //------------------------------------------------------------------------------ // Check if file system has file id //------------------------------------------------------------------------------ bool QuarkFileSystemView::hasFileId(IFileMD::id_t fid, IFileMD::location_t fs_id) { FileSystemHandler* handler = fetchRegularFilelistIfExists(fs_id); if (handler) { return handler->hasFileId(fid); } return false; } //------------------------------------------------------------------------------ // Clear unlinked files for filesystem //------------------------------------------------------------------------------ bool QuarkFileSystemView::clearUnlinkedFileList(IFileMD::location_t location) { FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(location); if (!handlerUnlinked) { return false; } handlerUnlinked->nuke(); return true; } //---------------------------------------------------------------------------- // Parse an fs set key, returning its id and whether it points to "files" or // "unlinked" //---------------------------------------------------------------------------- bool parseFsId(const std::string& str, IFileMD::location_t& fsid, bool& unlinked) { std::vector parts = eos::common::StringTokenizer::split>(str, ':'); if (parts.size() != 3) { return false; } if (parts[0] + ":" != fsview::sPrefix) { return false; } fsid = std::stoull(parts[1]); if (parts[2] == fsview::sFilesSuffix) { unlinked = false; } else if (parts[2] == fsview::sUnlinkedSuffix) { unlinked = true; } else { return false; } return true; } //---------------------------------------------------------------------------- // Get iterator object to run through all currently active filesystem IDs //---------------------------------------------------------------------------- std::shared_ptr> QuarkFileSystemView::getQdbFileSystemIterator(const std::string& pattern) { qclient::QScanner replicaSets(*pQcl, pattern); std::set uniqueFilesytems; for (; replicaSets.valid(); replicaSets.next()) { // Extract fsid from key IFileMD::location_t fsid; bool unused; if (!parseFsId(replicaSets.getValue(), fsid, unused)) { eos_static_crit("Unable to parse key: %s", replicaSets.getValue().c_str()); continue; } uniqueFilesytems.insert(fsid); } return std::shared_ptr> (new QdbFileSystemIterator(std::move(uniqueFilesytems))); } //------------------------------------------------------------------------------ // Get iterator to list of files without replicas //------------------------------------------------------------------------------ std::shared_ptr> QuarkFileSystemView::getStreamingNoReplicasFileList() { return mNoReplicas->getStreamingFileList(); } //------------------------------------------------------------------------------ // Load view from backend //------------------------------------------------------------------------------ void QuarkFileSystemView::loadFromBackend() { std::vector patterns { fsview::sPrefix + "*:files", fsview::sPrefix + "*:unlinked" }; for (const auto& pattern : patterns) { for (auto it = getQdbFileSystemIterator(pattern); (it && it->valid()); it->next()) { IFileMD::location_t fsid = it->getElement(); if (pattern.find("unlinked") != std::string::npos) { initializeUnlinkedFilelist(fsid); } else { initializeRegularFilelist(fsid); } } } } //------------------------------------------------------------------------------ //! Initialize FileSystemHandler for given filesystem ID, if not already //! initialized. Otherwise, do nothing. //! //! In any case, return pointer to the corresponding FileSystemHandler. //! //! @param fsid file system id //------------------------------------------------------------------------------ FileSystemHandler* QuarkFileSystemView::initializeRegularFilelist( IFileMD::location_t fsid) { std::unique_lock lock(mMutex); auto iter = mFiles.find(fsid); if (iter != mFiles.end()) { // Found return iter->second.get(); } mFiles[fsid].reset(new FileSystemHandler(fsid, mExecutor.get(), pQcl, pFlusher, false)); return mFiles[fsid].get(); } //------------------------------------------------------------------------------ //! Fetch FileSystemHandler for a given filesystem ID, but do not initialize //! if it doesn't exist, give back nullptr. //! //! @param fsid file system id //------------------------------------------------------------------------------ FileSystemHandler* QuarkFileSystemView::fetchRegularFilelistIfExists( IFileMD::location_t fsid) { std::unique_lock lock(mMutex); auto iter = mFiles.find(fsid); if (iter == mFiles.end()) { return nullptr; } return iter->second.get(); } //------------------------------------------------------------------------------ //! Initialize unlinked FileSystemHandler for given filesystem ID, //! if not already initialized. Otherwise, do nothing. //! //! In any case, return pointer to the corresponding FileSystemHandler. //! //! @param fsid file system id //------------------------------------------------------------------------------ FileSystemHandler* QuarkFileSystemView::initializeUnlinkedFilelist( IFileMD::location_t fsid) { std::unique_lock lock(mMutex); auto iter = mUnlinkedFiles.find(fsid); if (iter != mUnlinkedFiles.end()) { // Found return iter->second.get(); } mUnlinkedFiles[fsid].reset(new FileSystemHandler(fsid, mExecutor.get(), pQcl, pFlusher, true)); return mUnlinkedFiles[fsid].get(); } //------------------------------------------------------------------------------ //! Fetch unlinked FileSystemHandler for a given filesystem ID, but do not //! initialize if it doesn't exist, give back nullptr. //! //! @param fsid file system id //------------------------------------------------------------------------------ FileSystemHandler* QuarkFileSystemView::fetchUnlinkedFilelistIfExists( IFileMD::location_t fsid) { std::unique_lock lock(mMutex); auto iter = mUnlinkedFiles.find(fsid); if (iter == mUnlinkedFiles.end()) { return nullptr; } return iter->second.get(); } EOSNSNAMESPACE_END