//------------------------------------------------------------------------------ // File: proc_fs.cc // Author: Andreas-Joachim Peters - CERN & Ivan Arizanovic - Comtrade //------------------------------------------------------------------------------ /************************************************************************ * 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 "mgm/FsView.hh" #include "mgm/proc/proc_fs.hh" #include "mgm/proc/ProcInterface.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/IMaster.hh" #include "namespace/interface/IFsView.hh" #include "namespace/interface/IView.hh" #include "namespace/Prefetcher.hh" #include "common/LayoutId.hh" #include "common/Path.hh" #include "common/Constants.hh" #include "common/ParseUtils.hh" EOSMGMNAMESPACE_BEGIN //------------------------------------------------------------------------------ // Get type of entity used in a fs mv operation. //------------------------------------------------------------------------------ EntityType get_entity_type(const std::string& input, XrdOucString& stdOut, XrdOucString& stdErr) { std::ostringstream oss; EntityType ret = EntityType::UNKNOWN; // check for nodes size_t ppos = input.find(":"); if (ppos != std::string::npos) { // this is a node with port ret = EntityType::NODE; return ret; } // check for fs,group,space size_t pos = input.find('.'); if (pos == std::string::npos) { if (input.find_first_not_of("0123456789") == std::string::npos) { // This is an fs id errno = 0; (void) strtol(input.c_str(), nullptr, 10); if (errno) { eos_static_err("input fsid: %s must be a numeric value", input.c_str()); oss << "fsid: " << input << " must be a numeric value"; stdErr = oss.str().c_str(); } else { ret = EntityType::FS; } } else { // This is a space ret = EntityType::SPACE; } } else { // This is group definition, make sure the space and group tokens are correct std::string space = input.substr(0, pos); std::string group = input.substr(pos + 1); if (space.find_first_not_of("0123456789") == std::string::npos) { eos_static_err("input space.group: %s must contain a string value for space", input.c_str()); oss << "space.group: " << input << " must contain a string value for space"; stdErr = oss.str().c_str(); } else { // Group must be a numeric value if (group.find_first_not_of("0123456789") != std::string::npos) { eos_static_err("input space.group: %s must contain a numeric value for group", input.c_str()); oss << "space.group: " << input << " must contain a numeric value for group"; stdErr = oss.str().c_str(); } else { ret = EntityType::GROUP; } } } return ret; } //------------------------------------------------------------------------------ // Get operation type based on the input entity types //------------------------------------------------------------------------------ MvOpType get_operation_type(const std::string& in1, const std::string& in2, XrdOucString& stdOut, XrdOucString& stdErr) { // Do them individually to get proper error messages EntityType in1_type = get_entity_type(in1, stdOut, stdErr); if (in1_type == EntityType::UNKNOWN) { return MvOpType::UNKNOWN; } EntityType in2_type = get_entity_type(in2, stdOut, stdErr); if (in2_type == EntityType::UNKNOWN) { return MvOpType::UNKNOWN; } if (((in1_type == EntityType::FS) && (in2_type == EntityType::SPACE)) || ((in1_type == EntityType::FS) && (in2_type == EntityType::GROUP)) || ((in1_type == EntityType::GROUP) && (in2_type == EntityType::SPACE)) || ((in1_type == EntityType::SPACE) && (in2_type == EntityType::SPACE)) || ((in1_type == EntityType::FS) && (in2_type == EntityType::NODE))) { return static_cast((in1_type << 2) | in2_type); } return MvOpType::UNKNOWN; } //------------------------------------------------------------------------------ // Dump metadata information //------------------------------------------------------------------------------ int proc_fs_dumpmd(std::string& sfsid, XrdOucString& option, bool show_path, bool show_fid, bool show_fxid, bool show_size, XrdOucString& stdOut, XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in, size_t& entries) { entries = 0; int retc = 0; bool monitor = false; bool processPath = false; std::ostringstream out; std::ostringstream err; std::ostringstream warn; out << std::setfill('0'); warn << std::setfill('0') << std::hex; if (option == "m") { monitor = true; show_path = false; show_fid = false; show_fxid = false; show_size = false; } processPath = monitor || show_path; if (!sfsid.length()) { err << "error: no provided"; retc = EINVAL; } else { int fsid = atoi(sfsid.c_str()); eos::Prefetcher::prefetchFilesystemFileListWithFileMDsAndParentsAndWait( gOFS->eosView, gOFS->eosFsView, fsid); if (monitor) { eos::Prefetcher::prefetchFilesystemUnlinkedFileListWithFileMDsAndWait( gOFS->eosView, gOFS->eosFsView, fsid); } eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex); for (auto it_fid = gOFS->eosFsView->getFileList(fsid); (it_fid && it_fid->valid()); it_fid->next()) { std::shared_ptr fmd; std::string containerpath; std::string fullpath; try { fmd = gOFS->eosFileService->getFileMD(it_fid->getElement()); if (fmd) { entries++; if (processPath) { try { std::string spath = gOFS->eosView->getUri(fmd.get()); XrdOucString safepath = spath.c_str(); eos::common::StringConversion::SealXrdPath(safepath); fullpath = safepath.c_str(); safepath = eos::common::Path{spath.c_str()} .GetParentPath(); eos::common::StringConversion::SealXrdPath(safepath); containerpath = safepath.c_str(); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_err("Couldn't retrieve path for fxid=%08llx " "errc=%d emsg=\"%s\"", it_fid->getElement(), e.getErrno(), e.getMessage().str().c_str()); } } if ((!show_path) && (!show_fid) && (!show_fxid) && (!show_size)) { std::string env; fmd->getEnv(env, true); XrdOucString senv = env.c_str(); if (senv.endswith("checksum=")) { senv.replace("checksum=", "checksum=none"); } out << senv.c_str(); if (monitor) { out << "&container=" << (containerpath.size() ? containerpath.c_str() : "(null)"); } } else { bool has_info = false; if (show_path) { out << "path=" << (fullpath.size() ? fullpath.c_str() : "(null)"); has_info = true; } if (show_fid) { out << (has_info ? " " : "") << "fid=" << fmd->getId(); has_info = true; } if (show_fxid) { out << (has_info ? " " : "") << "fxid=" << std::setw(8) << std::hex << fmd->getId() << std::dec; has_info = true; } if (show_size) { out << (has_info ? " " : "") << "size=" << fmd->getSize(); } } out << endl; } } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_err("Couldn't retrieve meta data for fxid=%llx errc=%d " "emsg=\"%s\"", (unsigned long long) it_fid->getElement(), e.getErrno(), e.getMessage().str().c_str()); } if (!fmd) { warn << "# warning: ghost entry fxid=" << std::setw(8) << std::hex << it_fid->getElement() << std::dec << endl; retc = EIDRM; } else if (processPath && containerpath.empty()) { warn << "# warning: missing container for fxid=" << std::setw(8) << std::hex << fmd->getId() << std::dec << endl; retc = EIDRM; } } if (monitor) { // Also add files which have yet to be unlinked for (auto it_fid = gOFS->eosFsView->getUnlinkedFileList(fsid); (it_fid && it_fid->valid()); it_fid->next()) { std::shared_ptr fmd; try { fmd = gOFS->eosFileService->getFileMD(it_fid->getElement()); if (fmd) { entries++; std::string env; fmd->getEnv(env, true); XrdOucString senv = env.c_str(); senv.replace("checksum=&", "checksum=none&"); out << senv.c_str() << "&container=(null)" << endl; } } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_err("Couldn't retrieve meta data for fxid=%08llx errc=%d " "emsg=\"%s\"", it_fid->getElement(), e.getErrno(), e.getMessage().str().c_str()); } } } } if (retc == EIDRM) { out << warn.str().c_str(); err << "# error: filesystem contains problematic entries" << endl; } stdOut += out.str().c_str(); stdErr = err.str().c_str(); return retc; } //------------------------------------------------------------------------------ // Configure filesystem //------------------------------------------------------------------------------ int proc_fs_config(std::string& identifier, std::string& key, std::string& value, XrdOucString& stdOut, XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in, const std::string& statusComment) { int retc = 0; const std::string vid_hostname = vid_in.host; eos::common::FileSystem::fsid_t fsid = 0; // Check if identifier is fsid (must be pure numeric) if (identifier.find_first_not_of("0123456789") == std::string::npos) { fsid = atoi(identifier.c_str()); } if (!identifier.length() || !key.length() || !value.length()) { stdErr = "error: illegal parameters"; retc = EINVAL; } else { FileSystem* fs = nullptr; eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex); if (fsid) { // by filesystem id fs = FsView::gFsView.mIdView.lookupByID(fsid); } if (!fs && FsView::gFsView.GetMapping(identifier)) { // by filesystem uuid fs = FsView::gFsView.mIdView.lookupByID(FsView::gFsView.GetMapping(identifier)); } if (!fs) { // by host:port:data name std::string path = identifier; size_t slashpos = identifier.find('/'); if (slashpos != std::string::npos) { path.erase(0, slashpos); identifier.erase(slashpos); if ((identifier.find(':') == std::string::npos)) { identifier += ":1095"; // default eos fst port } if ((identifier.find("/eos/") == std::string::npos)) { identifier.insert(0, "/eos/"); identifier.append("/fst"); } if (FsView::gFsView.mNodeView.count(identifier)) { for (auto it = FsView::gFsView.mNodeView[identifier]->begin(); it != FsView::gFsView.mNodeView[identifier]->end(); ++it) { FileSystem* candidate = FsView::gFsView.mIdView.lookupByID(*it); if (candidate && candidate->GetPath() == path) { fs = candidate; } } } } } if (fs) { // Check the allowed strings if (((key == "configstatus") && (eos::common::FileSystem::GetConfigStatusFromString(value.c_str()) != eos::common::ConfigStatus::kUnknown)) || (((key == eos::common::SCAN_IO_RATE_NAME) || (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) || (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) || (key == eos::common::SCAN_DISK_INTERVAL_NAME) || (key == eos::common::SCAN_NS_INTERVAL_NAME) || (key == eos::common::SCAN_NS_RATE_NAME) || (key == eos::common::FSCK_REFRESH_INTERVAL_NAME) || (key == "max.ropen" || (key == "max.wopen")) || (key == "headroom") || (key == "graceperiod") || (key == "drainperiod") || (key == "proxygroup") || (key == "filestickyproxydepth") || (key == "forcegeotag") || (key == "sharedfs") || (key == "s3credentials")))) { // Check permissions size_t dpos = 0; std::string nodename = fs->GetString("host"); if ((dpos = nodename.find('.')) != std::string::npos) { nodename.erase(dpos); } // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set then we skip // the check below as this currently breaks the Kubernetes setup. bool skip_hostname_match = false; if (getenv("EOS_SKIP_SSS_HOSTNAME_MATCH")) { skip_hostname_match = true; } if ((vid_in.uid == 0) || (vid_in.prot == "sss")) { if ((vid_in.prot == "sss") && (vid_in.uid != 0)) { if (!skip_hostname_match && vid_hostname.compare(0, nodename.length(), nodename, 0, nodename.length())) { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (1)\n"; retc = EPERM; return retc; } } } else { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (2)\n"; retc = EPERM; return retc; } if ((key == eos::common::SCAN_IO_RATE_NAME) || (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) || (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) || (key == eos::common::SCAN_DISK_INTERVAL_NAME) || (key == eos::common::SCAN_NS_INTERVAL_NAME) || (key == eos::common::SCAN_NS_RATE_NAME) || (key == eos::common::FSCK_REFRESH_INTERVAL_NAME) || (key == "headroom") || (key == "graceperiod") || (key == "drainperiod")) { fs->SetLongLong(key.c_str(), eos::common::StringConversion::GetSizeFromString(value.c_str())); FsView::gFsView.StoreFsConfig(fs); } else if (key == "configstatus") { if (value == "empty") { // Check if this filesystem is really empty if (gOFS->eosFsView->getNumFilesOnFs(fs->GetId()) != 0) { std::ostringstream oss; oss << "error: the filesystem is not empty, therefore it can't be removed\n" << "# -------------------------------------------------------------------\n" << "# You can inspect the registered files via the command:\n" << "# [eos] fs dumpmd " << fs->GetId() << " --path\n" << "# -------------------------------------------------------------------\n" << "# You can drain the filesystem if it is still operational via the command:\n" << "# [eos] fs config " << fs->GetId() << " configstatus=drain\n" << "# -------------------------------------------------------------------\n" << "# You can force to remove these files via the command:\n" << "# [eos] fs dropfiles " << fs->GetId() << "\n" << "# -------------------------------------------------------------------\n" << "# You can force to drop these files (brute force) via the command:\n" << "# [eos] fs dropfiles " << fs->GetId() << " -f \n" << "# -------------------------------------------------------------------\n" << "# [eos] = 'eos -b' on MGM or 'eosadmin' on storage nodes\n"; stdErr = oss.str().c_str(); retc = EPERM; return retc; } } if (!fs->SetString(key.c_str(), value.c_str())) { stdErr = "error: failed to apply configuration change"; retc = EINVAL; return retc; } std::string operation; bool success; if (statusComment.empty()) { success = fs->RemoveKey("statuscomment"); operation = "remove"; } else { success = fs->SetString("statuscomment", statusComment.c_str()); operation = "save"; } if (!success) { eos_static_warning("failed to %s config status comment " "fs_identifier=%s comment=%s", operation.c_str(), identifier.c_str(), statusComment.c_str()); } FsView::gFsView.StoreFsConfig(fs); } else if (key == "s3credentials") { // Validate S3 credentials string if (std::count(value.begin(), value.end(), ':') != 1) { stdErr += "error: invalid S3 credentials string"; retc = EINVAL; return retc; } else { size_t pos = value.find(':'); if (pos == 0 || (pos + 1) == value.length()) { stdErr += "error: S3 credentials string is missing "; stdErr += (pos == 0) ? "" : ""; retc = EINVAL; return retc; } } fs->SetString(key.c_str(), value.c_str()); FsView::gFsView.StoreFsConfig(fs); } else if (key == "forcegeotag") { std::string geotag = eos::common::SanitizeGeoTag(value); if (geotag != value) { stdErr += geotag.c_str(); retc = EINVAL; return retc; } fs->SetString(key.c_str(), value.c_str()); FsView::gFsView.StoreFsConfig(fs); } else { // Other proxy* key set fs->SetString(key.c_str(), value.c_str()); FsView::gFsView.StoreFsConfig(fs); } } else { stdErr += "error: not an allowed parameter <"; stdErr += key.c_str(); stdErr += ">"; retc = EINVAL; } } else { stdErr += "error: cannot identify the filesystem by <"; stdErr += identifier.c_str(); stdErr += ">"; retc = EINVAL; } } return retc; } //------------------------------------------------------------------------------ // Add filesystem //------------------------------------------------------------------------------ int proc_fs_add(mq::MessagingRealm* realm, std::string& sfsid, std::string& uuid, std::string& nodename, std::string& mountpoint, std::string& space, std::string& configstatusStr, std::string& sharedfs, XrdOucString& stdOut, XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in, bool force) { using eos::common::StringConversion; const std::string vid_hostname = vid_in.host; common::FileSystem::fsid_t fsid = atoi(sfsid.c_str()); common::ConfigStatus configStatus = common::FileSystem::GetConfigStatusFromString(configstatusStr.c_str()); if ((!nodename.length()) || (!mountpoint.length()) || (!space.length()) || (!configstatusStr.length()) || (configStatus < eos::common::ConfigStatus::kOff)) { stdErr += "error: illegal parameters"; return EINVAL; } // Node name comes as /eos/..../, we just need without domain std::string rnodename = nodename; rnodename.erase(0, 5); size_t dpos; if ((dpos = rnodename.find('.')) != std::string::npos) { rnodename.erase(dpos); } // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set then we skip // the check below as this currently breaks the Kubernetes setup. bool skip_hostname_match = (getenv("EOS_SKIP_SSS_HOSTNAME_MATCH") ? true : false); // Rough check that the filesystem is added from a host with the same // hostname ... anyway we should have configured 'sss' security if ((vid_in.uid == 0) || (vid_in.prot == "sss")) { if ((vid_in.prot == "sss") && (vid_in.uid != 0)) { if (!skip_hostname_match && vid_hostname.compare(0, rnodename.length(), rnodename, 0, rnodename.length())) { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (1)"; return EPERM; } } } else { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (2)"; return EPERM; } eos::common::RWMutexWriteLock fs_wr_lock(FsView::gFsView.ViewMutex); // queuepath = /eos// std::string queuepath = nodename; queuepath += mountpoint; common::FileSystemLocator locator; if (!common::FileSystemLocator::fromQueuePath(queuepath, locator)) { eos_static_err("msg=\"could not parse queue path\" queue=\"%s\"", queuepath.c_str()); stdErr += "error: could not parse queue path qeueue='"; stdErr += queuepath.c_str(); stdErr += "'"; return EINVAL; } // Check if this filesystem exists already .... if (FsView::gFsView.ExistsQueue(nodename, queuepath)) { eos_static_err("msg=\"file system already registered\" queue=\"%s\"", queuepath.c_str()); stdErr += "error: cannot register filesystem - it already exists!"; return EEXIST; } // Check if there is a mapping for 'uuid' if (FsView::gFsView.GetMapping(uuid) || ((fsid > 0) && (FsView::gFsView.HasMapping(fsid)))) { eos_static_err("msg=\"file system already registered\" uuid=%s fsid=%lu", uuid.c_str(), fsid); stdErr = SSTR("error: file system identified by uuid=" << uuid << " id=" << sfsid << " already exists").c_str(); return EEXIST; } // We want one atomic update with all the parameters defined std::string splitspace; std::string splitgroup; unsigned int groupsize = 0; unsigned int groupmod = 0; // Logic to automatically adjust scheduling subgroups StringConversion::SplitByPoint(space, splitspace, splitgroup); if (FsView::gFsView.mSpaceView.count(splitspace)) { groupsize = atoi(FsView::gFsView.mSpaceView[splitspace]->GetMember (std::string("cfg.groupsize")).c_str()); groupmod = atoi(FsView::gFsView.mSpaceView[splitspace]->GetMember (std::string("cfg.groupmod")).c_str()); } else { if (splitspace != eos::common::EOS_SPARE_GROUP) { eos_static_err("msg=\"no such space\" space=%s", splitspace.c_str()); stdErr = SSTR("error: not such space \"" << splitspace << "\"").c_str(); return EINVAL; } } // Groups where we attempt to insert the current file system std::set target_grps; // Case when specific group is requested by the user if (splitgroup.length()) { try { int id = std::stoi(splitgroup); if (id >= (int)groupmod) { stdErr = SSTR("error: requested group " << id << " bigger than groupmod").c_str(); return EINVAL; } target_grps.insert(id); } catch (...) { eos_static_err("msg=\"invalid group requested\" group=\"%s\"", splitgroup.c_str()); stdErr = SSTR("error: invalid group requested \"" << splitgroup << "\"").c_str(); return EINVAL; } } else { // Otherwise, try out all the groups in the space for (int i = 0; i < (int)groupmod; ++i) { target_grps.insert(i); } } // Final group will be decided later on splitgroup.clear(); // Handle special case for "spare" space that does not have any groups if (splitspace == eos::common::EOS_SPARE_GROUP) { target_grps.clear(); } for (auto grp_id : target_grps) { std::string schedgroup = SSTR(splitspace << "." << grp_id); // All good if group is not yet created if (FsView::gFsView.mGroupView.count(schedgroup) == 0) { splitgroup = std::to_string(grp_id); break; } FsGroup* group = FsView::gFsView.mGroupView[schedgroup]; if (!force) { // Skip if group is already full if (group->size() > groupsize) { if (target_grps.size() == 1) { stdErr += SSTR("error: scheduling group " << splitspace << "." << grp_id << " is full" << std::endl).c_str(); } continue; } } // Skip if group already contains an fs from the current node. // Allow disabling this check in development clusters through the envvar bool exists = false; if (!getenv("EOS_ALLOW_SAME_HOST_IN_GROUP")) { for (auto it = group->begin(); it != group->end(); ++it) { FileSystem* entry = FsView::gFsView.mIdView.lookupByID(*it); if (entry && (entry->GetString("host") == locator.getHost())) { exists = true; break; } } } if (exists) { continue; } splitgroup = std::to_string(grp_id); break; } if ((splitspace != eos::common::EOS_SPARE_GROUP) && splitgroup.empty()) { eos_static_err("msg=\"no group available for file system\" fsid=%lu" " queue=%s", fsid, queuepath.c_str()); stdErr += "error: no group available for file system"; return EINVAL; } FileSystem* fs = nullptr; if (fsid) { if (!FsView::gFsView.ProvideMapping(uuid, fsid)) { eos_static_err("msg=\"conflict registering file system id/uuid\"" "fsid=%lu uuid=%s", fsid, uuid.c_str()); stdErr += "error: conflict adding your uuid/fsid mapping"; return EINVAL; } else { fs = new FileSystem(locator, realm); } } else { fsid = FsView::gFsView.CreateMapping(uuid); fs = new FileSystem(locator, realm); } stdOut += SSTR("success: mapped '" << uuid << "' <=> fsid=" << fsid).c_str(); common::GroupLocator groupLocator; std::string description = ((splitspace == eos::common::EOS_SPARE_GROUP) ? splitspace : SSTR(splitspace << "." << splitgroup)); common::GroupLocator::parseGroup(description, groupLocator); common::FileSystemCoreParams coreParams(fsid, locator, groupLocator, uuid, configStatus, sharedfs); if (FsView::gFsView.Register(fs, coreParams)) { // Set all the space related default parameters if (FsView::gFsView.mSpaceView.count(space)) { if (FsView::gFsView.mSpaceView[space]->ApplySpaceDefaultParameters(fs)) { // Store the modifications FsView::gFsView.StoreFsConfig(fs); } } } else { // Remove mapping if (FsView::gFsView.RemoveMapping(fsid, uuid)) { stdErr += SSTR("\ninfo: unmapped '" << uuid << "' fsid=" << fsid << std::endl).c_str(); } else { stdErr += "error: cannot remove mapping - this can be fatal!"; } // Remove filesystem object stdErr += "error: cannot register filesystem - check for path duplication!"; return EINVAL; } return 0; } //------------------------------------------------------------------------------ // Move a filesystem/group/space to a group/space //------------------------------------------------------------------------------ int proc_fs_mv(std::string& src, std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in, bool force, mq::MessagingRealm* realm) { int retc = 0; MvOpType operation = get_operation_type(src, dst, stdOut, stdErr); eos::common::RWMutexWriteLock fs_wr_lock(FsView::gFsView.ViewMutex); switch (operation) { case MvOpType::FS_2_GROUP: retc = proc_mv_fs_group(FsView::gFsView, src, dst, stdOut, stdErr, force); break; case MvOpType::FS_2_SPACE: retc = proc_mv_fs_space(FsView::gFsView, src, dst, stdOut, stdErr, force); break; case MvOpType::GRP_2_SPACE: retc = proc_mv_grp_space(FsView::gFsView, src, dst, stdOut, stdErr, force); break; case MvOpType::SPC_2_SPACE: retc = proc_mv_space_space(FsView::gFsView, src, dst, stdOut, stdErr, force); break; case MvOpType::FS_2_NODE: retc = proc_mv_fs_node(FsView::gFsView, src, dst, stdOut, stdErr, force, vid_in, realm); break; default: stdErr = "error: operation not supported"; retc = EINVAL; break; } return retc; } //------------------------------------------------------------------------------ // Check if a file system can be moved. It needs to be active and in RW mode. //------------------------------------------------------------------------------ bool proc_fs_can_mv(eos::mgm::FileSystem* fs, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force) { std::ostringstream oss; FileSystem::fs_snapshot_t snapshot; if (fs->SnapShotFileSystem(snapshot)) { if (force) { return true; } if (dst.find('.') != std::string::npos) { if (snapshot.mGroup == dst) { oss << "error: file system " << snapshot.mId << " is already in " << "group " << dst << std::endl; stdOut = oss.str().c_str(); return false; } } else { if (snapshot.mSpace == dst) { oss << "error:: file system " << snapshot.mId << " is already in " << "space " << dst << std::endl; stdOut = oss.str().c_str(); return false; } } // File system must be in RW mode and active for the move to work bool is_empty = (fs->GetConfigStatus() == eos::common::ConfigStatus::kEmpty); bool is_active = (fs->GetActiveStatus() == eos::common::ActiveStatus::kOnline); if (!(is_empty && is_active)) { eos_static_err("msg=\"file system is not empty or is not active\" " "fsid=%lu", snapshot.mId); oss << "error: file system " << snapshot.mId << " is not empty or " << "is not active" << std::endl; stdErr = oss.str().c_str(); return false; } } else { eos_static_err("%s", "msg=\"failed to snapshot file system\""); oss << "error: failed to snapshot files system" << std::endl; stdErr = oss.str().c_str(); return false; } return true; } //------------------------------------------------------------------------------ // Move a filesystem to a group //------------------------------------------------------------------------------ int proc_mv_fs_group(FsView& fs_view, const std::string& src, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force) { using eos::mgm::FsView; using eos::mgm::FileSystem; using eos::common::StringConversion; int pos = dst.find('.'); eos::common::FileSystem::fsid_t fsid = atoi(src.c_str()); std::string space = dst.substr(0, pos); std::string group = dst.substr(pos + 1); size_t grp_size = 0; size_t grp_mod = 0; std::ostringstream oss; // Check if space exists and get groupsize and groupmod auto it_space = fs_view.mSpaceView.find(space); if (it_space != fs_view.mSpaceView.end()) { grp_size = std::strtoul(it_space->second->GetConfigMember("groupsize").c_str(), nullptr, 10); grp_mod = std::strtoul(it_space->second->GetConfigMember("groupmod").c_str(), nullptr, 10); } else { eos_static_err("msg=\"requested space %s does not exist\"", space.c_str()); oss << "error: space " << space << " does not exist" << std::endl; stdErr = oss.str().c_str(); return EINVAL; } // Get this filesystem FileSystem* fs = fs_view.mIdView.lookupByID(fsid); if (fs) { if (!proc_fs_can_mv(fs, dst, stdOut, stdErr, force)) { return EINVAL; } } else { eos_static_err("no such fsid: %i", fsid); oss << "error: no such fsid: " << fsid << std::endl; stdErr = oss.str().c_str(); return EINVAL; } // Get the target group if (dst != eos::common::EOS_SPARE_GROUP) { auto const iter = fs_view.mGroupView.find(dst); if (iter != fs_view.mGroupView.end()) { FsGroup* grp = iter->second; size_t grp_num_fs = grp->size(); // Check that we can still add file systems to this group if (!force && (grp_num_fs > grp_size)) { eos_static_err("msg=\"reached maximum number of fs for group %s\"", dst.c_str()); oss << "error: reached maximum number of file systems for group " << dst.c_str() << std::endl; stdErr = oss.str().c_str(); return EINVAL; } // Check that there is no other file system from the same node // in this group bool is_forbidden = false; std::string host; std::string fs_host = fs->GetHost(); for (auto it = grp->begin(); it != grp->end(); ++it) { FileSystem* entry = fs_view.mIdView.lookupByID(*it); if (entry) { host = entry->GetHost(); if (fs_host == host) { is_forbidden = true; break; } } } if (!force && is_forbidden) { eos_static_err("msg=\"group %s already contains an fs from the " "same node\"", dst.c_str()); oss << "error: group " << dst << " already contains a file system from the same node" << std::endl; stdErr = oss.str().c_str(); return EINVAL; } } else { // A new group will be created, check that it respects the groupmod param size_t grp_indx = std::strtoul(group.c_str(), nullptr, 10); if (!force && (grp_indx >= grp_mod)) { eos_static_err("group %s is not respecting the groupmod value of %u", dst.c_str(), grp_mod); oss << "error: group " << dst.c_str() << " is not respecting the groupmod" << " value of " << grp_mod << " for this space" << std::endl; stdErr = oss.str().c_str(); return EINVAL; } eos_static_debug("group %s will be created", dst.c_str()); } } else { // This is a special case when we "park" file systems in the spare space eos_static_debug("fsid %s will be \"parked\" in space spare", src.c_str()); } if (fs_view.MoveGroup(fs, dst)) { // Apply defaults from the new space std::set paramlist; paramlist.insert("scaninterval"); paramlist.insert("scan_rain_interval"); paramlist.insert("scanrate"); paramlist.insert("headroom"); paramlist.insert("drainperiod"); paramlist.insert("graceperiod"); paramlist.insert("max.ropen"); paramlist.insert("max.wopen"); for (auto it = paramlist.begin(); it != paramlist.end(); ++it) { std::string value = it_space->second->GetConfigMember(*it); if (value.length()) { int64_t uvalue = StringConversion::GetSizeFromString(value.c_str()); fs->SetLongLong(it->c_str(), uvalue); oss << "info: applying space config " << *it << "=" << value << std::endl; } } FsView::gFsView.StoreFsConfig(fs); oss << "success: filesystem " << (int) fs->GetId() << " moved to group " << dst << std::endl; stdOut = oss.str().c_str(); } else { eos_static_err("failed to move fsid: %i to group: %s", fsid, dst.c_str()); oss << "error: failed to move filesystem " << fsid << " to group " << dst << std::endl; stdErr = oss.str().c_str(); return EINVAL; } return 0; } //------------------------------------------------------------------------------ // Move a filesystem to a space //------------------------------------------------------------------------------ int proc_mv_fs_space(FsView& fs_view, const std::string& src, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force) { using eos::mgm::FsView; std::ostringstream oss; // Check if file system is not already in this space FileSystem::fsid_t fsid = strtol(src.c_str(), nullptr, 10); FileSystem* fs = fs_view.mIdView.lookupByID(fsid); if (fs) { if (!proc_fs_can_mv(fs, dst, stdOut, stdErr, force)) { return EINVAL; } } else { eos_static_err("msg=\"no such file system\" fsid=%lu", fsid); oss << "error: no such fsid: " << fsid << std::endl; stdErr = oss.str().c_str(); return EINVAL; } auto it_space = fs_view.mSpaceView.find(dst); if (it_space == fs_view.mSpaceView.end()) { eos_static_info("msg=\"creating space %s\"", dst.c_str()); FsSpace* new_space = new FsSpace(dst.c_str()); fs_view.mSpaceView[dst] = new_space; it_space = fs_view.mSpaceView.find(dst); } int grp_size = std::atoi(it_space->second->GetConfigMember ("groupsize").c_str()); int grp_mod = std::atoi(it_space->second->GetConfigMember ("groupmod").c_str()); if ((dst == eos::common::EOS_SPARE_GROUP) && grp_mod) { eos_static_err("%s", "msg=\"space spare must have groupmod 0\""); oss << "error: space \"spare\" must have groupmod 0. Please update the " << "space configuration using \"eos space define " << std::endl; stdErr = oss.str().c_str(); stdOut.erase(); return EINVAL; } std::list sorted_grps; if (grp_mod) { sorted_grps = proc_sort_groups_by_priority(fs_view, dst, grp_size, grp_mod); } else { // Special case for spare space which doesn't have groups sorted_grps.emplace_back(eos::common::EOS_SPARE_GROUP); } bool done = false; for (const auto& grp : sorted_grps) { if (proc_mv_fs_group(fs_view, src, grp, stdOut, stdErr, force) == 0) { stdErr = ""; done = true; break; } } if (!done) { eos_static_err("msg=\"failed to add fs %s to space %s\"", src.c_str(), dst.c_str()); std::ostringstream oss; oss << "error: failed to add file system " << src.c_str() << " to space " << dst.c_str() << " - no suitable group found" << std::endl; stdOut.erase(); stdErr = oss.str().c_str(); return EINVAL; } return 0; } //------------------------------------------------------------------------------ // Sort the groups in a space by priority - the first ones are the ones that // are most suitable to add a new file system to them. //------------------------------------------------------------------------------ std::list proc_sort_groups_by_priority(FsView& fs_view, const std::string& space, size_t grp_size, size_t grp_mod) { using eos::mgm::FsView; struct cmp_grp { bool operator()(FsGroup* a, FsGroup* b) { return (a->size() < b->size()); } }; // All groups in space are possible candidates std::list grps; // first - highest priority, last - lowest std::set set_grps; std::string node; for (std::uint32_t i = 0; i < grp_mod; ++i) { node = space; node += "."; node += std::to_string(i); set_grps.insert(node); } // Get all groups belonging to the current space and that have less than the // max number of file systems for (auto it = fs_view.mGroupView.begin(); it != fs_view.mGroupView.end(); ++it) { if (it->first.find(space) == 0) { set_grps.erase(it->first); if (it->second->size() < grp_size) { grps.push_back(it->second); } } } grps.sort(cmp_grp()); // Any groups left in the initial set represent completely new groups // without any file systems i.e highest priority std::list ret_grps(set_grps.begin(), set_grps.end()); for (auto && grp : grps) { ret_grps.push_back(grp->mName); } return ret_grps; } //------------------------------------------------------------------------------ // Move a group to a space //------------------------------------------------------------------------------ int proc_mv_grp_space(FsView& fs_view, const std::string& src, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force) { using eos::mgm::FsView; std::ostringstream oss; std::list failed_fs; // file systems that couldn't be moved auto it_grp = fs_view.mGroupView.find(src); if (it_grp == fs_view.mGroupView.end()) { eos_static_err("group %s does not exist", src.c_str()); oss << "error: group " << src << " does not exist"; stdErr = oss.str().c_str(); return EINVAL; } // Get all file systems from the group since iterators will be invalidated // when we start moving them to the new destination FsGroup* grp = it_grp->second; std::list lst_fsids; for (auto it = grp->begin(); it != grp->end(); ++it) { lst_fsids.push_back(std::to_string(*it)); } for (const auto& sfsid : lst_fsids) { if (proc_mv_fs_space(fs_view, sfsid, dst, stdOut, stdErr, force)) { failed_fs.push_back(sfsid); } } if (!failed_fs.empty()) { oss << "warning: the following file systems could not be moved "; for (auto && elem : failed_fs) { oss << elem << " "; } oss << std::endl; stdOut.erase(); stdErr = oss.str().c_str();; return EINVAL; } else { oss << "success: all file systems in group " << src << " have been " << "moved to space " << dst << std::endl; stdOut = oss.str().c_str(); stdErr.erase(); } return 0; } //------------------------------------------------------------------------------ // Move space to space //------------------------------------------------------------------------------ int proc_mv_space_space(FsView& fs_view, const std::string& src, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force) { using eos::mgm::FsView; std::ostringstream oss; std::list failed_fs; // file systems that couldn't be moved auto it_space1 = fs_view.mSpaceView.find(src); if (it_space1 == fs_view.mSpaceView.end()) { eos_static_err("space %s does not exist", src.c_str()); oss << "error: space " << src << " does not exist"; stdErr = oss.str().c_str(); return EINVAL; } auto it_space2 = fs_view.mSpaceView.find(dst); if (it_space2 == fs_view.mSpaceView.end()) { eos_static_err("space %s does not exist", dst.c_str()); oss << "error: space " << dst << " does not exist"; stdErr = oss.str().c_str(); return EINVAL; } // Get all file systems from the space since iterators will be invalidated // when we start moving them to the new destination FsSpace* space1 = it_space1->second; std::list lst_fsids; for (auto it = space1->begin(); it != space1->end(); ++it) { lst_fsids.push_back(std::to_string(*it)); } for (const auto& sfsid : lst_fsids) { if (proc_mv_fs_space(fs_view, sfsid, dst, stdOut, stdErr, force)) { failed_fs.push_back(sfsid); } } if (!failed_fs.empty()) { oss << "warning: the following file systems could not be moved "; for (auto && elem : failed_fs) { oss << elem << " "; } oss << std::endl; stdOut.erase(); stdErr = oss.str().c_str();; return EINVAL; } else { oss << "success: all file systems in space " << src << " have been " << " moved to space " << dst << std::endl; stdOut = oss.str().c_str(); stdErr.erase(); } return 0; } //------------------------------------------------------------------------------ // Move FS to a new node //------------------------------------------------------------------------------ int proc_mv_fs_node(FsView& fs_view, const std::string& src, const std::string& dst, XrdOucString& stdOut, XrdOucString& stdErr, bool force, eos::common::VirtualIdentity& vid_in, mq::MessagingRealm* realm) { std::ostringstream oss; eos::common::FileSystem::fsid_t fsid = stoi(src.c_str()); FileSystem* fs = fs_view.mIdView.lookupByID(fsid); if (fs) { FileSystem::fs_snapshot_t snapshot; if (fs->SnapShotFileSystem(snapshot)) { // pretend this is empty fs->SetString("configstatus", "empty"); std::string a; std::string b; std::string id = src; std::string uuid = snapshot.mUuid; std::string hostport = snapshot.mHostPort; std::string path = snapshot.mPath; std::string space = snapshot.mSpace; std::string group = snapshot.mGroup; std::string sharedfs = snapshot.mSharedFs; std::string configstatus = eos::common::FileSystem::GetConfigStatusAsString( snapshot.mConfigStatus); int rc = proc_fs_rm(a , b, id, stdOut, stdErr, vid_in); FsView::gFsView.ViewMutex.UnLockWrite(); if (!rc) { std::string nodename = "/eos/"; nodename += dst; nodename += "/fst"; int rc = proc_fs_add(realm, id, uuid, nodename, path, getenv("EOS_ALLOW_SAME_HOST_IN_GROUP") ? group : space, configstatus, sharedfs, stdOut, stdErr, vid_in, true); if (rc) { oss << "error: failed to reinsert filesystem with id='" << fsid << "' - this is really really bad!!!" << std::endl; stdErr += oss.str().c_str(); stdOut.erase(); } } else { oss << "error: failed ot snapshot filesystem with id='" << fsid << "'" << std::endl; stdErr = oss.str().c_str(); stdOut.erase(); } FsView::gFsView.ViewMutex.LockWrite(); } else { oss << "error: failed ot snapshot filesystem with id='" << fsid << "'" << std::endl; stdErr = oss.str().c_str(); stdOut.erase(); } } else { oss << "error: no such filesystem with id='" << fsid << "'" << std::endl; stdErr = oss.str().c_str(); stdOut.erase(); } return 0; } //------------------------------------------------------------------------------ // Remove filesystem //------------------------------------------------------------------------------ int proc_fs_rm(std::string& nodename, std::string& mountpoint, std::string& id, XrdOucString& stdOut, XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in) { int retc = 0; const std::string vid_hostname = vid_in.host; eos::common::FileSystem::fsid_t fsid = 0; if (id.length()) { fsid = stoi(id); } FileSystem* fs = 0; if (id.length()) { // find by id fs = FsView::gFsView.mIdView.lookupByID(fsid); } else { if (mountpoint.length() && nodename.length()) { std::string queuepath = nodename; queuepath += mountpoint; fs = FsView::gFsView.FindByQueuePath(queuepath); } } if (fs) { std::string nodename = fs->GetString("host"); std::string cstate = fs->GetString("configstatus"); size_t dpos = 0; if ((dpos = nodename.find('.')) != std::string::npos) { nodename.erase(dpos); } // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set then we skip // the check below as this currently breaks the Kubernetes setup. bool skip_hostname_match = false; if (getenv("EOS_SKIP_SSS_HOSTNAME_MATCH")) { skip_hostname_match = true; } if ((vid_in.uid == 0) || (vid_in.prot == "sss")) { if ((vid_in.prot == "sss") && (vid_in.uid != 0)) { if (!skip_hostname_match && vid_hostname.compare(0, nodename.length(), nodename, 0, nodename.length())) { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (1)\n"; retc = EPERM; return retc; } } } else { stdErr = "error: filesystems can only be configured as 'root' or " "from the server mounting them using sss protocol (2)\n"; retc = EPERM; return retc; } // @note We can only remove a file system only if it's empty and // exceptionally if we are a slave MGM and the fs is in drain mode // and we got a request to remove it. The empty state from the // master MGM is never propagated to the slaves. if ((cstate != "empty") && !((cstate == "drain") && !gOFS->mMaster->IsMaster())) { stdErr = "error: you can only remove file systems which are in 'empty' status"; retc = EINVAL; } else { if (!FsView::gFsView.UnRegister(fs, true, true)) { stdErr = "error: couldn't unregister the filesystem "; stdErr += nodename.c_str(); stdErr += " "; stdErr += mountpoint.c_str(); stdErr += " "; stdErr += id.c_str(); stdErr += "from the FsView"; retc = EFAULT; } else { stdOut = "success: unregistered "; stdOut += nodename.c_str(); stdOut += " "; stdOut += mountpoint.c_str(); stdOut += " "; stdOut += id.c_str(); stdOut += " from the FsView"; } } } else { stdErr = "error: there is no filesystem defined by "; stdErr += nodename.c_str(); stdErr += " "; stdErr += mountpoint.c_str(); stdErr += " "; stdErr += id.c_str(); stdErr += " "; retc = EINVAL; } return retc; } //------------------------------------------------------------------------------- // Clean unlinked files from filesystem //------------------------------------------------------------------------------- int proc_fs_dropdeletion(const eos::common::FileSystem::fsid_t& fsid, const eos::common::VirtualIdentity& vid_in, std::string& out, std::string& err) { if (fsid == 0ul) { err = "error: no such filesystem fsid=0"; return EINVAL; } if ((vid_in.uid != 0)) { err = "error: command can only be executed by 'root'"; return EPERM; } eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex); if (gOFS->eosFsView->clearUnlinkedFileList(fsid)) { out = SSTR("success: dropped deletions on fsid=" << fsid).c_str(); } else { out = SSTR("note: there is no deletion list for fsid=" << fsid).c_str(); } return 0; } //------------------------------------------------------------------------------- // Clean ghost entries in a filesystem view //------------------------------------------------------------------------------- int proc_fs_dropghosts(const eos::common::FileSystem::fsid_t& fsid, const std::set& set_fids, const eos::common::VirtualIdentity& vid_in, std::string& out, std::string& err) { if (fsid == 0ul) { err = "error: no such filesystem fsid=0"; return EINVAL; } if ((vid_in.uid != 0)) { err = "error: command can only be executed by 'root'"; return EPERM; } std::set to_delete; if (set_fids.empty()) { // We check all the files on that filesystem eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex); for (auto it_fid = gOFS->eosFsView->getFileList(fsid); (it_fid && it_fid->valid()); it_fid->next()) { try { auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement()); } catch (eos::MDException& e) { if (e.getErrno() == ENOENT) { out += SSTR("# removing id: " << it_fid->getElement() << "\n").c_str(); to_delete.insert(it_fid->getElement()); } } } } else { eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex); for (const auto& fid : set_fids) { try { auto fmd = gOFS->eosFileService->getFileMD(fid); } catch (eos::MDException& e) { if (e.getErrno() == ENOENT) { out += SSTR("# removing id: " << fid << "\n").c_str(); to_delete.insert(fid); } } } } eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex); for (const auto& fid : to_delete) { gOFS->eosFsView->eraseEntry(fsid, fid); } out += SSTR("success: dropped " << to_delete.size() << " ghost entries from fsid=" << fsid).c_str(); return 0; } EOSMGMNAMESPACE_END