437 lines
16 KiB
C++
437 lines
16 KiB
C++
#include <common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h>
|
|
#include <common/net/message/control/GenericResponseMsg.h>
|
|
#include <common/net/message/storage/attribs/SetAttrRespMsg.h>
|
|
#include <common/net/message/storage/attribs/SetLocalAttrMsg.h>
|
|
#include <common/net/message/storage/attribs/SetLocalAttrRespMsg.h>
|
|
#include <common/toolkit/MessagingTk.h>
|
|
#include <components/FileEventLogger.h>
|
|
#include <components/worker/SetChunkFileAttribsWork.h>
|
|
#include <program/Program.h>
|
|
#include <session/EntryLock.h>
|
|
#include "SetAttrMsgEx.h"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
|
|
bool SetAttrMsgEx::processIncoming(ResponseContext& ctx)
|
|
{
|
|
#ifdef BEEGFS_DEBUG
|
|
const char* logContext = "SetAttrMsg incoming";
|
|
#endif // BEEGFS_DEBUG
|
|
|
|
EntryInfo* entryInfo = getEntryInfo();
|
|
|
|
LOG_DEBUG(logContext, Log_DEBUG,
|
|
"BuddyMirrored: " + std::string(entryInfo->getIsBuddyMirrored() ? "Yes" : "No") +
|
|
" Secondary: " + std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)
|
|
? "Yes" : "No") );
|
|
(void) entryInfo;
|
|
|
|
// update operation counters (here on top because we have an early sock release in this msg)
|
|
updateNodeOp(ctx, MetaOpCounter_SETATTR);
|
|
|
|
return BaseType::processIncoming(ctx);
|
|
}
|
|
|
|
std::tuple<FileIDLock, FileIDLock> SetAttrMsgEx::lock(EntryLockStore& store)
|
|
{
|
|
if (DirEntryType_ISDIR(getEntryInfo()->getEntryType()))
|
|
return std::make_tuple(
|
|
FileIDLock(),
|
|
FileIDLock(&store, getEntryInfo()->getEntryID(), true));
|
|
else
|
|
return std::make_tuple(
|
|
FileIDLock(&store, getEntryInfo()->getEntryID(), true),
|
|
FileIDLock());
|
|
}
|
|
|
|
std::unique_ptr<MirroredMessageResponseState> SetAttrMsgEx::executeLocally(ResponseContext& ctx,
|
|
bool isSecondary)
|
|
{
|
|
const char* logContext = "Set file attribs";
|
|
|
|
App* app = Program::getApp();
|
|
MetaStore* metaStore = app->getMetaStore();
|
|
Config* cfg = app->getConfig();
|
|
|
|
EntryInfo* entryInfo = getEntryInfo();
|
|
FhgfsOpsErr setAttrRes;
|
|
const bool forwardToStorage = !entryInfo->getIsBuddyMirrored()
|
|
|| !hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond);
|
|
|
|
bool responseSent = false;
|
|
|
|
if (entryInfo->getParentEntryID().empty() || DirEntryType_ISDIR(entryInfo->getEntryType()))
|
|
{
|
|
// special case: setAttr for root directory
|
|
if (entryInfo->getParentEntryID().empty())
|
|
setAttrRes = setAttrRoot();
|
|
else
|
|
setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs());
|
|
|
|
if (setAttrRes != FhgfsOpsErr_SUCCESS || !(shouldFixTimestamps() || getFileEvent()))
|
|
return boost::make_unique<ResponseState>(setAttrRes);
|
|
|
|
if (shouldFixTimestamps())
|
|
{
|
|
auto dir = metaStore->referenceDir(entryInfo->getEntryID(),
|
|
entryInfo->getIsBuddyMirrored(), true);
|
|
if (dir)
|
|
{
|
|
fixInodeTimestamp(*dir, inodeTimestamps);
|
|
metaStore->releaseDir(dir->getID());
|
|
}
|
|
}
|
|
|
|
if (!isSecondary && getFileEvent() && app->getFileEventLogger() && getFileEvent())
|
|
{
|
|
unsigned numHardlinks = 0;
|
|
auto dir = metaStore->referenceDir(entryInfo->getEntryID(),
|
|
entryInfo->getIsBuddyMirrored(), true);
|
|
if (likely(dir))
|
|
{
|
|
numHardlinks = dir->getNumHardlinks();
|
|
metaStore->releaseDir(dir->getID());
|
|
}
|
|
|
|
EventContext eventCtx = makeEventContext(entryInfo, entryInfo->getParentEntryID(),
|
|
getMsgHeaderUserID(), "", numHardlinks, isSecondary);
|
|
logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx);
|
|
}
|
|
|
|
return boost::make_unique<ResponseState>(setAttrRes);
|
|
}
|
|
|
|
// update nlink count if requested by caller
|
|
if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) &&
|
|
isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_DECR_NLINKCNT))
|
|
{
|
|
// error: both increase and decrease of nlink count was requested
|
|
return boost::make_unique<ResponseState>(FhgfsOpsErr_INTERNAL);
|
|
}
|
|
else if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) ||
|
|
isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_DECR_NLINKCNT))
|
|
{
|
|
int val = isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) ? 1 : -1;
|
|
setAttrRes = metaStore->incDecLinkCount(entryInfo, val);
|
|
return boost::make_unique<ResponseState>(setAttrRes);
|
|
}
|
|
|
|
// we need to reference the inode first, as we want to use it several times
|
|
auto [inode, referenceRes] = metaStore->referenceFile(entryInfo);
|
|
if (!inode)
|
|
return boost::make_unique<ResponseState>(referenceRes);
|
|
|
|
// in the following we need to distinguish between several cases.
|
|
// 1. if times shall be updated we need to send the update to the storage servers first
|
|
// because, we need to rely on storage server's attrib version to prevent races with
|
|
// other messages that update the times (e.g. Close)
|
|
// 2. if times shall not be updated (must be chmod or chown then) and quota is enabled
|
|
// we first set the local attributes and then send the update to the storage server.
|
|
// if an early response optimization is set in this case we send the response between
|
|
// these two steps
|
|
// 3. no times update (i.e. chmod or chown) and quota is disabled => only update locally,
|
|
// as we don't have a reason to waste time with contacting the storage servers
|
|
|
|
bool timeUpdate =
|
|
getValidAttribs() & (SETATTR_CHANGE_MODIFICATIONTIME | SETATTR_CHANGE_LASTACCESSTIME);
|
|
|
|
// note: time update and mode/owner update can never be at the same time
|
|
if (timeUpdate && forwardToStorage)
|
|
{
|
|
// only relevant if message needs to be sent to the storage server. if not (e.g.
|
|
// because this is secondary of a buddy group, we can use the "default" case later
|
|
setAttrRes = setChunkFileAttribs(*inode, true);
|
|
|
|
if (setAttrRes == FhgfsOpsErr_SUCCESS)
|
|
setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs());
|
|
}
|
|
else if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA) && forwardToStorage)
|
|
{
|
|
const UInt16Vector* targetIdVec = inode->getStripePattern()->getStripeTargetIDs();
|
|
ExceededQuotaStorePtr quotaExStore;
|
|
for (auto targetIter = targetIdVec->begin(); targetIter != targetIdVec->end(); targetIter++)
|
|
{
|
|
if (inode->getStripePattern()->getPatternType() == StripePatternType_BuddyMirror)
|
|
{
|
|
uint16_t primaryTargetID = app->getStorageBuddyGroupMapper()->getPrimaryTargetID(*targetIter);
|
|
quotaExStore = app->getExceededQuotaStores()->get(primaryTargetID);
|
|
}
|
|
else
|
|
{
|
|
// this is not very efficient at the moment, as we need to look at every quota exceeded
|
|
// store for every target in the stripe pattern; we need to do that because storage pools
|
|
// might change over time, i.e. the pool that is stored in the metadata doesn't always
|
|
// match the actual targets a file is stored on
|
|
quotaExStore = app->getExceededQuotaStores()->get(*targetIter);
|
|
}
|
|
|
|
// check if exceeded quotas exists, before doing a more expensive and explicit check
|
|
if (quotaExStore && quotaExStore->someQuotaExceeded())
|
|
{ // store for this target is present AND someQuotaExceeded() was true
|
|
QuotaExceededErrorType quotaExceeded = quotaExStore->isQuotaExceeded(
|
|
getAttribs()->userID, getAttribs()->groupID);
|
|
|
|
if (quotaExceeded != QuotaExceededErrorType_NOT_EXCEEDED)
|
|
{
|
|
LogContext(logContext).log(Log_NOTICE,
|
|
QuotaData::QuotaExceededErrorTypeToString(quotaExceeded) + " "
|
|
"UID: " + StringTk::uintToStr(getAttribs()->userID) + "; "
|
|
"GID: " + StringTk::uintToStr(getAttribs()->groupID));
|
|
|
|
setAttrRes = FhgfsOpsErr_DQUOT;
|
|
goto finish;
|
|
}
|
|
}
|
|
}
|
|
|
|
// only relevant if message needs to be sent to the storage server. if not (e.g.
|
|
// because this is secondary of a buddy group, we can use the "default" case later
|
|
setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs());
|
|
|
|
// allowed only if early chown respnse for quota is set
|
|
if (cfg->getQuotaEarlyChownResponse())
|
|
{
|
|
earlyComplete(ctx, ResponseState(setAttrRes));
|
|
responseSent = true;
|
|
}
|
|
|
|
if (setAttrRes == FhgfsOpsErr_SUCCESS)
|
|
setChunkFileAttribs(*inode, false);
|
|
}
|
|
else
|
|
{
|
|
setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs());
|
|
}
|
|
|
|
finish:
|
|
if (shouldFixTimestamps())
|
|
fixInodeTimestamp(*inode, inodeTimestamps, entryInfo);
|
|
|
|
if (!isSecondary && setAttrRes == FhgfsOpsErr_SUCCESS &&
|
|
app->getFileEventLogger() && getFileEvent())
|
|
{
|
|
EventContext eventCtx = makeEventContext(
|
|
entryInfo,
|
|
entryInfo->getParentEntryID(),
|
|
getMsgHeaderUserID(),
|
|
"",
|
|
inode->getNumHardlinks(),
|
|
isSecondary
|
|
);
|
|
|
|
logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx);
|
|
}
|
|
|
|
metaStore->releaseFile(entryInfo->getParentEntryID(), inode);
|
|
|
|
if (!responseSent)
|
|
return boost::make_unique<ResponseState>(setAttrRes);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
void SetAttrMsgEx::forwardToSecondary(ResponseContext& ctx)
|
|
{
|
|
sendToSecondary(ctx, *this, NETMSGTYPE_SetAttrResp);
|
|
}
|
|
|
|
FhgfsOpsErr SetAttrMsgEx::setAttrRoot()
|
|
{
|
|
App* app = Program::getApp();
|
|
DirInode* rootDir = app->getRootDir();
|
|
|
|
NumNodeID expectedOwnerNode = rootDir->getIsBuddyMirrored()
|
|
? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() )
|
|
: app->getLocalNode().getNumID();
|
|
|
|
if ( expectedOwnerNode != rootDir->getOwnerNodeID() )
|
|
return FhgfsOpsErr_NOTOWNER;
|
|
|
|
if(!rootDir->setAttrData(getValidAttribs(), getAttribs() ) )
|
|
return FhgfsOpsErr_INTERNAL;
|
|
|
|
|
|
return FhgfsOpsErr_SUCCESS;
|
|
}
|
|
|
|
FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribs(FileInode& file, bool requestDynamicAttribs)
|
|
{
|
|
StripePattern* pattern = file.getStripePattern();
|
|
|
|
if( (pattern->getStripeTargetIDs()->size() > 1) ||
|
|
(pattern->getPatternType() == StripePatternType_BuddyMirror) )
|
|
return setChunkFileAttribsParallel(file, requestDynamicAttribs);
|
|
else
|
|
return setChunkFileAttribsSequential(file, requestDynamicAttribs);
|
|
}
|
|
|
|
/**
|
|
* Note: This method does not work for mirrored files; use setChunkFileAttribsParallel() for those.
|
|
*/
|
|
FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribsSequential(FileInode& inode,
|
|
bool requestDynamicAttribs)
|
|
{
|
|
const char* logContext = "Set chunk file attribs S";
|
|
|
|
StripePattern* pattern = inode.getStripePattern();
|
|
const UInt16Vector* targetIDs = pattern->getStripeTargetIDs();
|
|
TargetMapper* targetMapper = Program::getApp()->getTargetMapper();
|
|
TargetStateStore* targetStates = Program::getApp()->getTargetStateStore();
|
|
NodeStore* nodes = Program::getApp()->getStorageNodes();
|
|
|
|
FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS;
|
|
|
|
std::string fileID(inode.getEntryID());
|
|
|
|
PathInfo pathInfo;
|
|
inode.getPathInfo(&pathInfo);
|
|
|
|
// send request to each node and receive the response message
|
|
unsigned currentTargetIndex = 0;
|
|
for(UInt16VectorConstIter iter = targetIDs->begin();
|
|
iter != targetIDs->end();
|
|
iter++, currentTargetIndex++)
|
|
{
|
|
uint16_t targetID = *iter;
|
|
bool enableFileCreation = (currentTargetIndex == 0); // enable inode creation of first node
|
|
|
|
SetLocalAttrMsg setAttrMsg(fileID, targetID, &pathInfo, getValidAttribs(), getAttribs(),
|
|
enableFileCreation);
|
|
|
|
setAttrMsg.setMsgHeaderUserID(inode.getUserID());
|
|
|
|
if(isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA))
|
|
setAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_USE_QUOTA);
|
|
|
|
RequestResponseArgs rrArgs(NULL, &setAttrMsg, NETMSGTYPE_SetLocalAttrResp);
|
|
RequestResponseTarget rrTarget(targetID, targetMapper, nodes);
|
|
|
|
rrTarget.setTargetStates(targetStates);
|
|
|
|
// send request to node and receive response
|
|
FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs);
|
|
|
|
if(requestRes != FhgfsOpsErr_SUCCESS)
|
|
{ // communication error
|
|
LogContext(logContext).log(Log_WARNING,
|
|
"Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; "
|
|
"fileID: " + inode.getEntryID() + "; "
|
|
"Error: " + boost::lexical_cast<std::string>(requestRes));
|
|
|
|
if(retVal == FhgfsOpsErr_SUCCESS)
|
|
retVal = requestRes;
|
|
|
|
continue;
|
|
}
|
|
|
|
// correct response type received
|
|
const auto setRespMsg = (const SetLocalAttrRespMsg*)rrArgs.outRespMsg.get();
|
|
|
|
FhgfsOpsErr setRespResult = setRespMsg->getResult();
|
|
if (setRespResult != FhgfsOpsErr_SUCCESS)
|
|
{ // error: local inode attribs not set
|
|
LogContext(logContext).log(Log_WARNING,
|
|
"Target failed to set attribs of chunk file: " + StringTk::uintToStr(targetID) + "; "
|
|
"fileID: " + inode.getEntryID());
|
|
|
|
if(retVal == FhgfsOpsErr_SUCCESS)
|
|
retVal = setRespResult;
|
|
|
|
continue;
|
|
}
|
|
|
|
// success: local inode attribs set
|
|
if (setRespMsg->isMsgHeaderFeatureFlagSet(SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS))
|
|
{
|
|
DynamicFileAttribsVec dynAttribsVec(1);
|
|
setRespMsg->getDynamicAttribs(&(dynAttribsVec[0]));
|
|
inode.setDynAttribs(dynAttribsVec);
|
|
}
|
|
|
|
LOG_DEBUG(logContext, Log_DEBUG,
|
|
"Target has set attribs of chunk file: " + StringTk::uintToStr(targetID) + "; " +
|
|
"fileID: " + inode.getEntryID());
|
|
}
|
|
|
|
if(unlikely(retVal != FhgfsOpsErr_SUCCESS) )
|
|
LogContext(logContext).log(Log_WARNING,
|
|
"Problems occurred during setting of chunk file attribs. "
|
|
"fileID: " + inode.getEntryID());
|
|
|
|
return retVal;
|
|
}
|
|
|
|
FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribsParallel(FileInode& inode, bool requestDynamicAttribs)
|
|
{
|
|
const char* logContext = "Set chunk file attribs";
|
|
|
|
App* app = Program::getApp();
|
|
MultiWorkQueue* slaveQ = app->getCommSlaveQueue();
|
|
StripePattern* pattern = inode.getStripePattern();
|
|
const UInt16Vector* targetIDs = pattern->getStripeTargetIDs();
|
|
|
|
size_t numTargetWorks = targetIDs->size();
|
|
|
|
FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS;
|
|
DynamicFileAttribsVec dynAttribsVec(numTargetWorks);
|
|
FhgfsOpsErrVec nodeResults(numTargetWorks);
|
|
SynchronizedCounter counter;
|
|
|
|
PathInfo pathInfo;
|
|
inode.getPathInfo(&pathInfo);
|
|
|
|
// generate work for storage targets...
|
|
|
|
for(size_t i=0; i < numTargetWorks; i++)
|
|
{
|
|
bool enableFileCreation = (i == 0); // enable inode creation on first target
|
|
|
|
SetChunkFileAttribsWork* work = NULL;
|
|
if (requestDynamicAttribs) // we are interested in the chunk's dynamic attributes, because we
|
|
{ // modify timestamps and this operation might race with others
|
|
work = new SetChunkFileAttribsWork(inode.getEntryID(), getValidAttribs(), getAttribs(),
|
|
enableFileCreation, pattern, (*targetIDs)[i], &pathInfo, &(dynAttribsVec[i]),
|
|
&(nodeResults[i]), &counter);
|
|
}
|
|
else
|
|
{
|
|
work = new SetChunkFileAttribsWork(inode.getEntryID(), getValidAttribs(), getAttribs(),
|
|
enableFileCreation, pattern, (*targetIDs)[i], &pathInfo, NULL, &(nodeResults[i]),
|
|
&counter);
|
|
}
|
|
|
|
work->setQuotaChown(isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA) );
|
|
work->setMsgUserID(getMsgHeaderUserID() );
|
|
|
|
slaveQ->addDirectWork(work);
|
|
}
|
|
|
|
// wait for work completion...
|
|
counter.waitForCount(numTargetWorks);
|
|
|
|
// we set the dynamic attribs here, no matter if the remote operation suceeded or not. If it
|
|
// did not, storageVersion will be zero and the corresponding data will be ignored
|
|
// note: if the chunk's attributes were not requested from server at all, this is also OK here,
|
|
// because the storageVersion will be 0
|
|
inode.setDynAttribs(dynAttribsVec);
|
|
|
|
// check target results...
|
|
for(size_t i=0; i < numTargetWorks; i++)
|
|
{
|
|
if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) )
|
|
{
|
|
LogContext(logContext).log(Log_WARNING,
|
|
"Problems occurred during setting of chunk file attribs. "
|
|
"fileID: " + inode.getEntryID());
|
|
|
|
retVal = nodeResults[i];
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
|
|
error_exit:
|
|
return retVal;
|
|
}
|